Repository navigation
Description
Opening a thread renders its entire transcript into the DOM, and the transcript arrives whole over IPC. Nothing bounds what the renderer holds; only #89's storage cap (1000 messages) bounds what exists.
Today
getThreadDetail(electron/services.js:3076-3084) returnsmessages: store.getMessages(threadId).slice()— the full array, up to 1000+ messages, structured-cloned over IPC on every thread open.ThreadViewrenders all of it:timeline.map((entry) => …)atsrc/components/ThreadView.tsx:4120. No windowing, no pagination — every message becomes a liveMessageBlockwith markdown rendering, diff colouring (DiffLine), tool cards, attachments.- The store's live data agrees this is the common case, not the tail: the biggest thread is 1081 messages / 2.6 MB, three more are over 1000, and the per-thread cap means long-running threads all converge on ~1000-message transcripts.
memo on MessageBlock/UserMessageBlock keeps re-renders cheap during streaming — that part is done right, and streaming appends are already tail-pushed (runner.js prefix diff). The unpaid cost is the initial mount and the standing DOM: switching to a big thread mounts ~1000 heavy components at once (one long main-thread task = the exact freeze a user feels as "the app hangs when I click a thread"), and every one of them stays mounted, holding DOM nodes and listeners, while you type in the composer underneath.
Proposed: render the tail, load earlier on demand
Not a virtualization library. Transcripts are append-only and users land at the bottom; the cheap shape is:
ThreadViewrenders the last N timeline entries (N ≈ 120 — covers multiple screens) plus one "Show earlier — X messages" row at the top. Clicking extends the window by another chunk. State is one integer.- The existing overflow marker from Cap transcript growth and stop whole-store synchronous stringify on flush #89's
capList(the "older messages dropped" event) composes naturally — it just sits above the first rendered chunk when reached. - Anchors that point above the window (jump-to-turn Jump-to-turn rail: one tick per user prompt #487, transcript find Find in the current transcript (Cmd+F), not just the sidebar #486, deep links) extend the window to include their target instead of failing — one
ensureVisible(index)that raises the integer.
Ships independently of, and composes with, the two stored-side issues:
- Shard messagesByThread into per-thread files: one message re-serializes all 227 threads #225 (per-thread files) fixes what the main process pays per flush.
- Electron architecture: agent-host utility process, MessagePort streaming, startup wins #418's "lazy transcripts" bullet fixes what boot hydrates.
- This fixes what the renderer pays per thread-open and holds while idle. It also makes
getThreadDetail's full-array IPC push mostly moot (the renderer can keep receiving the full array; it just stops mounting all of it — trimming the IPC payload can come later with Shard messagesByThread into per-thread files: one message re-serializes all 227 threads #225's lazy loads).
Tests
test/threadView.test.tsx: a 500-message detail renders only the tail window plus the "Show earlier" row; clicking it grows the window; a detail under N renders fully with no row.- Streaming append to a windowed thread appends at the tail without extending the top window.
- Jump-to-anchor above the window extends the window to include it.
Related
#225 (store shard — main-process half), #418 (lazy transcripts at boot), #487 (jump-to-turn rail), #486 (find in transcript), #461 (focus view reduces block count for a different reason), #124 (the beachball history that started the perf thread).
Activity
currentbits commentedon Aug 21, 2026
Working this in a worktree: renderer-only tail window (last 120 timeline entries + Show earlier),
ensureVisiblefor jump/find/deep links. Not touching #225 IPC payload or #418 boot hydrate.Tests first in
test/threadView.test.tsxas specced, then the window integer in ThreadView.currentbits commentedon Aug 21, 2026
Renderer tail-window is in this worktree.
revealMessageIdis the Jump-to-turn rail: one tick per user prompt #487/Find in the current transcript (Cmd+F), not just the sidebar #486/ensureVisiblehook.Tests:
test/threadView.test.tsx(windowing describe) +test/transcriptWindow.test.ts. tsc clean.Long threads load in pages and render lighter, safer file restores, a…