Skip to content

Transcript windowing: opening a 1000-message thread mounts the whole transcript into the DOM #564

Closed

Description

@currentbits

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) returns messages: store.getMessages(threadId).slice() — the full array, up to 1000+ messages, structured-cloned over IPC on every thread open.
  • ThreadView renders all of it: timeline.map((entry) => …) at src/components/ThreadView.tsx:4120. No windowing, no pagination — every message becomes a live MessageBlock with 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:

  1. ThreadView renders 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.
  2. 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.
  3. 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:

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

  1. added and removed on Aug 19, 2026
  2. currentbits commented on Aug 21, 2026

    @currentbits
    OwnerAuthor

    Working this in a worktree: renderer-only tail window (last 120 timeline entries + Show earlier), ensureVisible for jump/find/deep links. Not touching #225 IPC payload or #418 boot hydrate.

    Tests first in test/threadView.test.tsx as specced, then the window integer in ThreadView.

  3. added and removed on Aug 21, 2026
  4. currentbits commented on Aug 21, 2026

    @currentbits
    OwnerAuthor

    Renderer tail-window is in this worktree.

    Tests: test/threadView.test.tsx (windowing describe) + test/transcriptWindow.test.ts. tsc clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions