Repository navigation
perf(cli): render one smaller page of transcript messages - #1550
Conversation
A re-render lays out every rendered node, and the cost is linear in that count whether or not the node changed: an unchanged 1,500-node transcript measures ~60ms per frame against ~0.1ms when nothing re-renders, so memoizing cannot rescue it and a streamed chunk re-renders the whole page. At the real density (~770 nodes per message) one page of 15 messages costs ~266ms per frame against ~104ms for six, so a long conversation falls behind its own output. LoadPreviousButton already offers the rest of the history, so the smaller page trades a click of history for frames that keep up with the stream.
|
The underlying observation is useful: opentui's re-render cost is apparently linear in node count regardless of diffing, so the page size for streamed transcript rendering is a real knob worth tuning. The comment you added documenting why 15 was chosen (or wasn't) is a genuine improvement over the bare constant. That said, this is a one-line behavioral change justified entirely by numbers in the PR description that aren't reproducible from the diff itself. There's no bench script checked in, no test asserting the frame-cost claim, and no way for a maintainer to verify 6 vs. 15 vs. some other value without re-running your informal
Also 15 → 6 is a steep cut to default history visibility — more than half of users' visible scrollback disappears behind a click. That's a real UX regression that should be weighed against the frame-rate win explicitly by a maintainer, ideally with the two numbers ( Good instinct, right file, but needs a verifiable benchmark and probably a smaller first cut (e.g. 15 → 10) with room to iterate, rather than committing to 6 based on out-of-tree measurements. |
Why
A re-render in opentui lays out every rendered node, and the cost is linear in that count whether or not the node changed:
renderOncewith nothing re-renderedSo the cost is not a per-frame paint charge — an untouched tree is nearly free. It is paid per re-render, which is why memoizing cannot rescue it: a page of 15 memoized messages measured 15.0 ms against 13.1 ms for the same page unmemoized at 1,800 nodes.
@opentui/core@0.3.4has no culling, so the only lever is how many nodes a page holds.A streamed chunk re-renders the whole page. At the real density (~770 nodes per message) one page measures:
What
MESSAGE_BATCH_SIZE15 → 6, with the measurement recorded next to the constant so the next reader knows it is a frame-cost knob and not a layout preference.LoadPreviousButtonalready renders the hidden count and loads a page at a time (handleLoadPreviousMessagesaddsMESSAGE_BATCH_SIZE), so the rest of the history is one click away and each load stays cheap.Verification
bun run typecheck→ exit 0bun testover all non-integration files: 3958 pass, 20 fail — the same 20 import errorsmainhas in a partial checkout (Failed to load CLI environment … Cannot find module '../../../packages/internal/src/env');mainalone measures 3951 pass / 20 fail.bun benchruns against@opentui/core's test renderer, not the app harness; the ratio is what the change buys.Trade-off
Fewer messages visible before clicking "load previous". If 6 is too aggressive, the number is independent of the mechanism — say which page size you want and I will re-measure it.