Skip to content

feat(rendering): block-level un-collapse — retire SemanticStreamingTurn (#491) - #492

Merged
Juliusolsson05 merged 2 commits into
mainfrom
feat/block-level-uncollapse
Jul 7, 2026
Merged

Juliusolsson05 merged 2 commits into
mainfrom
feat/block-level-uncollapse

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

Closes #491 — the final slice of the rendering rewrite.

What

The ledger already decided live-turn visibility at block grain, but the view bridge collapsed every turn into one item so SemanticStreamingTurn could draw it — and that component then re-derived the same suppression (buildSemanticRenderUnits). Two decision-makers for live turns, synced by hand. This removes the duplicate: the bridge now emits one item per ledger-approved block, drawn by a dumb row.

  • Bridge (ledgerFeedItems.ts): emits semantic-block / semantic-collapsed-activity (churn fold) / blockless semantic-text. Groups by turnId (not adjacency) so equal-timestamp interleaved turns stay contiguous; fetches each block's rich payload from turnsById.
  • groupSemanticActivity: pure presentation grouping extracted from buildSemanticRenderUnits — no suppression (the ledger owns it).
  • Feed: renders the three items via SemanticLiveBlockRow / SemanticCollapsedActivityRow / MarkerRow+StreamingProse; debug/work-hint signals re-derived from each item's owner.
  • Deleted: SemanticStreamingTurn + the now-dead deriveFeedCommittedProjection / committedClaudeMessageTurnIds.
  • Retained (non-render owners, documented in the manifest): buildSemanticRenderUnits (Optimistic submitted user prompts are buried behind semantic/work rows #239 predicate + debug), selectMergedEntries (entries source), ghost-semantic-owned (ghost-vs-live dedup — not a component artifact).

⚠️ Needs your visual verification before merge

Shadow parity only ever verified turn grain — this exposes the ledger's per-block decisions to the pixels for the first time. It's guarded by a new block-grain bridge test (text + churn-fold + non-churn block + payload identity) and the full corpus, but the residual risk is visual. Please eyeball a build across: Claude + Codex · streaming text · live tool_use (Edit/Write/TodoWrite/Bash/MCP) · tool_result · thinking · collapsed churn ("worked: N reads") · AskUserQuestion · no-blocks Codex text.

Gates

tsc node+web clean · rendering 164 · unit 430 · renderer 14 · integration 28 — all green. −345/+374, SemanticStreamingTurn (149 lines) deleted.

🤖 Generated with Claude Code

Juliusolsson05 and others added 2 commits July 7, 2026 23:01
…rn, ledger is sole live-turn decider (#491)

The last slice of the rendering rewrite. The ledger already decided live-turn
visibility at BLOCK grain, but the view bridge collapsed every turn back into
one item so SemanticStreamingTurn could render it — and that component then
RE-DERIVED the same suppression (buildSemanticRenderUnits: text dedup,
committed-ownership, collapsed-running). Two decision-makers for live turns,
kept in sync by hand. This kills the duplicate.

- View bridge (ledgerFeedItems.ts) no longer collapses: it emits ONE
  FeedRenderItem per ledger-approved semantic block — `semantic-block`,
  `semantic-collapsed-activity` (churn fold), or blockless `semantic-text`.
  Groups by turnId (not adjacency) so equal-timestamp interleaved turns still
  emit contiguously. Fetches each block's rich payload from turnsById (the
  candidate carries only turnId+blockIndex).
- `groupSemanticActivity` (renderUnits.ts): pure presentation grouping extracted
  from buildSemanticRenderUnits — NO suppression (the ledger owns that now).
- Feed renders the three block items via the dumb SemanticLiveBlockRow /
  SemanticCollapsedActivityRow / MarkerRow+StreamingProse. history/current debug
  + work-hint signals re-derived from the items' `owner` tag.
- DELETED: SemanticStreamingTurn (StreamingTurn.tsx), and the now-dead
  deriveFeedCommittedProjection / committedClaudeMessageTurnIds / committedMessageIds
  / visibleDecisionForEntry it only fed.
- RETAINED with non-render owners (documented in the manifest): buildSemanticRenderUnits
  (the #239 semanticTurnHasRenderableContent predicate + debug bundles),
  selectMergedEntries (Feed's ghost-folded entries source), ghost-semantic-owned
  (a ghost-vs-live-turn dedup, not a component artifact).

RISK: shadow parity only ever verified TURN grain — this exposes the ledger's
per-block decisions to the pixels for the first time. Guarded by a new
block-grain bridge test (text + churn-fold + non-churn block + payload identity)
and the corpus; the residual is VISUAL and needs an eyeball on a build before merge.

Gates: tsc node+web clean; rendering 164, unit 430, renderer 14, integration 28.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion parity + bridge accounting

A 2-Claude + 2-Codex orchestration review of the un-collapse found 4 real issues
(and validated the grouping/interleave/block-fetch structure as correct). All
fixed + locked with block-grain tests:

- semantic.ts finding 1 (Claude, CONFIRMED): a write_stdin tool block always has
  text:'' — the chars ride parsedInput/argumentsJson/inputJson. The collector
  checked b.text and so suppressed EVERY write_stdin, hiding real Codex stdin
  from the live feed. Now reads the chars via writeStdinChars (mirrors the
  retired renderUnits helper). Latent ledger bug the un-collapse exposed.
- semantic.ts finding 2 (Claude): text ownership keyed on finalized===true only,
  but a Codex message can be status:'completed' without finalized (the
  isTerminalProxyBlock equivalence). A completed-but-unfinalized reply escaped
  text dedup and could double-render under the resp_* vs rollout id split. Now
  keys on `finalized || status==='completed'`.
- ledgerFeedItems.ts finding (Codex): a RUNNING collapsed_activity was emitted as
  an item, but CollapsedActivityRow null-paints while running — a null-painting
  row the ledger counted as content (false-ownership). The bridge now skips it
  (mirrors semanticRenderUnitPaintsDom).
- ledgerFeedItems.ts finding (both Codex): a missing multi-block turn only pushed
  its first candidate to dropped[] (turnId marked emitted before the lookup), so
  the rest were silently skipped — the #239 accounting gap. Now pushes all the
  turn's candidate ids.

Codex #1's "grouping reorders ledger rows" was REFUTED by Claude: order.ts breaks
equal-timestamp ties on SOURCE_RANK before per-turn sequence, so a committed row
can never fall between a turn's blocks — group-by-turn is safe.

New tests: write_stdin chars (parsedInput + argumentsJson), status:completed text
key, running-churn emits no collapsed item. Gates: tsc clean; rendering 166,
unit 432, renderer 14, integration 28.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Orchestration review (2 Claude + 2 Codex) — findings addressed

Dispatched a 4-agent review fleet via the built-in orchestration MCP. It validated the structure (grouping is a faithful copy of the old grouping half; group-by-turn interleave is safe per order.ts; block fetch/keys correct) and found 4 real issues — all now fixed and locked with block-grain tests:

# Reviewer Finding Fix
1 Claude (CONFIRMED) semantic.ts suppressed every write_stdin (checked b.text, always '' for a tool block — chars live in parsedInput/argumentsJson) → real Codex stdin hidden from the live feed read chars via writeStdinChars (mirrors retired helper)
2 Claude (PLAUSIBLE) text ownership keyed on finalized===true only; a Codex status:'completed' && !finalized reply escaped dedup → duplicate under the resp_* vs rollout id split key on finalized || status==='completed'
3 Codex a running collapsed_activity was emitted but CollapsedActivityRow null-paints while running → null-painting row the ledger counts as content bridge skips running collapsed (mirrors semanticRenderUnitPaintsDom)
4 Codex (×2) a missing multi-block turn only recorded its first candidate in dropped[] → #239 accounting gap push all the turn's candidate ids

Refuted: Codex #1's "grouping reorders ledger rows" — Claude proved order.ts breaks equal-timestamp ties on SOURCE_RANK before per-turn sequence, so a committed row can never fall between a turn's blocks. Group-by-turn is safe.

Findings 1 & 2 were latent ledger bugs that only mattered once this PR made the ledger's per-block decisions authoritative for the pixels — exactly the block-grain risk the PR flagged, now pinned and fixed. New tests cover all three suppression paths + the accounting.

Gates: tsc node+web clean · rendering 166 · unit 432 · renderer 14 · integration 28.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Block-level un-collapse: retire SemanticStreamingTurn, make the ledger the sole decision-maker for live turns (final rendering-rewrite slice)

1 participant