Repository navigation
Feed RENDER-layer rewrite: artifact cards, sealed-line streaming highlight, ANSI output - #524
Juliusolsson05 wants to merge 29 commits into
Conversation
Approved design for the full feed painter rewrite: artifact view-model layer normalizing committed + live planes, one shared component kit, sealed-line streaming code highlighting, ANSI-aware command output, per-tool cards for every Claude/Codex artifact family. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Grounded in a four-way audit + first-hand reads of the painter, streaming path, and kit surfaces. Spec amended: hljs v11 has no continuation API, so StreamingCodeBlock is stateless-per-line with finalize repair. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Command output has never been ANSI-aware anywhere in the feed — colored test/build output rendered as literal escape garbage. parseAnsi handles SGR color/weight (16/256/24-bit), collapses \r progress rewrites, and strips cursor/OSC noise; AnsiText paints spans with the same luminance-branched palette the terminal panes use (readXtermTheme), so feed output and raw terminal output share one color dialect. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sealed lines highlight once and cache by index (append cost = new bytes, not total bytes); only the live tail line re-tokenizes per delta. blockKey deliberately excludes the language so a late fence info-string re-highlights once instead of remounting. Uses toHighlightLanguage to bridge the Monaco-vocabulary names (typescriptreact/shell) that hljs.getLanguage doesn't know. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r-delta Monaco The open-fence branch rebuilt a full Monaco editor+model+LSP document on every streaming token (CodeBlock's Monaco effect lists `code` in its deps) and remounted again when the fence language arrived a delta late (language was baked into the key). Live code now paints highlighted line-by-line with sealed-line caching and zero remounts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nly the tail re-parses StreamingProse memoizes on the full growing string, so during a stream the memo never hit and the whole unified pipeline re-ran per delta: O(len²) per message, and a second fence re-parsed the first through markdown each delta. SegmentedMarkdown seals everything through the last CLOSED fence (identity changes only when a fence closes), streams only the tail, and routes open fences through StreamingCodeBlock. Applied to both live block text (BlockRow) and blockless turn text (Feed semantic-text). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OutputWell is the single collapsible output region (successor to both TruncatedOutputRow copies — behavior preserved, ANSI + loud byte-cap added). StatusBadge is the one status vocabulary every artifact card flips through. ExpandSection generalizes ToolResultRow's private LazyDetails first-open Monaco gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pies The shared row and Codex's byte-similar private twin both dumped raw bytes into <pre> (escape codes rendered as literal garbage) with no byte cap once expanded. All committed tool output, live Codex function output, and live tool results now flow through the one kit OutputWell: same 3-line/expand behavior, ANSI colors, loud truncation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…m port) Feed.tsx drops from ~1060 to ~640 lines. The scroll family (mount restore, sticky-bottom listener + auto-scroll, older-history load, bootstrap pin-once, scroll-to-latest) moves to useScrollFeedBehaviors; both picker tweens move to usePickerAutoScroll (which returns a stable cancelTween — scroll-to-latest's one legitimate touch point on the tween ref); feed-debug emission moves to useFeedDebugEmission. Logic and WHY comments ported verbatim; zero behavior change intended. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…act) Types only — the normalization contract both planes resolve into and every card renders from. Live ≈ committed becomes structural: same VM, same card, status flip on completion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… one VM commandFromCommitted/commandFromLive and genericFromCommitted/ genericFromLive produce identical CommandArtifact/GenericToolArtifact shapes from ToolUseBlock+result pairs and SemanticLiveBlock+snapshot respectively. Provider wire knowledge moves to pure extractor modules (claude: order-insensitive slash-command envelope parser, verified against real transcripts where tag order VARIES between CC versions; codex: execCommandInput canonical home + result meta reader). Headline logic reuses the existing corpus-hardened smartHeadline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… + exit codes Bash / exec_command / local_shell_call / write_stdin route through routeFamily → CommandCard on BOTH planes: committed interception in Block.tsx (paired result consumed into the card, suppressed below — same predicate both branches per the #442 lesson) and live routing in BlockRow (output streams in via the tool_output_delta fold). Exit codes finally display (red 'exit N'); git-intent cards still win for recognized git commands; empty write_stdin keeps its render-nothing carve-out (renderUnits.isInvisibleWriteStdinBlock mirrors it). CodexExecCommandRow/CodexWriteStdinRow and the duplicated execCommandInput are deleted — extractors.ts is the canonical home. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he raw-JSON live dump MCP and unknown tools render through ONE card on both planes: JsonToolRow's corpus-hardened per-param presentation preserved, plus StatusBadge, a result slot (JSON slab / ANSI OutputWell) consumed into the card, and — the live upgrade — partial input streaming as growing highlighted JSON via StreamingCodeBlock instead of a raw <pre> dump. The interim Write preview also moves onto StreamingCodeBlock, so live file writes are now line-by-line highlighted ahead of the dedicated FileWriteCard. Suppression is gated by RESULT_CONSUMING_FAMILIES ⊂ landed cards, so un-carded families (apply_patch errors etc.) keep their legacy result rendering — no data loss mid-migration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Slash invocations render as a /name pill + message/args instead of raw <command-name> tag soup, and the separate local-command-stdout user entry renders as a muted ANSI OutputWell (real transcripts carry escape codes in it — verified). The envelope parser is order-insensitive because tag order varies between CC versions. Codex rollout stops dropping exit-0/no-output exec results so the CommandCard can paint the header-only '$ cmd ✓' record; the full corpus runs clean with no divergence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iEdit/apply_patch DiffView succeeds DiffSlab (line core + overflow sizer ported verbatim) adding per-file headers with action badges / ±counts / RTL-truncated paths and first-40/last-40 windowing for huge diffs. DiffCard renders Claude Edit+MultiEdit (diffLines) and Codex apply_patch (grammar parser, moved to extractors with the partial-JSON wrapper decoder) identically on both planes; live paths show the file the moment its JSON literal closes and the diff fills in as it streams. Patch successes finally get a visible ✓ (they rendered NOTHING); patch failures render tinted per-file unified_diffs inside the card. Deleted: EditRow/MultiEditRow, CodexApplyPatchRow, PatchFileHeader, CodexToolRow, the dead patch_apply_end result branch, and the BlockRow live-tool conversion layer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One Write surface for both planes: path + language pill + growing line counter + StatusBadge over a sealed-line StreamingCodeBlock body. The live preview is finally syntax-highlighted (the old one was forced plain because whole-string hljs per delta was quadratic). Finalized large files offer a lazy Monaco editor view. WriteRow deleted; Write results consumed by the card (errors render inside it in red). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ource One card owns both halves of a lookup (the use row was a bare generic card; the result was special-cased by tool name inside the shared ToolResultRow — audit gap #9). Codex parsed read/search execs keep their 'Read N lines from path' summary, now inside CommandCard. Also fixes a cutover regression: opencode's legacy-claimed read/ todowrite results bypass family suppression (isLegacyProviderClaimed), so OpencodeReadResult keeps parsing its tag soup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unifies TodoRow (committed) and SemanticTodoList (live) — near-identical JSX maintained twice. Glyphs preserved (☑ ◐ ☐); the label prop keeps OpenCode's lowercase todowrite header honest and its dispatch special- case dies. Also excises a wholesale duplicated dispatch block that a splice had left in BlockRow (the shadowed copy had a wrong provider tag and no ReadCard route) — BlockRow is now 336 lines, down from 635. ClaudeRows.tsx and TodoList.tsx deleted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…al tools Codex web_search / tool_search / image_generation had bespoke live chips and unrecognizable generic committed rows (the drift the rollout synthesis comment itself flagged). One card each now renders both planes; Claude WebSearch/WebFetch join the web family. The committed synthesized names (web_search/tool_search/image_generation/local_shell) enter the routing table alongside their live *_call kinds — committed local_shell rows get CommandCard too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…yloads + Task spawn synonym One thinking renderer for both planes (the two copies had drifted); redacted_thinking gets an explicit pill instead of silently vanishing; Codex reasoning shows summary + full tracks when distinct. Image blocks accept the spec's url source (http(s)-gated) and get a dependency-free click-to-zoom lightbox. Hook/system entries expose their payload behind a collapsed ExpandSection. Claude spawn matching accepts 'Task' alongside 'Agent' (verified: this CLI generation emits Agent — 184 transcript hits — but newer upstream vocabulary is Task). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…wns the paint renderToolUse leaves the capability contract and all three providers (every family now has a landed card; routeFamily is total, with a loud generic-card fallback instead of a silent non-render). ToolUseRow and the JsonToolRow fallback path in Block.tsx are deleted. renderToolResult stays: it still carries real behavior for unpaired results (Codex meta- keyed rows) and OpenCode's legacy-claimed read tag-soup parser — whose tool_use side now renders the generic card without consuming the result. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stage RENDER now describes the resolve/artifacts/kit architecture, the hooks extraction, and the streaming primitives; stale references to the deleted dual-ladder dispatch (renderToolUse, JsonToolRow fallback, EditRow reuse) are gone per the doc's own fix-stale-docs rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The duplicated-block cleanup kept the copy WITHOUT the Write branch, so a streaming Write fell to the generic card and painted the raw partial JSON buffer instead of the decoded file preview. The whole point of the live path is decoding the action WHILE the JSON streams — extractStreamingWriteInput pulls file_path + unescaped content out of the incomplete buffer; it just wasn't being called. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Cards again Modern Codex's `exec` tool streams a JS SCRIPT as its input (tools.exec_command / tools.apply_patch / tools.write_stdin) instead of the classic per-tool names. Routing by name sent every one to the generic card, so edits painted as raw scripts while Codex's own TUI showed 'Edited x (+7 -3)' — caught by the 2026-07-12 debug bundle. classifyUnifiedExecScript extracts the script's intent partial-safely (the embedded patch literal decodes while still streaming, so live diffs grow file by file); both planes route apply_patch scripts to DiffCard, exec_command/write_stdin to CommandCard, unknown shapes to the generic card. Verified against the bundle's real payloads. Also fixes the review-confirmed unreachable JSON-wrapper decode in partialApplyPatchInput and the CRLF collapse that blanked every \r\n-terminated output line (curl headers, Windows-origin tools). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- HIGH: spawn-tool results are no longer family-suppressed — TaskSubagentRow renders no result content, so suppression silently destroyed Claude subagent final reports; spawns fall through to provider dispatch (Codex still drops its join-payload noise there). - MED: splitSealedPrefix's marker walk advances by a full ``` token, fixing the 4-backtick-fence desync that rendered code as prose. - MED: StreamingCodeBlock probes its retained cache tail and rebuilds on divergence — no ghost lines when fences share a key in one batch. - MED: ANSI stripping covers private-mode CSI (?25l), colon-form SGR, and \x1b(B charset designators (all rendered as literal garbage); colon/private params are strip-only, never applied. - MED: parseAnsi caps DOM spans at 4000 — a crafted x\x1b[m repetition minted ~50k spans under the byte cap; past the ceiling the tail is one unstyled span (content survives, styling degrades). - LOW: GenericToolCard no longer branches on vm.plane for highlighting (card contract); unified-diff header matching requires the space so ---/+++ content lines survive; apply_patch keeps blank body lines. - PERF: OutputWell memoizes the over-cap slice/line-count. Deliberately NOT taken: the O(total)-per-delta incremental-state rewrites (streamingWriteInput scan cache, StreamingCodeBlock split, sealed-prefix scans — real but µs-scale at current sizes and high regression risk), the Block-suppression architectural note (by design, documented in registry.ts), the inline-VM memo regression (bounded to toolIndexVersion bumps), and the \r style-carry nicety. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…deep read
Vendored codex-src updated to latest main (~15k PRs ahead) and read
hard: TUI history cells, exec/diff rendering, markdown streaming, and
the protocol/rollout persistence policy. Ground truth from a real
2026-07-12 rollout confirmed the decisive facts: default Legacy
rollouts NEVER persist exec_command_begin/end (script classification is
the right committed seam), patch_apply_end IS persisted but with a
new wire shape, and unified-exec output hides behind a
'Script completed / Wall time / Output:' preamble.
- Strip the unified-exec output wrapper in the rollout mapper: the
collapsed preview finally shows OUTPUT (it used to burn all three
preview lines on boilerplate); wall time becomes the CommandCard
duration chip; 'Script failed' marks the result as an error.
- patch_apply_end: normalize BOTH observed wire shapes (protocol-source
HashMap<path, {unified_diff}> and the real binary's plain path array)
and render standalone events (their call_id pairs with nothing since
unified exec) as a Codex-native PatchResultCard — '• Edited path'
with tinted per-file diffs when diffs exist, a path list when not,
and '✘ Failed to apply patch' + stderr on failure (TUI patches.rs).
- OutputWell: head+tail preview (first N, '… +K lines', last N) —
Codex's output_lines clipping; the tail is where test summaries and
errors live, and head-only previews hid exactly that.
- DiffCard headers: colored (+N −M) totals (render_line_count_summary).
Follow-ups noted, not taken: Paginated-mode item_completed TurnItem
support, mcp/web/image *_end event enrichment, and tapping the
app-server event stream for structured live exec (the long-term fix).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Driven by the user's raw-codex vs feed comparison: - 'Script running with cell ID N' wrapper variant now strips too (the first strip only knew completed/failed, so attached background scripts still burned their preview on boilerplate). - tools.wait AND empty-chars write_stdin polls classify as 'wait' and render the native slim '• Waited for background terminal' row with a duration chip (they were a generic '3 params' card / raw script). The legacy standalone write_stdin null carve-out is untouched, so renderUnits' invisible-block selector still agrees with the paint. - Scripts with MULTIPLE tools.exec_command calls (Promise.all fanouts) now collect every command as its own command line — the first-only extraction hid all but one. - CommandCard header goes native: bold Running/Ran verb + bash- syntax-highlighted command (highlight_bash_to_lines equivalent), replacing the plain $-prefixed text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Superseded by #533, which merged the tested combined feed rewrite plus the follow-up streaming and presentation fixes into main. |
|
Status update: this PR is intentionally back in draft and must not be merged in its current form. The current rendering rewrite has accumulated substantial shape-assumption, reconciliation, and maintainability problems. We are changing the implementation philosophy: first build an unknown/distinctive event-shape collector and a reviewed fixture corpus from real Claude/Codex/OpenCode traffic; then define classified render contracts and modular renderers from that evidence. This branch requires a heavy rebase/reconciliation against current main before any new implementation work, and much of the present painter should be expected to be replaced rather than patched forward. |
|
WIP checkpoint pushed from the newer local feed-render-rewrite worktree. Commit: 1d46769 This preserves the materially newer local implementation on the draft PR so it is reviewable and cannot be lost. It includes the pure presentation projection, projection receipts, stable operation rows, provider extractors, presentation primitives, fixtures, and the later Rules-of-Hooks crash fix. Validation run before push:
Important: this checkpoint is evidence and salvage material, not an assertion that the current 11.8k-line migration is the architecture we should merge. The PR remains intentionally draft. The next design pass is evidence-first: build the distinctive/unknown shape collector and classified fixture corpus before freezing renderer families or replacing legacy paths. Provider ownership rule clarified after reviewing this checkpoint:
The checkpoint should now be reviewed for parts to retain, rewrite, or delete after the shape corpus exists. |
## 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>
|
Superseded by the evidence-first provider-owned renderer rewrite in #555. Phase 9 now records each valuable #524 primitive as ported, selectively ported, reimplemented, or rejected in docs/rendering/legacy-deletion-manifest.md. The global artifact projection and shared provider classifiers are intentionally not carried forward. #555 remains open through Phase 10 and is not being merged yet. |
What this is
The full RENDER-layer rewrite per
docs/superpowers/specs/2026-07-11-feed-render-layer-rewrite-design.mdand its 21-task implementation plan (both in this branch). The DECIDE layer (ownership ledger, view bridge,FeedRenderItemcontract) is untouched — every change is in the painter and provider renderer modules.Headline fixes
StreamingCodeBlockseals each completed line into a per-line hljs cache; only the tail line re-tokenizes per delta.SegmentedMarkdown: the prefix through the last closed fence parses once; O(len²) → O(len)).AnsiTexton the terminal palette;\rprogress rewrites collapse) and exit codes finally display (redexit Nbadges; Codex silent successes render a$ cmd ✓record instead of vanishing).ui/resolve/+ui/artifacts/): CommandCard, DiffCard (Edit/MultiEdit/apply_patch incl. tinted patch-failure diffs — successes used to render nothing), FileWriteCard (live-highlighted streaming writes), ReadCard, TodoCard, WebCard/ImageGenCard (Codex special tools get committed parity), SlashCommandRow (no more raw<command-name>tag soup), ThinkingBlock (redacted gets an explicit pill), GenericToolCard (the ONE fallback — live MCP tools no longer dump raw partial JSON).ui/hooks/. Deleted: both dispatch ladders' tool routing,JsonToolRow/ToolUseRow/DiffSlab-consumers,ClaudeRows, Codex exec/patch/stdin rows, bothTruncatedOutputRowcopies,renderToolUsefrom the capability contract.TaskalongsideAgent(future-proofing; current transcripts emitAgent).Verification
tscclean on both projects; 481/481 tests pass including the bundle + recording corpora (zero divergence — the one rollout change, silent-success results, was absorbed cleanly). The 1 failed test file is the documented pre-existing emptyhotkeyBindingsuite.electron-vite build+ remote-client build pass (kit stays browser-pure; Monaco remains desktop-lazy).🤖 Generated with Claude Code