Session Flow was reading the laundered text: the feature that never rendered a single card
The Session Flow view showed '0 steps' for every session because it re-parsed <dk-summary> blocks out of transcript text that the Claude reader had already stripped them from — the parsed payload was sitting in a `summary` field one hop away. The feature had only ever been verified against empty states.
The founder opened 🧭 Session Flow — the read-back lens built precisely so he could stop losing
the thread — and every session said "No summaries yet — 0 steps," including panes whose
transcripts held dozens of <dk-summary> blocks on disk.
Chasing the wrong ghosts first
Three plausible suspects fell one by one under actual evidence: pane ids DO persist across app
restarts (localStorage layouts), the hook events file HAD 1,589 events for the affected pane, and
boot-time recover_sessions re-seeding works as designed. The real bug was quieter:
read_transcript(the Claude transcript reader) strips<dk-summary>from each merged assistant turn's text — and returns the parsed block as the message'ssummaryfield.SessionFlowtook that launderedtextand re-parsed it for<dk-summary>blocks.
Producer removes the marker and hands it to you on a plate; consumer searches the plate it was removed from. Result: a permanently empty view — the feature had never rendered a single card in production. The handoff doc had flagged exactly this risk ("verified rendering live" covered only the EMPTY states; populating cards was the open P0).
The fix
The parser moved into a pure, unit-tested helper (sessionFlowHelpers.ts) whose
summariesFromMessages() reads BOTH shapes — the summary field (Claude reader) and raw blocks
left in text (Codex reader) — with dedupe so a reader that one day does both never double-counts.
Nine test cases pin the two reader shapes, ordering, malformed-block skips, and the legacy flat
card shape. 393/393 green.
Lesson
When a producer strips-and-relocates data, the consumer must read from where it was relocated — a contract between two files with no shared test breaks silently. And rendering an empty state is not feature verification: drive the populated path once before calling it shipped.