Skip to content

perf(tui): terminal resize re-renders the full transcript; cost scales with session size #54112

Open

Description

@ego1s1

Resizing the terminal with a big session open feels sluggish, so I dug in to see what actually happens per resize. Turns out the whole transcript gets re-evaluated every time.

In packages/tui/src/routes/session/index.tsx, messages() reads the full store with no slicing, then <For each={messages()}> renders every message, each rendering all of its parts, each text part going through <markdown>. On top of that, width-dependent memos (like the tool-output collapse) all recompute through useTerminalDimensions. Nothing is virtualized.

I measured it with a testRender harness (transcript-shaped tree, resize across a few widths). Median per-resize cost:

  • 50 parts → ~17 ms
  • 500 parts → ~23 ms
  • 2000 parts → ~94 ms

So it's O(session) per frame. Full numbers in the gist.

Two related things I ran into in the same area:

  1. Past a few thousand markdown/code renderables in one tree, the native side throws Failed to create native renderable (via TextBufferRenderable <- Code <- Markdown:updateBlocks). Looks like a finite FFI handle pool, so very large histories can hit a hard ceiling just by rendering.
  2. session.deleted still only drops the session entry and message.removed leaves part[] behind (TUI store memory leak: session deletion and message removal leave orphaned data #12351 describes exactly this — flagging since that issue is closed but the code still shows the gap on dev).

To be clear about what this is not: per-resize terminal output is screen-bounded (around 10 KB per frame against a real 52 MB session, bit-identical per width, TUI RSS flat over 30 resizes). So this is purely a responsiveness/perf issue, not a memory blowup. Filing as perf only.

Possible directions: virtualize the transcript (viewport + overscan), truncate large part bodies in the transcript view, and port cleanupSessionCaches() over to the TUI store. Happy to PR the test harness as packages/tui/test/context/resize-cost.test.tsx plus the store eviction if that sounds reasonable.

Measurements: https://gist.github.com/ego1s1/417d155a67f25c95e997614ddf784daa

Activity

  1. opencode-agent commented on Oct 9, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed measurements and the gist! To restate the request: the v2 TUI should not re-render the whole transcript on every terminal resize, so that resize cost stays bounded by the viewport instead of growing with session size. You suggest viewport virtualization with overscan, truncating large part bodies in the transcript view, and evicting cached store data.

    One note from the current v2 code: routes/session/index.tsx does slice rows (visibleRows via hidden()/visibleRowsEnd), but that only hides older history. It is not a viewport window, so every row that is shown still mounts all its parts and markdown, and the width-dependent memos all recompute on resize. Your core observation holds.

    Maintainers will consider this; I can't promise anything about whether or when. Related issues:

    If you open a PR for the resize-cost harness or the store eviction, linking it here (and to #39380 if it's relevant) will help maintainers review it alongside the virtualization question.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions