The problem is mundane and the solutions are all creepy
My most useful technical conversations of the last two years happened inside ChatGPT, Claude and Gemini — debugging sessions, architecture arguments, long derivations. All of it trapped in a scrolling div behind someone else's login, un-greppable, un-archivable, one account suspension away from gone.
Search for an exporter and you find a genre: paste your conversation into our site, or install our extension, which requests permission to read data on all websites and phones home for "analytics." You're handing a stranger the transcript of everything you've asked an AI. For most people that's their work, their health, their finances, their business ideas.
The fix isn't a better privacy policy. It's an architecture where the privacy claim is structurally verifiable — where you don't have to trust me, because I've deprived myself of the ability to misbehave.
Two permissions, zero network calls
The whole extension manifest asks for:
"permissions": ["activeTab", "downloads"]
That's it. activeTab means it can only touch the page you're currently looking at, only after you click the icon — not "all sites," not in the background. downloads means it can save a file. There is no host_permissions block, no background fetch, no analytics endpoint, no telemetry.
You don't have to take my word for any of this. Open the manifest — it's ten lines. The absence of a permission is a much stronger guarantee than the presence of a privacy policy, because the browser enforces it and the policy doesn't.
Everything — DOM extraction, formatting, serialization — happens in the page's own context and terminates at the download folder. The extension has no server because there's nothing a server could usefully do.
The actually hard part: lazy rendering
Here's what I didn't expect to be the bulk of the work.
All three platforms virtualize their message lists. A 200-message conversation does not exist in the DOM — only the 15 or so messages near your viewport do. Scroll up and old ones get mounted while the ones below get destroyed. If you naively query the message nodes and serialize them, you export a fragment of the middle of the conversation and never notice, because the output looks plausible.
So before it can read anything, the extension has to drive the page: programmatically scroll to force the virtualizer to mount every message, waiting for content to settle at each step, until it reaches a stable full transcript. Only then does extraction start. It's the least glamorous code in the project and the reason the exports are actually complete.
And then the selectors rot
Each platform needs its own DOM adapter, and none of them owe me a stable interface. ChatGPT, Claude and Gemini all ship UI changes on their own schedule, and any of those can silently break extraction. This is the permanent tax on scraping a UI you don't control, and there's no clever way out — only a small enough adapter per platform that fixing one is a ten-minute job rather than a refactor.
Four formats, because export is not one use case
| Format | For |
|---|---|
| Markdown | Pasting into notes, docs, a PR. The default, and the clipboard button copies it directly. |
| JSON | Feeding back into a pipeline. Structured, machine-readable. |
| Plain text | Grep. Diff. Archive. |
| Self-contained HTML | Reading it later, styled, offline. One file, no assets, respects your system dark mode. |
Code blocks keep their fences and language tags — which sounds trivial until you've exported a debugging session and found your Python collapsed into unindented prose. Files are named after the conversation title, because conversation(3).txt is not an archive.
The general point
I build production GenAI systems in a regulated industry, and the discipline that transfers most cleanly to small tools is this: prefer guarantees you can't break over promises you intend to keep.
A privacy policy is a promise. A manifest with two permissions and no network access is a guarantee — enforced by the browser, auditable by a stranger in thirty seconds, and immune to my own future bad decisions. Same instinct as deterministic citation validation in a RAG system: don't ask whether the component behaved, build it so misbehaving isn't reachable.
It's a browser extension for saving chat logs. It is not important software. But the constraint that shaped it is the same one I'd defend on a system that matters.