Repository navigation
feat(rendering): evidence-first provider-owned renderer rewrite - #555
Merged
Juliusolsson05 merged 39 commits intoJul 18, 2026
Merged
Conversation
…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>
…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>
… 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>
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
force-pushed
the
feat/render-shapes-phase1-fingerprint-catalog
branch
from
July 18, 2026 11:59
bdbc105 to
57ad22b
Compare
fix(rendering): resolve exhaustive PR 555 review findings
This was referenced Sep 10, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
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
fp2-*fingerprints, typed provider catalogs, and renderer receipts make every observed shape and ownership decision inspectable.audit-rendering-shapes.mtsturn provider drift into a bounded review queue.planned, unknown-outcome, unsupported-lifecycle, or misrouted entries.Provider-owned operation rendering
src/providers/<provider>/renderer/.renderOperationcall. Providers explicitly render, fall back, or absorb each half; absorbed results name their visible owner.Git and compaction convergence
Legacy deletion and clean cutover
ToolUseRow, central question/task/compaction exceptions, duplicate result decoders, old Git detector/parser/card location, and obsolete Codex wrappers.Verification
origin/mainis merged; the branch is zero commits behind.claude-code-headlessJsonlTailertiming test, whose immediate focused rerun passed 2/2.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