SWMM ENABLEMENT · TOOLING NOTE
Claude Chat Archive
How the single-file searchable archive of three years of conversations is built and how it works
Robert Dickinson · Build of 14 October 2026 · Document written 18 October 2026
The archive is one HTML file. Open it with a double-click, with no server, no install and no network, and search three years of conversation history offline. This note describes what the file contains, how it is built, how each part works, and how to refresh it when a new export arrives.
Three years of work with Claude produced 1,671 conversations and 17,991 messages. The claude.ai search box finds recent things well and old things poorly, and it cannot search a conversation that has been archived into an export. Every app built in those conversations, every engine defect found, every file format cracked, sits in a transcript that nobody can retrieve by memory alone.
The archive solves one problem: find the conversation again. A few remembered words, a filename, a half-recalled topic, and the file returns the conversation, its full text, and a link that opens the original on claude.ai. It also answers the second-order questions. How many apps came out of this. Which conversations resemble the one in front of me. What was I working on in May.
At a glance
| Figure | Value | Note |
| Conversations | 1,671 | Four merged sources |
| Messages | 17,991 | Human and assistant, both sides |
| App-building conversations | 533 | Produced at least one app file |
| Distinct apps | 800 | One conversation can yield several |
| Claude projects | 15 | 406 uploaded documents |
| Span | Nov 21 2023 to Oct 14 2026 | 1,059 days |
| Days with activity | 720 | 68 percent of the span |
| Searchable text | 26.9 million characters | Full message text, uncapped in practice |
| File size | 12.3 MB | Was 38.9 MB before compression |
| Source | Conversations | Links back to the original |
| Claude.ai chat | 1,565 | Yes, claude.ai/chat/<uuid> |
| DeepSeek | 96 | No, transcript only |
| Claude Design | 6 | No |
| Claude Code | 4 | No |
2. How the file is built
The build is a pipeline of five stages. Nothing in it needs a server, and the only inputs are the Claude.ai data export and the previously shipped HTML file.
- Export. Claude.ai Settings produces a set of zip files. The one that matters is conversations.json, currently 638 MB. The projects export supplies the project list and their document names.
- Extract. A Python script streams that file with ijson rather than loading it into memory, and writes one compact record per conversation. The whole pass takes about ten seconds.
- Enrich. App detection, keyword extraction and the related-conversation index run over the extracted records. Topic labels and keywords already written for earlier builds are carried forward unchanged by conversation id, so only new conversations need new labels.
- Compress. The resulting data.json (29.1 MB) is gzipped to 9.6 MB and base64-encoded to 12.8 MB of text.
- Embed. That text replaces the single __DATA_B64__ placeholder in the page template. The output is one self-contained HTML file.
Why the data is gzipped
The uncompressed build reached 38.9 MB, which is past the limit for sending a file through a chat and slow to load. Compressing the payload cuts the file to 12.3 MB, about a third of its former size, and it loads faster because the browser reads less from disk.
The page therefore carries a small synchronous DEFLATE decoder, about 120 lines, which runs before anything else. It was verified by decompressing the real payload and comparing a SHA-256 hash against the original file: an exact byte-for-byte match, in 630 milliseconds. A synchronous decoder was chosen over the browser’s own DecompressionStream because the latter is asynchronous and would have required restructuring the whole application closure. The decoder also keeps the file working offline in any browser, including older ones.
<script id=”app-data” type=”text/plain”>H4sIAAAA…</script>
const raw = Uint8Array.from(atob(b64), c => c.charCodeAt(0));
const bytes = gunzip(raw); // returns the original bytes
const DATA = JSON.parse(new TextDecoder().decode(bytes));
The decoder passes a plain, uncompressed payload straight through unchanged, so an older build of the file still opens correctly with the current template.
3. The record for one conversation
Every conversation is one object. Field names are one or two characters because the names repeat 1,671 times and the file is shipped, not edited by hand.
| Field | Holds | Example or note |
| i | Conversation uuid | Builds the claude.ai link |
| t | Title | As Claude named it |
| c, u | Created and updated | ISO, 19 characters |
| n | Message count | Both sides |
| p | Preview | First human message, 240 characters |
| s | Full searchable text | Message text only, capped at 1,000,000 |
| f | File basenames | Files written, presented or opened, max 14 |
| tg | Activity flags | code, web, past, ask, viz |
| src | Source | chat, deepseek, design, claude-code |
| tp | Topic labels | One to three short hand-written phrases |
| kw | Keywords | Up to six distinctive terms, clickable |
| ap | Built an app | True or absent |
| ap2 | The apps | Present when one chat produced several |
| rel | Related conversations | Up to six, each with a similarity score |
| proj | Project or frame name | Design records only |
The searchable text holds what was said, not what the tools did. Assistant reasoning and tool output are both excluded. The export puts the assistant’s thinking inside the same message.text field as its reply, which in one 259-message conversation meant 738,445 characters against 254,082 in the visible text. Including it would have tripled the file and returned hits on words that were never shown.
4. How search works
Search runs over all 1,671 records on every keystroke and stays interactive because the whole dataset is already in memory.
Tokenising the query
- Words are split on whitespace and lowercased. All words must match somewhere in a conversation for it to appear, so adding a word narrows the result rather than widening it.
- A phrase in double quotes is one token. “staggered grid” matches that phrase, not the two words anywhere.
- Tokens shorter than two characters are dropped, unless dropping them would leave nothing.
Fuzzy matching
Each token becomes a pattern that tolerates the ways the same term gets written. A hyphen, an underscore or an apostrophe may sit between any two letters, and a space in a phrase may be a space, a hyphen or a non-breaking space. So one query finds cross-section, cross section and cross_section.
One parameter governs a real defect that was found here. The pattern builder escapes its characters differently depending on whether it runs against raw data or against already-escaped HTML. Highlighting works on escaped text; scoring works on raw text. When both used the escaped form, a query such as </div> silently stopped matching anything, because the stored text holds the literal characters and the pattern was looking for the entities. The offending line was:
// before: always escaped, which is only correct for highlight()
word.split(”).map(ch => escapeRe(escapeHtml(ch))).join(GAP)
// after: the caller says which form it needs
word.split(”).map(ch => forEscapedText ? escapeRe(escapeHtml(ch))
: escapeRe(ch)).join(GAP)
Scoring
A conversation scores per token, and the token takes the best field it matched. The weights decide what rises to the top.
| Field matched | Points | Why that weight |
| Title | 100 | The strongest signal that a chat is about the term |
| Topic label | 80 | Hand-written, so a match is deliberate |
| Filename | 50 | Finds the chat that produced a named file |
| Preview | 20 | The opening question |
| Body text | 10, plus 2 per further hit to 20 | Repetition counts a little, not a lot |
Each result card lists which fields matched, so a hit in the body reads differently from a hit in the title. While a search is active the sort switches to relevance by itself; clearing the box restores the chosen order.
5. How apps are detected
An app-building conversation is one that produced an app file. The test runs on what the conversation actually wrote, not on what was said about it.
- Candidates are the files the assistant created or presented: .html, .htm and .jsx. Files that were only read, or that you uploaded, do not count, because opening an old app is not building one.
- An artifact of HTML or React type counts as an app in its own right.
- index.html is treated as a wrapper and dropped when the same conversation produced another app file, and counted when it is the only one.
- Draft names are clustered so that one app with five revisions is one app. Similarity is measured on the filename stem, threshold 0.62, and the last file in a cluster represents it, since that is the finished one.
The threshold is not a guess. Five values were scored against the previous labelled release:
| Threshold | Missed | False positives | File-list mismatches |
| 0.55 | 13 | 0 | 57 |
| 0.58 | 13 | 0 | 56 |
| 0.62 | 13 | 0 | 51 (chosen) |
| 0.66 | 13 | 0 | 51 |
| 0.70 | 13 | 0 | 54 |
The October 2026 build widened this rule. It reads a conversation’s whole file list rather than its first fourteen names, counts React files, and counts artifacts. That took the app count from 656 to 800. About 25 of the new rows come from genuinely new conversations and about 119 are apps that existed all along and went uncounted. Every app the narrower rule found is still counted, so the series only rises.
Pre-2026 figures are a floor, not a count. In 2024 and 2025 the work happened in artifacts and in chat, and those conversations wrote few files, so there is little for the detector to find.
6. How related conversations are found
Each card offers up to six conversations that resemble it. The list is computed at build time, not in the browser.
- Each conversation becomes one document: its title counted three times, its topic labels twice, and the first 20,000 characters of its text once. Weighting the title and the labels keeps the match topical rather than incidental.
- A TF-IDF matrix is built over all 1,671 documents with English stop words removed, terms that appear in only one conversation dropped, and sublinear term frequency.
- Rows are normalised and the full cosine similarity matrix is taken. For each conversation the top six neighbours above 0.15 are stored with their scores.
An earlier version of this feature silently did nothing. It relied on a field that was never populated: 0 of 1,320 records had it, so every card showed an empty list and no error. The replacement was checked by looking for a conversation whose duplicate was known, and finding it at 87 percent similarity.
Cross-source links are part of the point. 918 of the stored links connect conversations from different sources, which is how a DeepSeek discussion surfaces beside the Claude conversation that continued it.
7. What each tab does
Archive
The main view. Search box, filters, and one card per conversation showing the date, title, topic labels, preview, activity tags, keyword chips, matched fields and related conversations. Clicking a card expands it to the full text, collapsed behind a Show more control past 1,200 characters. For a Claude.ai record there is a button that opens the original conversation.
Filters and controls:
- Activity chips: wrote code, searched the web, searched past chats, asked a question back, drew a chart.
- Date range, from and to.
- Sort by newest, recently updated, oldest, most messages or title. Recently updated exists because old apps get revised, and a conversation touched last week should be findable even if it started last year.
- Collections: your own named groups, saved in the browser.
- Export CSV of whatever is currently filtered, and bulk select with its own export.
- Load 100 more at the foot of the list.
Projects
The 15 Claude projects, each with its description, start date and the documents uploaded into it. The largest is Visual SWMM Fortran at 263 documents.
Stats
A GitHub-style activity heatmap, one strip per year, plus messages over time, conversation-length spread, busiest days, and breakdowns by activity tag and project. Clicking a day, a tag or a project jumps back to the Archive tab filtered to it.
Apps
Every app on its own row, with the count in the tab label, currently Apps (800). A conversation that produced three apps appears three times, once per app, each row opening the same conversation. This came from a real complaint: in the early days two or three apps were often built in one sitting, and a single row for that chat hid two of them.
8. What the page remembers
The file writes nothing to disk and sends nothing anywhere. A few conveniences live in the browser’s local storage under the prefix claudeArchive:, namely the theme choice and your collections. Clearing site data loses them; the archive itself is unaffected, since it is all in the file.
Light and dark are both supported, dark by default, with the toggle in the header beside the index-date stamp.
9. Refreshing it when a new export arrives
The refresh needs two inputs and nothing else. No external state, no saved database, no build server.
- Request a full export from Claude.ai Settings and wait for the email.
- Upload two files: the new conversations zip, and the most recent archive HTML.
- The previous data.json and the page template are recovered out of that HTML by decoding the embedded payload. This is what carries the hand-written topic labels and keywords forward so they are never rewritten.
- The extractor runs over the new export, the enrichment steps run again, and the non-Claude.ai records (DeepSeek, Claude Code, Design) are carried across unchanged.
- The page is rebuilt and the date stamp in the header updates by itself from the data.
Upload the most recent HTML, not an older one. The file is the only record of the merged non-Claude.ai sources and of the topic labels. An out-of-date file silently drops them.
A full refresh takes a few minutes, most of it spent uploading the export.
10. Limits and known gaps
- Only Claude.ai records link back to a live conversation. The DeepSeek, Claude Code and Design transcripts are text in the file and nothing more.
- Active days count days with a Claude.ai chat message. The other three sources carry no per-day stamps, so their messages are in the message total but not in the heatmap.
- Tool output is not searchable. An engine number seen in a .rpt dump during a conversation will not be found, because only what was said is indexed.
- Twelve conversations are flagged as apps on the strength of the previous release alone. The old pipeline had a signal that could not be reconstructed, and dropping those rows silently was judged worse than keeping them.
- Thirty-three conversations are empty, with no messages and no title. They were opened and never used, they were in earlier builds, and they are kept so the headline count does not change for an unexplained reason.
- The archive is a snapshot. Anything after the export date is absent, which the header stamp says in as many words.
11. The companion dashboard
A separate page, swmmbob, reads the same figures and presents them as charts: headline totals, volume by month, year by year, the source mix, conversation length, apps per chat, the longest conversations and the projects. Every number on it cites the dataset it came from, and the quality-marker panel carries its own measurement date because it is measured differently from the rest.
AI use
Robert Dickinson directed this work and is responsible for it. He asked for the archive, set its requirements through about two dozen rounds of use and revision, and decided every trade-off recorded in its decision log.
Claude (Opus 5, Anthropic) built it, between July and October 2026: the streaming extractor, the app-detection and clustering rules, the TF-IDF related-conversation index, the search scoring, the page itself, the gzip decoder embedded in it, the companion dashboard, and this document.
Checks were mechanical where that was possible. The gzip decoder was verified by SHA-256 against the original payload. Each rebuilt page is loaded in a headless browser that exercises search, the tab counts and the app rows. The clustering threshold was chosen by scoring five values against the previous labelled release. No person has reviewed the 80 topic labels written for the October build, and the quality markers reported on the dashboard were measured separately, on a different date, with patterns recorded alongside them.