fix(web): normalize Claude response viewer turns - #169
Conversation
Only CLAUDE.md conflicted: master restructured it into the short-rule + docs/architecture-invariants.md pointer layout while this PR was open. The response-viewer detail now lives in architecture-invariants, so the Claude turn-grouping and restored-placeholder rebind notes moved there. Changeset rewritten to record the measured effect on real transcripts.
|
Thanks, this one is a genuinely good diagnosis. I rebuilt it on current master, verified it against real transcripts rather than just the fixtures, and pushed the conflict resolution to your branch. The diagnosis is rightThe old code treated a Claude transcript as if it were a chat log, one row per message. It isn't: it's an append-only event log where a single logical exchange spans many rows. Tool results are recorded as
Verified on real dataFixtures prove the shape; they don't prove the fix helps on real transcripts or that grouping doesn't silently eat content. So I extracted both parsers verbatim, the master one and yours, and ran them over the 40 largest real transcripts on this machine:
The zero-character result is the important one: the grouping merges rows without dropping any assistant output. And the non-full path being byte-identical on every file means the plain last-response behaviour is a pure no-op, so the blast radius really is confined to the viewer. I checked the 8 residual duplicates individually. All are legitimate: repeated "You've hit your session limit" notices and a repeated Worst individual case went from 282 cards to 18, and another from 184 (32 of them duplicates) to 68. One thing worth being aware of
I left it in, for three reasons: it only triggers on the Housekeeping
Verification
Thanks again. Reading the transcript format correctly instead of patching the symptom is the right instinct, and the sidechain and restored-placeholder catches were the parts that needed someone actually paying attention. |
The Claude reader concatenated every assistant row between two human prompts
into one card, fusing up to 74 distinct model messages into a single card, and
it never read the attachment rows that hold a prompt typed while the agent was
working. Measured over 57 real transcripts on 2026-09-01, the viewer shows
1,806 messages instead of 356 and 353 user cards instead of 178, with the
assistant text sequence unchanged row for row and the response without
?context=full byte-identical on all 57 files.
One assistant row IS one whole model message: in that corpus no assistant row
carries more than one content block and no message id carries more than one
text block, so there was nothing to reassemble. Each row becomes its own
message carrying an additive {kind, label, turn}, and the frontend renders a
same-role run inside one turn as badge-less continuation segments — which is
what keeps a p90 of 11 messages per turn from reading as card spam. A numeric
turn gates that rendering, so Codex, the external-CLI pane parser and an older
server keep one badge per card.
A prompt typed while Claude is working is recorded ONLY as an
attachment/queued_command row. Taking it when origin.kind is 'human' and
commandMode is 'prompt' recovers 162 user cards from 163 such rows — one is a
verbatim repeat inside an unanswered user run and is collapsed by the existing
dedup guard — and restores the turn boundary whose absence let the assistant
runs fuse. The CLI's own queue entries are cleanly separable: of 322
queued_command rows, 159 are commandMode 'task-notification' and not one of
them carries an origin key.
This narrows #169 rather than reverting it: sidechain exclusion, the
restored-<uuid8> rebind, replayed-snapshot dedup and synthetic-row filtering
are all unchanged and still asserted.
Summary:\n- Rebind recovered sessions to an unambiguous Claude transcript UUID when persisted mux metadata is stale.\n- Build viewer cards at real human turn boundaries instead of rendering every JSONL content row as a separate message.\n- Deduplicate replayed assistant snapshots while preserving tool, task, skill, compact-summary, and team metadata filtering.\n- Add focused regression coverage for recovered sessions, turn grouping, and repeated streamed fragments.\n\nVerification:\n- test/routes/session-routes-claude-last-response.test.ts: 2 passed\n- typecheck, lint, frontend syntax, public asset checks, format check, lockfile check, and production build passed.\n\nThis branch is based on the current upstream master and contains only the Claude viewer normalization fix plus its changeset.