Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
65fd638
docs: feed RENDER-layer rewrite design spec
Juliusolsson05 Jul 11, 2026
c69ee08
docs: feed RENDER-layer rewrite implementation plan (21 tasks, 5 phases)
Juliusolsson05 Jul 11, 2026
0bec1b6
feat(feed): ANSI SGR parser + AnsiText kit primitive
Juliusolsson05 Jul 11, 2026
8164699
feat(feed): StreamingCodeBlock sealed-line streaming highlighter
Juliusolsson05 Jul 11, 2026
321abbe
fix(feed): stream live code fences through StreamingCodeBlock, not pe…
Juliusolsson05 Jul 11, 2026
595f8a7
perf(feed): segment streaming markdown — sealed prefix parses once, o…
Juliusolsson05 Jul 11, 2026
3b9718c
feat(feed): OutputWell / StatusBadge / ExpandSection kit chrome
Juliusolsson05 Jul 11, 2026
93d66d2
feat(feed): ANSI-aware OutputWell replaces both TruncatedOutputRow co…
Juliusolsson05 Jul 11, 2026
e77de79
refactor(feed): extract scarred Feed behaviors into ui/hooks (verbati…
Juliusolsson05 Jul 11, 2026
ea0c2fe
feat(feed): ArtifactVM discriminated union (artifact view-model contr…
Juliusolsson05 Jul 11, 2026
e6d4419
feat(feed): artifact resolvers — committed + live planes normalize to…
Juliusolsson05 Jul 11, 2026
181000b
feat(feed): CommandCard — one command surface, live + committed, ANSI…
Juliusolsson05 Jul 11, 2026
af5c8ce
feat(feed): GenericToolCard — single live+committed fallback, kills t…
Juliusolsson05 Jul 11, 2026
c26928c
feat(feed): slash-command rows + render Codex silent-success commands
Juliusolsson05 Jul 11, 2026
eff8827
feat(feed): DiffCard + DiffView — one file-edit surface for Edit/Mult…
Juliusolsson05 Jul 11, 2026
37184e0
feat(feed): FileWriteCard — live-highlighted streaming file writes
Juliusolsson05 Jul 11, 2026
9cd9dc7
feat(feed): ReadCard — Read/Grep/Glob/LS one-liners with expandable s…
Juliusolsson05 Jul 11, 2026
dc151c5
feat(feed): TodoCard — one todo surface, live + committed
Juliusolsson05 Jul 11, 2026
63fa575
feat(feed): WebCard + ImageGenCard — committed parity for Codex speci…
Juliusolsson05 Jul 11, 2026
a819430
feat(feed): ThinkingBlock unification + image URLs/lightbox + hook pa…
Juliusolsson05 Jul 11, 2026
2579e5c
refactor(feed): delete legacy tool_use dispatch — resolve/artifacts o…
Juliusolsson05 Jul 11, 2026
8fbe02e
docs(rendering): update rendering-system.md for the artifact painter
Juliusolsson05 Jul 11, 2026
27c054a
fix(feed): restore the live Write route lost in the dispatch dedup
Juliusolsson05 Jul 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/rendering/rendering-design-principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Two mechanisms, both surviving the cutover (they don't need the deleted legacy r
- ...use `streamPhase` as a submit-ownership signal — ownership is renderable *content* (§7 rule 10).

**Always:**
- Treat the ledger as the *single* decision point. If you find yourself deciding visibility in a feed component, stop — that decision belongs upstream, and putting it in the painter recreates the distributed-ownership bug class the ledger was built to end.
- Treat the ledger as the *single* decision point. If you find yourself deciding visibility in a feed component, stop — that decision belongs upstream, and putting it in the painter recreates the distributed-ownership bug class the ledger was built to end. The 2026-07 painter rewrite added an artifact layer (`features/feed/ui/resolve/` + `ui/artifacts/` — see `rendering-system.md` §5 and `docs/superpowers/specs/2026-07-11-feed-render-layer-rewrite-design.md`); that layer is *derivation*, not decision — resolvers must stay pure and total (never hide, never throw), and a card must never branch on `plane`.
- Prefer showing-and-explaining over hiding.
- Fix stale documentation you touch. The pipeline has a history of comments outliving the code they describe (`SemanticStreamingTurn`, `AGENT_CODE_RENDER_SHADOW`, `AGENT_CODE_RENDER_PIPELINE`, `deriveFeedRenderModel` are all **deleted** but still referenced in stray comments). A stale comment about a *rendering* decision is how the next person reintroduces the old model.

Expand Down
17 changes: 13 additions & 4 deletions docs/rendering/rendering-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,20 @@ A discriminated union: `entry` (a committed/ghost/optimistic transcript entry)

### `Feed.tsx` — the painter

`Feed = memo(FeedImpl)`: the whole component is memoized so composer typing / focus / split-resize bail the entire markdown subtree. Its core is a single `{renderItems.map(renderFeedItem)}` — **the one-owner rule** — where `renderFeedItem` is a switch over the six item types. The container owns its own scroll listener; sticky-bottom follow, scroll-position persistence across unmount, lazy mounting (`LazyEntry` + `EAGER_TAIL`, keyed on committed ordinal so a busy live turn can't push the newest prompt into lazy-mount), older-history load, and the picker auto-scroll tweens all live here as independent effects. **Performance is an identity-stability story:** memo-by-`text` prose is the single biggest win, every row is individually memoized, and the tool-index context clones only on `toolIndexVersion` bump.
`Feed = memo(FeedImpl)`: the whole component is memoized so composer typing / focus / split-resize bail the entire markdown subtree. Its core is a single `{renderItems.map(renderFeedItem)}` — **the one-owner rule** — where `renderFeedItem` is a switch over the six item types. The scarred behaviors (sticky-bottom follow, scroll-position persistence, lazy mounting via `LazyEntry` + `EAGER_TAIL` keyed on committed ordinal, older-history load, both picker auto-scroll tweens, and the feed-debug emission) live in `features/feed/ui/hooks/` — ported verbatim in the 2026-07 painter rewrite, each hook header citing its Feed.tsx region of origin. **Performance is an identity-stability story:** every row is individually memoized, and the tool-index context clones only on `toolIndexVersion` bump.

### Row dispatch
### Row dispatch — the artifact layer (2026-07 RENDER rewrite)

`EntryRow` → (`CompactBoundaryRow` / `CompactSummaryRow` / `TaskNotificationRow` / `ConversationRow` / `SystemRow`). `ConversationRow` renders string content as one `MarkerRow`, array content as one `Block` per content-block (coalescing adjacent subagent spawns under a `SubagentGroupHeader`). **`Block`** is the per-content-block dispatcher — `text`/`thinking`/`image`/`tool_use`/`tool_result`routing tools through `getRendererProviderCapabilities(provider).renderToolUse/renderToolResult` with a shared `JsonToolRow`/`ToolResultRow` fallback, plus git-widget interception (`GitCardRow`), subagent rows, and AskUserQuestion answered rows.
`EntryRow` → (`CompactBoundaryRow` / `CompactSummaryRow` / `TaskNotificationRow` / `ConversationRow` / `SystemRow`). `ConversationRow` renders array content as one `Block` per content-block. From `Block` (committed) and `SemanticLiveBlockRow` (live) down, dispatch is no longer two parallel laddersthe drift machine the 2026-07 audit flagged. Both planes normalize into **one `ArtifactVM` per logical artifact** and every family renders through **exactly one card**:

Live rows mirror committed ones so streaming ≈ final: **`SemanticLiveBlockRow`** dispatches by `block.kind` and *reuses the committed provider renderers* for live Edit/MultiEdit/Codex tools (same card, partial input). Everything hangs off **`MarkerRow`** — the universal `❯` (user) / `⏺` (assistant) / `⎿` (tool/sub-item) fixed-marker-column + hanging-indent primitive — and prose flows through `TextProse`/`StreamingProse` (react-markdown + remark-gfm, `remark-breaks` for the streaming screen buffer). The single "agent is working" affordance is **`WorkIndicator`**, driven solely by `streamPhase`.
- **`features/feed/ui/artifacts/types.ts`** — the `ArtifactVM` discriminated union. Every VM carries `status: streaming | running | complete | error` and `plane` (debug provenance ONLY — a card that branches on plane is reintroducing the fork).
- **`features/feed/ui/resolve/`** — pure, decision-free derivation. `registry.ts` is the ONE family-routing table (`routeFamily`, total — unknown names are `generic`, never hidden) plus `RESULT_CONSUMING_FAMILIES` (families whose card consumes the paired tool_result; the committed result block is suppressed for exactly these — one predicate, both branches) and `isLegacyProviderClaimed` (opencode `read` keeps its tag-soup result parser). `fromCommitted.ts` / `fromLive.ts` produce identical VMs from `ToolUseBlock`+result pairs and `SemanticLiveBlock`+snapshot respectively.
- **`features/feed/ui/artifacts/`** — one card per family: `CommandCard` (Bash/exec/local_shell/stdin — ANSI output, exit badges, Codex parsed-read summaries), `DiffCard` (Edit/MultiEdit/apply_patch over `DiffView`), `FileWriteCard` (live-highlighted streaming writes), `ReadCard` (Read/Grep/Glob/LS one-liners with expandable source), `TodoCard`, `WebCard`, `ImageGenCard`, `SlashCommandRow` (parses the `<command-name>` envelope out of user text), `ThinkingBlock` (one renderer, both planes; redacted gets an explicit pill), and `GenericToolCard` — THE one fallback (MCP rides it with a server pill).
- **`features/feed/ui/kit/`** — the streaming-safe primitives underneath: `StreamingCodeBlock` (sealed-line hljs cache — a sealed line never re-tokenizes, only the live tail line re-renders per delta; NO Monaco on the live path, ever — the per-delta Monaco remount was the 2026-07 rewrite's headline defect), `SegmentedMarkdown` (the sealed prefix through the last CLOSED fence parses once; only the tail re-parses per delta), `AnsiText` (SGR subset on the terminal palette), `OutputWell` (the one collapsible output region, loud truncation), `DiffView` (per-line tint + syntax highlight, windowed for huge diffs), `StatusBadge`, `ExpandSection` (first-open Monaco gating).

Provider knowledge lives in **extractors, not JSX**: `providers/{claude,codex}/renderer/extractors.ts` (slash envelope, edit partials, apply_patch grammar, exec/exit metadata). The capability table no longer has `renderToolUse`; `renderToolResult` survives only for unpaired results (Codex meta-keyed rows) and legacy-claimed tools. Pre-routing interceptions (git-intent cards, spawn tools → `TaskSubagentRow`, AskUserQuestion) stay ahead of the family table in `Block`/`BlockRow`.

**Streaming ≈ final, by construction:** the same card renders `status:'streaming'` and `status:'complete'`; a tool finishing is a props flip, never a component swap or remount. Everything hangs off **`MarkerRow`** — the universal `❯` / `⏺` / `⎿` fixed-marker-column + hanging-indent primitive — and the single "agent is working" affordance is **`WorkIndicator`**, driven solely by `streamPhase`.

---

Expand Down Expand Up @@ -227,6 +234,8 @@ This is why typing in the composer never re-parses the transcript's markdown, an
| the painter | `features/feed/ui/Feed.tsx` |
| the item contract | `features/feed/model/renderModel.ts` |
| row dispatch | `features/feed/ui/rows/{EntryRow,ConversationRow,Block}.tsx` |
| the artifact layer | `features/feed/ui/artifacts/types.ts` + `features/feed/ui/resolve/registry.ts` |
| streaming primitives | `features/feed/ui/kit/` |
| live rows | `features/feed/ui/semantic/BlockRow.tsx` |
| the layout primitive | `features/feed/ui/MarkerRow.tsx` |
| machine-checked invariants | `rendering/replay/invariants.ts` |
Expand Down
Loading