Skip to content

feat(rendering): evidence-first provider-owned renderer rewrite - #555

Merged
Juliusolsson05 merged 39 commits into
mainfrom
feat/render-shapes-phase1-fingerprint-catalog
Jul 18, 2026
Merged

Juliusolsson05 merged 39 commits into
mainfrom
feat/render-shapes-phase1-fingerprint-catalog

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Jul 16, 2026 •

Copy link
Copy Markdown
Owner

Overview

This PR completes the evidence-first, provider-owned feed rendering rewrite. It replaces shared provider-guessing branches with an explicit pipeline in which the active provider recognizes its own wire evidence, normalizes it into a narrow model, and returns an explicit render, fallback, or absorption decision.

provider evidence
  → provider-owned recognition and normalization
  → narrow shared visual protocol where the grammar is genuinely common
  → stable feed or condition surface
  → exact rendering/absorption receipt

The implementation is complete across all ten phases. The PR remains unmerged until the final developer-overlay recording check described below is performed.

What changes

Evidence memory and executable coverage

  • Structural fp2-* fingerprints, typed provider catalogs, and renderer receipts make every observed shape and ownership decision inspectable.
  • The development Unknown Shape Inbox and audit-rendering-shapes.mts turn provider drift into a bounded review queue.
  • Catalog coverage recursively validates primary, lifecycle, and alternate routes. Shipping catalogs cannot retain planned, unknown-outcome, unsupported-lifecycle, or misrouted entries.
  • The frozen 48-bundle corpus currently contains 3,686 structurally known observations with no audit failure.
  • Schema-v1 receipts from the unreleased experiment are deliberately ignored rather than supported through PR-local compatibility code.

Provider-owned operation rendering

  • Claude, Codex, and OpenCode own their adapters, operation dispatch, semantic dispatch, durable-entry recognition, components, and shape catalogs under src/providers/<provider>/renderer/.
  • Tool use and correlated result evidence reach one renderOperation call. Providers explicitly render, fall back, or absorb each half; absorbed results name their visible owner.
  • Questions, plans, tasks, schedules, collaboration, Agent Code MCP operations, reads/search, web, edits/writes/patches, commands, workflows, media, LSP, and provider system records use evidence-backed provider grammars.
  • Unknown and arbitrary MCP shapes remain visible through bounded structured JSON/JSONL/path-line/media presentation plus lazy exact-source evidence. We do not pretend to specialize every open-world MCP schema.

Git and compaction convergence

  • Feed Git detection, parsing, and cards moved out of shared feed code into provider command adapters plus a narrow shared Git formatter.
  • Git claims only complete pure-Git command chains; mixed Git/non-Git commands decline to generic rendering.
  • Every claimed Git result preserves warnings and unparsed output through bounded lazy raw evidence.
  • Durable compact boundaries and summaries are admitted and painted through provider capabilities. Shared code owns only the compaction visual model.
  • Structured compaction state outranks terminal-screen fallback, stale screen state cannot regress a durable completion, and restart/replay renders from transcript evidence without depending on a live screen condition.

Legacy deletion and clean cutover

  • Removed the old Claude/Codex feed barrels, central ToolUseRow, central question/task/compaction exceptions, duplicate result decoders, old Git detector/parser/card location, and obsolete Codex wrappers.
  • Removed the standalone compaction screen channel, four duplicated pending-condition runtime mirrors, and the unreleased custom-rendering setting/compatibility shim.
  • Shared feed code no longer recognizes raw provider compact discriminators or imports a concrete provider renderer. Provider capabilities are the only interpretation boundary.
  • The generic fallback remains total, so deleting legacy specialists cannot make unsupported evidence disappear.

Verification

  • Latest origin/main is merged; the branch is zero commits behind.
  • TypeScript project build passes.
  • Desktop production build and remote-client production build pass.
  • Conditions-core synchronization passes.
  • Renderer, integration, catalog, replay, import-boundary, Git, compaction, task-notification, and provider-routing suites pass.
  • The integrated full run passed 1,180 tests with five skipped; the only miss was the known claude-code-headless JsonlTailer timing test, whose immediate focused rerun passed 2/2.
  • Frozen-corpus and local-recording shape audits pass. The recording audit reports 1,250 obsolete schema-v1 sightings and no schema-v2 failure.

Before merge

Restart Agent Code on the local PR overlay and capture one fresh schema-v2 developer recording while exercising the rewritten feed. Re-run the recordings audit and review any new inbox entries. This is the remaining operator validation step; no implementation phase is incomplete.

Documentation

Phase tracker

Phase Focus Status
1 Structural fingerprints and typed shape catalogs Complete
2 Shape observer and recording sidecars Complete
3 Unknown Shape Inbox and audit/extraction tooling Complete
4 Evidence inventory and seeded provider catalogs Complete
5 Provider-owned code-edit rendering Complete
6 Command grammar, trusted formatters, and component directories Complete
7 Reads/search/web and collaboration rendering Complete
8 Tasks/questions/workflows/MCP and rich media Complete
9 Long-tail coverage and evidence-backed legacy deletion Complete
10 Final Git and compaction ownership convergence Complete

…t (Phase 1)

Phase 1 of the evidence-first, provider-owned feed rendering plan (PR #554).
Pure contract layer — no IPC, UI, dispatch, or visual behavior changes.

- src/shared/types/renderShapes.ts: metadata-only sighting/outcome/
  disposition contracts shared by renderer, preload, and main (Phase 2
  consumers). Never carries payload values.
- rendering/evidence/shapeFingerprint.ts: content-independent structural
  identity (fp1-…) from sorted typed key paths + discriminator allowlist.
  Same structure/different content → same fingerprint; render-relevant
  discriminator change → different. Secret subtrees dropped via the one
  SENSITIVE_KEY regex; cycle/depth/width/unserializable inputs bounded by
  construction.
- rendering/evidence/defineRenderShape.ts + catalogCoverage.ts: typed
  per-provider catalog declaration (provider-prefixed ids enforced at
  compile time) and the pure known/misrouted/unsupported-lifecycle/
  unknown-structure/unknown-outcome classifier + catalog audit.
- rendering/model/unknowns.ts: registry re-keyed from content-sensitive
  payload hash to structural fingerprint — Bash ls / Bash git status are
  ONE finding now; payload hashes demoted to bounded samples (≤8) with a
  saturating distinct counter. SENSITIVE_KEY moved to a leaf module
  (sensitiveKey.ts) to break the unknowns↔fingerprint cycle; re-exported
  from unknowns.ts so redact.ts and audit-sensitive-core.mts keep their
  documented import path.
- providers/importBoundaries.test.ts: the plan's non-negotiable import
  rules 1–5 as one narrow scanning test with named remediation; BlockRow's
  two legacy edges grandfathered with eviction phases and a staleness
  check that forces allowlist cleanup on migration.
- TodoRow moved claude → providers/shared/renderer/rows: removes the one
  provider→provider renderer import (opencode dispatch reaching into
  Claude's rows), which would have been red on day one of the boundary
  test.

Verification: tsc -b (node+web) 0 errors; vitest 832 passed / 5 skipped
(134 files) including all existing rendering corpora; git diff --check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Juliusolsson05 Juliusolsson05 changed the title feat(rendering): structural fingerprint + typed shape-catalog contract (Phase 1) feat(rendering): evidence-first shape capture system — Phases 1–4 (fingerprint, observer, inbox, seeded catalogs) Jul 16, 2026
Juliusolsson05 and others added 7 commits July 16, 2026 19:55
…hase 2)

The capture half of the evidence loop. Dev-gated, off in production,
backpressure by construction (dedup by fingerprint+outcome-kind, local
counters for repeats, hard-capped coalesced queue, 2s flush timer) — a
thousand identical partial-JSON deltas produce one sighting and one IPC
batch, never a message per token.

- features/feed/evidence/{observer,outcome,RenderShapeCaptureContext}:
  module-state observer (rawCapture.ts precedent — no React state, no
  re-renders), pre-receipt outcome helpers, per-session {sessionId,
  provider} context binding with mount-time recording sync.
- Observation points: Block.tsx (committed tool-use/result, all 8 route
  decisions incl. absorbed-with-owner receipts for the git-widget/
  workflow/AUQ suppressions), EntryRow (transcript-entry kinds),
  SemanticLiveBlockRow (live plane, prefix vs input-complete lifecycle).
- SessionRecorder.renderShapes: __render_shape synthetic lines (event-kind,
  size-cap applies — sightings are bulk, notes bypass; documented WHY).
  Manager routes batches to the live recorder; missing recorder = counted
  no-op by contract.
- IPC render-shape:append (batch+byte capped at the trust boundary) and
  render-shape:read-sightings (bounded newest-first disk sweep for the
  Phase 3 inbox). Replay skips __render_shape as pipeline input.
- Capture rides the existing Toggle Session Recording command: disarm
  (final count flush) before stop; arm only after start succeeds.
- shapeFingerprint: top-level toolName joins the discriminator allowlist
  (semantic blocks carry the tool name there) — pre-pinning change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…atalogs (Phases 3-4)

Phase 3 — the inbox and its tooling:
- unknownShapeReport.ts: PURE derivation (sidecar sightings + fingerprint
  index -> grouped rows, worst-status-first). One implementation shared by
  the Dev Debug module and the audit script so the panel and CLI can never
  disagree about what "unknown" means. Derived state, no second database —
  recordings + catalogs ARE the store, restart-survival is free.
- RenderingShapes Dev Debug module: status/provider/event/fingerprint/
  catalog-id table over the render-shape:read-sightings sweep, observer
  stats line (armed/dropped/swallowed), copy-useful = inbox-only paste
  block for issues/agent sessions.
- scripts/audit-rendering-shapes.mts: coverage report over the bundle
  corpus (+ optional --recordings sweep); --seed prints ready-to-review
  catalog entries for unclassified fingerprints. Prints source, never
  writes it — classification stays a reviewed change.
- scripts/extract-rendering-shape.mts: fingerprint -> redacted draft
  fixture under testing/fixtures/rendering-shapes/<provider>/<fp>/, through
  the SAME redactRecording + findSensitiveSurvivors hard gate as recording
  extraction; refuses to write on any survivor.

Phase 4 — the seeded inventory (gate: zero unclassified fingerprints):
- bundleShapeSweep.ts: one sweep (mirroring the runtime observation points
  exactly) consumed by the coverage test, the audit script, and the seeding
  run — a drifted gate is worse than none.
- CLAUDE_RENDER_SHAPES (43 entries / 113 fingerprints), CODEX_RENDER_SHAPES
  (38 / 80) seeded from the 48-bundle corpus; structural variants share ids
  per the plan's multi-fingerprint rule. OPENCODE deliberately empty — its
  3 bundles are empty-shell fixtures, and inventing entries from an
  unobserved tool list is exactly what the plan forbids.
- All dispositions start `planned`; outcomeSatisfiesDisposition widened so
  planned/unsupported tolerate every legacy outcome (the painter routes by
  CONTENT — git Bash vs plain Bash — which a structural catalog must not
  flag as misrouted). Strictness bites per family when Phase 5+ receipts
  graduate entries to specialized/absorbed.
- shapes.coverage.test.ts: the Phase 4 exit gate as permanent CI — sweeps
  all 48 bundles (3,673 observations), asserts zero unknown-structure,
  catalog audit clean, no duplicate fingerprint claims.
- tsconfig node/web: providers-root shape files follow the established
  capabilities-registry include/exclude split.

Verification: tsc -b node+web 0 errors; vitest 846 passed / 5 skipped
(137 files); audit script exit 0 (193/193 shapes known-claimed).

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

User-caught skew: the Phase 4 seeds came from JSON-on-disk bundles, but
runtime payloads arrive via structured clone. JSON.stringify DROPS
undefined-valued keys and turns undefined array elements into null, so a
live payload carrying `finalized: undefined` would fingerprint differently
from the identical shape in its own serialized evidence — filing every such
sighting as a false unknown. Normalize at collection time: skip
undefined-valued keys, treat undefined elements as null. Bundle-derived
fingerprints are unaffected (JSON sources never contain undefined) — the
coverage gate stays at 193/193 — so no re-seed is needed; only the runtime
side moves onto the same identity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Orchestrated review (2 Claude + 2 Codex lens reviewers, 1 Claude + 1 Codex
whole-PR reviewers) surfaced 2 critical / ~15 major findings. All identity-
and privacy-affecting ones land here, while fingerprints are still cheap to
re-pin (same PR):

fingerprint (fp1 semantics changed — catalogs re-seeded in this commit):
- order-free discriminators: values collect into sorted sets per path
  ([text,tool_use] == [tool_use,text]) — the panel proved last-wins split
  the most common Claude message shape by element order
- dynamic-key normalization: non-identifier object keys collapse to <dyn>
  (codex patch results keyed by ABSOLUTE PATH were minting one fingerprint
  per file and leaking paths into shapePaths/inbox/seed output)
- nested type/kind/subtype values gated by a strict lowercase-enum regex
  (≤24 chars) so token-shaped secrets can't ride into discriminators
- MAX_VISITED_NODES traversal cap (walk itself was unbounded: ~700ms on a
  500k-key payload), depth 7 (the documented example clipped at 6),
  separators via fromCharCode (never literal control bytes again)

observer:
- literal NUL bytes removed (git treated the file as BINARY — unreviewable)
- route identity (rendererId/owner/surface) joins the dedup key: git-widget
  vs dispatch routes of one structure are separate evidence now
- honest flushedCount (a queue-shed first sight was unrecoverable), async
  disarm awaited by the stop command, final flush bypasses the live queue
  cap in bounded chunks, final copies get fresh observedAt
- recorder-miss auto-disarm (3 consecutive "no recorder" responses) closes
  the session-exit leak of up to 4096 keys per finished session

painter honesty:
- provider result null vs undefined: intentional suppression records as
  absorbed-with-owner, not "specialized" (opencode todowrite case)
- semantic plane: empty thinking → absorbed; codex apply_patch/exec_command/
  write_stdin → specialized; coarse generic only for the true long tail

parity + tooling:
- bundle sweep now fingerprints compact-summary and task-notification
  conversation entries (EntryRow sights them; the corpus gate was blind)
- unknownShapeReport: writer-key MAX aggregation (summing first-sight +
  final-flush lines double-counted), observedAt/shapePaths validation,
  sourceRecordingId traceability; inbox gets a refresh button
- sidecar sweep: 256 MiB total byte budget + 60-recording cap (the old
  200×128MiB worst case re-opened the whole-file OOM pattern)
- audit script keeps FULL outcomes (kind-only reconstruction would flag
  every graduated shape as misrouted), counts by writer-key max, exit code
  includes unknown-outcome, --seed groups variants under one id
- extract script defaults to structure-only (full-text-capped is a warned
  opt-in; the hard gate can't see free-text secrets), validates --mode;
  drafts are gitignored under testing/fixtures/rendering-shapes/
- boundary test also catches side-effect imports and require()
- toggle command: per-session lock, arms only when start succeeds, awaits
  the final flush before stopping the recorder; mount-sync is ARM-only
  (stale disarm race could silently kill a fresh capture)
- intentional-hidden added to the condition-surface DISPOSITION union

Re-seed: claude 44 entries / 115 fingerprints, codex 40 / 68 (dyn-key
merges collapsed path-variants; codex.kind now splits patch/exec/search
result semantics into their own ids). Coverage gate: 183/183 classified
across 3,686 observations, audit exit 0.

Deliberate deferrals (documented, per panel findings B1/B2/#4/#18):
condition-plane live observation (raw session:conditions channel is already
recorded; offline extraction can derive shapes), prefix fixtures for
edit/command shapes (arrive via capture soaks — the corpus held no
unfinalized blocks), blockless semantic-text turn observation (Phase 5+),
and sourceRecordingCursor precision (extraction windows are drafts by
contract).

Verification: tsc -b node+web 0 errors; vitest 851 passed / 5 skipped
(one unrelated pre-existing flake in packages/claude-code-headless
JsonlTailer watch-handoff — passes on isolated rerun and in the main
checkout; this PR changes nothing under packages/).

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

First live soak produced recordings with ZERO sidecar lines: under
AGENT_CODE_SESSION_RECORD the recorder auto-starts in main on the session's
FIRST event, which lands after Feed's single isSessionRecording query
resolved false, and the arm-only sync never re-checked. Bounded retry
schedule (0s/2s/5s/15s) closes the boot race; arm is idempotent, later
checks are no-ops, and production builds answer false instantly so the
retries cost nothing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he auto-record race

Second live test: recordings grew normally but sidecars stayed empty even
with the retry schedule, because a RESTORED idle pane's recorder starts on
its first event — whenever the user first prompts it, unboundedly after
Feed mount. No poll schedule can win that. Main now pushes
record-session:started (injected sendToMainWindow closure — the manager
stays under the outbound funnel, channel not in RECORDED_CHANNELS so the
observe() tap ignores it), preload exposes the subscription
(lsp:diagnostics pattern), and the capture context arms on the event. The
mount retries stay as the reload-recovery belt: a renderer reloading MID-
recording gets no fresh start event.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First real capture soak (two shape-hunter agents + parent session) found 53
new structural fingerprints. Regenerated both catalogs from ALL evidence —
48 bundles + recording sidecars — with the same grouping rules:

- claude: 44 -> 60 entries / 156 fingerprints (16 live-only ids): first-ever
  websearch/webfetch (committed+semantic), notebookedit, lsp, monitor,
  taskget/taskoutput/taskstop, generic MCP long tail (ref-docs, todoist,
  calendar, gmail), plus streaming-prefix variants folded into existing
  bash/read/edit/write ids.
- codex: 40 -> 48 entries / 80 fingerprints (8 live-only): the MODERN exec
  wrapper (tool-use + semantic) — the corpus's known biggest gap — plus a
  third orchestration naming generation (wait, send-message, list-agents,
  interrupt-agent, followup-task) and a patch-apply-end failure variant.

All planned dispositions; live-only ids carry no bundle fixture refs (their
evidence lives in local recordings; extract-rendering-shape.mts mints
fixtures on demand). Audit: 236/236 shapes known-claimed across 3,817
observations, exit 0; bundle coverage gate green; tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Juliusolsson05 Juliusolsson05 changed the title feat(rendering): evidence-first shape capture system — Phases 1–4 (fingerprint, observer, inbox, seeded catalogs) feat(rendering): evidence-first renderer rewrite — shape memory + modular provider rendering Jul 16, 2026
Juliusolsson05 and others added 8 commits July 16, 2026 22:22
… 5 core)

The first shared visual protocol and the pattern every family copies:

- protocols/code-edit/{model,CodeEditView}: CodeEditRenderModel (files/
  verbs/DiffLine[]/±counts/status/errorSummary/partial) + the shared leaf
  view composing existing primitives (MarkerRow/DiffSlab/displayPath).
  No provider field, no raw escape hatch, no provider branches.
- claude/renderer/adapters/codeEdit.ts: Edit/MultiEdit/Write → model, plus
  fromClaudePartialEditJson — STREAMING-FIRST per the product owner's trap
  warning: a model is paintable the moment file_path CLOSES in the raw
  partial JSON (escape-aware field extraction), diff lines grow in place as
  old/new_string stream; never gated on the full payload landing.
- codex/renderer/adapters/codeEdit.ts: apply_patch → model reusing the
  exported parseApplyPatch (prefix-tolerant by construction — model exists
  from '*** Begin Patch' + first file header) + patch_apply_end failure
  status; declines (null) pre-sentinel so fallbacks stay visible.
- First cutover (pattern proof): WriteRow → adapter + CodeEditView. Both
  committed dispatch and BlockRow's live path converge in that component,
  so streaming upgraded with zero feed-layer changes and zero new
  boundary edges. Edit/MultiEdit/CodexApplyPatchRow follow mechanically.
- 9 adapter tests pin the streaming-first contract (no paint before path
  closes; monotonic growth; torn escapes tolerated; multi-file verbs).

Verification: tsc -b node+web 0 errors; vitest 861 passed / 5 skipped
(138 files). Note: renderer tests require NODE_ENV unset (a leaked
production value loads production React and breaks act()) — pre-existing
environment footgun, not introduced here.

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

Product-owner verdict from tonight's live Phase 5 demo: LSP-backed
highlighting kicked in painstakingly slow — feed code blocks using the
monaco engine sat unstyled while the dynamic import + editor create + LSP
round-trip completed. CodeBlock now renders the synchronous hljs static
layer in the SAME FRAME (the memo was already computed for every engine),
then swaps the Monaco editor in place once it exists; Monaco's built-in
tokenization paints from creation, so the handoff is colored→colored and
LSP semantic tokens remain a later invisible upgrade. Placeholder re-arms
whenever the editor rebuilds (page/language/remount).

Layered tokens per the plan: plain text → lexical same-frame → semantic
later. First paint never waits on a language server.

Verification: tsc -b node+web 0 errors; vitest full suite green
(NODE_ENV unset — see prior commit's footgun note).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h cutovers, catalog graduation

- StreamingCodeText: sealed-line hljs highlighter (per-line cache, tail-only
  re-tokenize, O(new bytes)/delta) replaces the plain highlight={false} live
  Write preview — streaming writes are COLORED while streaming (product
  verdict). Committed pass stays authoritative for cross-line constructs.
- EditRow cut over: adapter + CodeEditView; the oversize gate moved into the
  adapter with a BOUNDED -/+ fallback (200 lines/side + explicit marker) —
  same protection the old OversizedEditSlab gave, one home.
- CodexApplyPatchRow cut over: provider component now WRAPS the shared view
  (plan chrome rule), keeping the pre-sentinel CodexToolRow fallback and the
  partial-raw-patch paged disclosure. apply_patch parser MOVED into the
  adapter (rows -> adapter -> shared stays acyclic).
- MultiEditRow deliberately KEPT legacy: its 20-per-page paging is superior
  to any cap and the shared view has no paging yet — joins the protocol when
  paging lands (documented preservation call, not an omission).
- Catalog graduation: claude.tool-use.{edit,write}.v1 and
  codex.tool-use.apply-patch.v1 -> specialized(rendererId) — the classifier
  now polices these routes; regressions file as known-misrouted. (No
  committed multiedit shape exists in the corpus; it seeds specialized on
  first sighting.)

Verification: tsc -b node+web 0 errors; vitest full suite green; coverage
gate + streaming-first adapter tests pass.

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

Product verdict: the green must be there WHILE streaming, not only on the
committed card. StreamingCodeText gains tone='added' — each line renders in
DiffSlab's exact per-line grammar (bg-diff-add-bg, + gutter, hljs body), so
the streaming preview and the committed Write diff are the same picture and
the handoff is seamless. Sealed-line caching unchanged (O(new bytes)).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Caught live during the Phase 5 showcase (user screenshot): modern Codex
never called apply_patch as a tool — it called exec with a JS wrapper,
  const patch = "*** Begin Patch\n..."; await tools.apply_patch(patch)
so the sentinel was present but the newlines were ESCAPES inside a string
literal; the line-based parser saw one giant line and the card fell to the
generic exec row showing raw wrapper JavaScript — the exact case the plan's
Phase 5 exit gate forbids. This is the corpus's documented #1 gap
(codex.tool-use.exec.v1, live-only catalog entry) rendering unhandled.

- applyPatchText now detects the escaped-sentinel case and decodes the
  containing string literal (decodeEmbeddedPatchLiteral): scan back to the
  opening quote, decode escapes forward, PREFIX-TOLERANT — an unterminated
  literal mid-stream decodes to the patch streamed so far, so wrapped
  patches stream as growing diffs too (streaming-first holds).
- Committed dispatch routes exec-with-sentinel through CodexApplyPatchRow;
  BlockRow's live function_call branch routes exec the same way. Plain
  command scripts decline inside the row and fall back as before —
  strictly additive routing.
- 3 new tests: full wrapper decode, streaming prefix, plain-command decline.

Verification: tsc -b node+web 0 errors; vitest full suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Command protocol (Phase 6)

- protocols/command/: CommandRenderModel + CommandView (hljs-bash headline,
  workspace-relative cwd, OutputWell body, conclusion line, streaming state).
- Formatter REGISTRY, dir-per-family: formatters/{tests,json,file-mutation}/
  + index.ts priority registration. Contract: conclude() is pure,
  conservative-decline, terminal-output-only, ENRICH-never-replace.
- file-mutation uses a real quote/escape-aware shell tokenizer with a
  parse-fully-or-decline contract (product-owner rule: no weak regex
  matching) — $(…), backticks, fd redirects, subshells, unterminated
  quotes abort the whole conclusion. Families captured live from our own
  dev session: heredoc writes/appends, inline python3 writers, sed -i.
- Claude: adapters/command.ts (fromClaudeBashBlock streaming-first,
  fromClaudePartialBashJson paints the moment `command` closes,
  claudeBashResultText/Conclusion); dispatch routes Bash use+result;
  silent success renders an explicit empty OutputWell (#524 lesson).
- Codex: adapters/command.ts (exec_command mapping with yield/max meta,
  fromCodexExecScript extracts EVERY embedded tools.exec_command incl.
  Promise.all fan-outs, stripCodexTransportEnvelope); exec plain-command
  case renders a real command card; results upgrade to OutputWell.
- Ported AND OWNED (tests are this branch's contract, #524 is reference
  only): shared/parsers/ansi.ts (+stripAnsi), AnsiText, OutputWell
  (head+tail preview so final error/summary lines always survive).
- Catalog: graduated codex exec-command + write-stdin to specialized.

## Dir-per-component restructure (product-owner rule, 2026-07-16)

Every distinguished provider component owns a directory, even single-file:
- claude/renderer/components/{edit,multi-edit,write,bash}/
- codex/renderer/components/{apply-patch,exec-command,write-stdin,tool,
  tool-result}/
- opencode/renderer/components/read-result/, shared components/todo/
- rows/ClaudeRows.tsx + rows/CodexRows.tsx are now ZERO-LOGIC barrels kept
  solely for BlockRow's grandfathered import edges (exact-specifier match
  in importBoundaries.test.ts) — deleted with the renderOperation
  migration. Provider-internal code imports component dirs directly.
- Live Bash moved out of BlockRow into components/bash/ (the boundary test
  correctly rejected feed->adapter imports; zero new grandfather entries).
- Dead code dropped in the split: ClaudeRows.editInput, CodexRows
  PatchFileHeader (both orphaned by the Phase 5 CodeEditView cutovers).

Docs: plan .md amendment (dir-per-component) pushed to PR #554 branch.

Verification: tsc -b node+web 0 errors; vitest 877 passed / 1 failed —
packages/claude-code-headless JsonlTailer fs.watch flake, fails ~2/3 in
isolation on untouched pinned submodule code, unrelated to this branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nd headlines

Product-owner verdict watching the Phase 6 showcase (2026-07-17): a
`cat > path <<'EOF' … EOF` rendered as a squashed one-line command
headline plus a 'writes …(heredoc)' conclusion — but the CONTENT being
written, the whole point of the operation, was invisible. A formatter can
only enrich a command card; it cannot change the card TYPE. So detect the
heredoc-write shape and route it into the code-edit protocol instead.

Why quoted-delimiter heredocs specifically: <<'EOF' performs NO shell
expansion, so the body bytes in the command text ARE the written bytes,
exactly. That is the one case where file content is honestly recoverable
from the command alone. Unquoted <<EOF would 569Xlexpand → visible text ≠
written bytes → decline to the command card.

- shellLex.ts: the quote/escape-aware tokenizer LIFTED out of the
  file-mutation formatter (now two consumers) into a shared primitive.
  file-mutation imports it — one lexer, one contract, no divergence.
- shellFileWrite.ts: extractShellHeredocWrite (strict parse-fully-or-
  decline: only `cat`, no file operands, quoted delimiter, exactly one
  op-bearing segment, nothing past the terminator) + shellHeredocWriteModel
  (→ code-edit model, pure additions, Writing/Appending verb).
- CodeEditVerb gains 'Appending' (>> heredoc; the view just prints it).
- Claude Bash adapter: fromClaudeBashCodeEdit + streaming-first
  fromClaudePartialBashCodeEdit (trustworthy once the command's FIRST LINE
  closes — content lines then grow in place like a streaming Write).
- Dispatch + live ClaudeLiveBashRow try the write route FIRST, fall back to
  the command card when it declines (every non-heredoc command unaffected).
- 10 new tests: exact content, mkdir&&cat idiom, >> append, and the four
  decline contracts (unquoted delim, non-cat writer, cat file operand,
  trailing command) + streaming prefix + completeness boundary.

Codex exec_command heredocs are a clean follow-up (its cmd arrives as a
bash -lc argv array needing different reconstruction); the extractor is
already provider-neutral shared code.

Verification: tsc -b node+web 0 errors; command suite 24 passed; import
boundaries 6 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Juliusolsson05 and others added 5 commits July 17, 2026 00:24
Product-owner rule from Phase 6 implementation: every distinguished
provider component owns a directory (components/<component>/index.tsx),
even single-file ones — the directory is the unit of ownership and
'ls components/' doubles as the specialized-rendering coverage list.
Same rule inside shared protocol families (command formatters as
formatters/<family>/). Records why rows/ClaudeRows.tsx and
rows/CodexRows.tsx survive only as zero-logic barrels for the
grandfathered BlockRow import edges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rendering

docs(rendering): add evidence-first renderer plans to #555
@Juliusolsson05 Juliusolsson05 changed the title feat(rendering): evidence-first renderer rewrite — shape memory + modular provider rendering feat(rendering): evidence-first renderer — shape memory, code-edit, and command grammars Jul 16, 2026
@Juliusolsson05 Juliusolsson05 changed the title feat(rendering): evidence-first renderer — shape memory, code-edit, and command grammars feat(rendering): evidence-first provider-owned renderer rewrite Jul 17, 2026
@Juliusolsson05
Juliusolsson05 force-pushed the feat/render-shapes-phase1-fingerprint-catalog branch from bdbc105 to 57ad22b Compare July 18, 2026 11:59
fix(rendering): resolve exhaustive PR 555 review findings
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.

1 participant