[PR #3346] perf(tui/chat): virtualize message rendering to remove O(n) reflows during streaming #10609

Closed
opened 2026-02-16 18:15:19 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/3346

State: closed
Merged: No


Summary

  • Motivation: Full-backlog reflows during streaming/shimmer caused UI jank in large histories. Render time scaled with total messages instead of visible rows.
  • Approach: Virtualize chat message blocks and add a fast path so work scales with the viewport.

Changes

  • Virtualized rendering:

    • Build per-block caches once in the slow path:
      • blockContents: rendered string per block
      • blockHeights: pre-measured line heights
      • blockPrefix: start line per block (includes leading blank + inter-block blanks)
    • Use sort.Search to find the first visible block and only synthesize visible rows.
    • Cache per-block line splits (blockLineCache) to avoid repeated strings.Split.
  • Incremental indexing:

    • blockIndexByPartID maps part IDs to block indices (foundation for targeted per-part updates).
    • streamingReasoningID ensures shimmer applies only to the last in-flight reasoning block.
  • Header optimization:

    • Cache the header; rebuild only when width changes (lastHeaderWidth, headerDirty).
  • Shimmer gating:

    • Animate only when an assistant/tool is in-flight, re-render only when at bottom and backlog ≤ 2000 lines to avoid pathological repaint loops.
  • Selection overlay correctness:

    • Disable the fast path when selection is active to preserve copy/highlighting fidelity.

Impact

  • Streaming remains smooth on large histories; rendering work scales with the viewport, not the backlog.
  • Lower CPU usage and fewer jank frames during token streaming and shimmer.

Non-goals

  • No changes to message semantics, event ingestion, or UI look-and-feel.
  • No new deps, flags, or config.

Testing

  • Go TUI builds and runs (darwin/arm64 local).
  • Manual checks:
    • Scrolling and streaming stay responsive with large histories.
    • Selection overlay still highlights accurately and copies cleanly.
    • Header stable; only recomputes on width changes.

Future Work (optional)

  • Apply per-part incremental updates on MessagePartUpdated by adjusting only affected blocks and local prefix/height entries.
  • Add a debug toggle to force-disable virtualization for repros.

Files

  • packages/tui/internal/components/chat/messages.go

Notes for reviewers

  • Fast path only engages when caches are warm and no selection overlay is active.
  • Cache invalidation on resize/theme/session transitions is explicit to avoid stale layouts.
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/3346 **State:** closed **Merged:** No --- Summary - Motivation: Full-backlog reflows during streaming/shimmer caused UI jank in large histories. Render time scaled with total messages instead of visible rows. - Approach: Virtualize chat message blocks and add a fast path so work scales with the viewport. Changes - Virtualized rendering: - Build per-block caches once in the slow path: - blockContents: rendered string per block - blockHeights: pre-measured line heights - blockPrefix: start line per block (includes leading blank + inter-block blanks) - Use sort.Search to find the first visible block and only synthesize visible rows. - Cache per-block line splits (blockLineCache) to avoid repeated strings.Split. - Incremental indexing: - blockIndexByPartID maps part IDs to block indices (foundation for targeted per-part updates). - streamingReasoningID ensures shimmer applies only to the last in-flight reasoning block. - Header optimization: - Cache the header; rebuild only when width changes (lastHeaderWidth, headerDirty). - Shimmer gating: - Animate only when an assistant/tool is in-flight, re-render only when at bottom and backlog ≤ 2000 lines to avoid pathological repaint loops. - Selection overlay correctness: - Disable the fast path when selection is active to preserve copy/highlighting fidelity. Impact - Streaming remains smooth on large histories; rendering work scales with the viewport, not the backlog. - Lower CPU usage and fewer jank frames during token streaming and shimmer. Non-goals - No changes to message semantics, event ingestion, or UI look-and-feel. - No new deps, flags, or config. Testing - Go TUI builds and runs (darwin/arm64 local). - Manual checks: - Scrolling and streaming stay responsive with large histories. - Selection overlay still highlights accurately and copies cleanly. - Header stable; only recomputes on width changes. Future Work (optional) - Apply per-part incremental updates on MessagePartUpdated by adjusting only affected blocks and local prefix/height entries. - Add a debug toggle to force-disable virtualization for repros. Files - packages/tui/internal/components/chat/messages.go Notes for reviewers - Fast path only engages when caches are warm and no selection overlay is active. - Cache invalidation on resize/theme/session transitions is explicit to avoid stale layouts.
yindo added the pull-request label 2026-02-16 18:15:19 -05:00
yindo closed this issue 2026-02-16 18:15:19 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#10609