DEV Community

Cover image for I was tired of losing track of my AI conversations, so I built a Chrome extension
Arham_Q
Arham_Q

Posted on

I was tired of losing track of my AI conversations, so I built a Chrome extension

TL;DR: I built Dendrite a Chrome extension that reads your live Claude and ChatGPT conversations and auto-saves every question, code block, and link into a sidebar. No copy-paste. No Notion. It just works.


The Problem Nobody Talks About (But Everyone Has)
You ask ChatGPT to write a Python function. It does. You keep chatting.
Twenty minutes later, you need that function. You scroll. And scroll. Past the small talk, the wrong answers, the “here’s a revised version,” the “actually, let me correct that.”
It’s gone. Buried. You’re now a digital archaeologist excavating your own conversation.
This happens every single day to developers using AI tools. And the worst part? The AI already did the work. You just can’t find it.
I’ve lost:
• A regex pattern for parsing dates (took 3 back-and-forths to get right)
• A CSS trick for sticky footers that actually worked
• A link to a GitHub repo the AI recommended
All buried in chat history I’ll never scroll to again.
So I stopped complaining and built Dendrite.


What Dentrite does
Dendrite is a Chrome extension that adds a collapsible sidebar to your Claude and ChatGPT tabs. As you chat, it watches the conversation and automatically extracts:
• ✅ Every question you asked
• ✅ Every code block the AI returned
• ✅ Every link that was dropped
• ✅ A running summary of the conversation
No button clicks. No copy-paste ritual. It just syncs live as the conversation updates.


How It Actually Works (The Interesting Part)
Here’s where it gets technically spicy. Claude and ChatGPT have no public API for reading conversation content. So how do you get the data out?
MutationObserver — the browser’s built-in way of watching the DOM for changes.

// Watching for new messages as they appear
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes.length > 0) {
debounce(captureMessages, 400)();
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});


Every time a new message appears in the chat, our observer fires. We then scrape the DOM for structured content.
Extracting code blocks, for example:

function extractCodeBlocks(messageEl) {
const codeBlocks = messageEl.querySelectorAll('pre code');
return Array.from(codeBlocks).map(block => ({
language: block.className.replace('language-', '') || 'text',
content: block.innerText.trim(),
timestamp: Date.now()
}));
}

And Links:

function extractLinks(messageEl) {
const anchors = messageEl.querySelectorAll('a[href]');
return Array.from(anchors)
.map(a => ({ href: a.href, text: a.innerText.trim() }))
.filter(link => link.href.startsWith('http'));
}

The tricky part? Debouncing. ChatGPT and Claude stream their responses token by token. Without debouncing, you’d capture 200 half-finished versions of the same message. The 400ms debounce lets the message finish before we snapshot it.
———
The Hardest Part: Selectors That Break
Here’s what nobody tells you about building browser extensions on top of AI chat apps: the DOM changes without warning.
One day “div.message-content” works. A frontend deploy later, it’s “article[data-testid="conversation-turn"]”. Selectors that worked Tuesday are dead by Friday.
My current approach: use multiple fallback selectors and log which one succeeds.

const MESSAGE_SELECTORS = [
'[data-message-author-role]', // ChatGPT (current)
'.human-turn, .assistant-turn', // Claude (current)
'.message-content', // fallback
];
function findMessages() {
for (const selector of MESSAGE_SELECTORS) {
const els = document.querySelectorAll(selector);
if (els.length > 0) {
console.debug('[Dendrite] Using selector:', selector);
return Array.from(els);
}
}
return [];
}

It’s fragile by nature. That’s the cost of building on platforms you don’t control.

The Stack (Intentionally Boring)

Layer Choice Why
Extension API Manifest V3 Required by Chrome Web Store
Logic Vanilla JavaScript Zero dependencies, instant load
UI HTML + CSS No framework overhead
Storage chrome.storage.local Persistent, private, no server needed
DOM Watching MutationObserver Only way to read live chat updates
Bundler None Ships as-is, no build step

Why “Dendrite”?
Dendrites are the branching receivers of a neuron — they collect incoming signals and feed them into the cell body.
That’s exactly what this extension does: it receives the signals from your AI conversations and feeds them somewhere useful.
But the name also hints at where this is going. V1 is a flat list. V2 will be a topic graph — a visual tree showing how questions across different chats connect. Ask about React in five different conversations? Dendrite will surface that pattern.


Current Limitations (Honesty Section)
I’d rather tell you now than have you find out:
• Selectors break on site updates — I patch them when I catch it, but there’s a delay
• No cloud sync — everything lives in chrome.storage.local. Close the browser, data stays. Uninstall, data’s gone.
• No Firefox support — Manifest V3 differences make it non-trivial. It’s on the list.
• Long chats get heavy — Haven’t optimized storage for 100+ message conversations ye


What I Want to Build Next
• Export to Markdown / Notion / Obsidian
• Keyword search across all saved conversations
• Topic clustering (the actual dendrite graph)
• Firefox port
• Highlight and manually save specific messages
What would you want it to track? Drop it in the comments — I’m actively building and reading every reply.


Follow Along
The extension is in active development. If you want early access or want to follow the build:
• Follow me here for V2 updates
• Comment down your thoughts it’ll be helpful


Built with zero frameworks and a lot of frustration. If you’ve ever lost a good code snippet to the AI chat void, you know why this exists.

Top comments (0)