perf: window the transcript render to O(viewport)#5
Conversation
The scrollback re-processed the ENTIRE transcript every frame. Even on a cache hit it cloned the full Vec<Line> and handed it to ratatui's Paragraph, which word-wraps all lines (then discards everything above the scroll offset) just to show ~40 visible rows. On a multi-thousand-line session every mouse-wheel tick re-wrapped the whole buffer — visibly sluggish, exactly the O(transcript)-per-scroll the symptom pointed at. Cache a prefix sum of per-line wrapped-row counts on each (already rare) rebuild, then on render binary-search the handful of logical lines that intersect the viewport and hand ratatui only that slice. Scrolling is now O(viewport), not O(transcript); the per-frame full-buffer clone + re-wrap are gone. `visible_window` is a pure, unit-tested function (incl. a property test that the returned slice always covers the requested window). Per-line counts use ratatui's own line_count, so the layout stays byte-for-byte identical to before — only the work per frame changed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe scrollback cache now stores wrapped-row prefix offsets, adds a helper to map viewport rows to logical-line slices, and updates rendering to build and use that slice instead of scrolling a paragraph over the full buffered transcript. ChangesScrollback viewport slicing
Sequence Diagram(s)sequenceDiagram
participant Render
participant ScrollbackBuild
participant visible_window
participant Paragraph
Render->>ScrollbackBuild: compute per-line wrapped row counts
Render->>ScrollbackBuild: store row_offsets and total_rendered_rows
Render->>visible_window: visible_window(row_offsets, y, viewport_rows)
visible_window-->>Render: first, intra, last
Render->>Paragraph: build paragraph from lines[first..last]
Render->>Paragraph: scroll((intra, 0))
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5 +/- ##
==========================================
+ Coverage 36.79% 41.86% +5.07%
==========================================
Files 30 30
Lines 5156 5317 +161
==========================================
+ Hits 1897 2226 +329
+ Misses 3259 3091 -168
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Add two TestBackend tests: one asserts the prefix sum is built and the latest message shows at the bottom (following mode); the other scrolls to the top and asserts the window tracks the offset (earliest line visible, latest off-screen). Exercises the cache-miss build + windowed render path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes the mouse-wheel scroll lag on large sessions.
Diagnosis
The transcript re-processed the whole buffer every frame. Even on a cache hit,
ui/scrollback.rsdid:ratatui's
Paragraphisn't virtualized —.wrap().scroll((y,0))word-wraps the entire transcript and then throws away everything above rowyto show ~40 rows. So a multi-thousand-line session re-wrapped the whole buffer on every mouse-wheel tick → the sluggishness. The per-messageRenderCachehelped, but this final step was still O(transcript) per frame.Fix — O(viewport) per frame
ScrollbackBuild::row_offsets), using ratatui's ownline_countper line so it stays byte-for-byte consistent with how the slice lays out.visible_window()binary-searches the few logical lines intersecting[y, y+viewport)and hands ratatui only that slice (plus an intra-line scroll offset). No full clone, no full re-wrap.Net: a 10k-line session now re-wraps ~viewport rows per scroll instead of 10k. Layout is identical — only the per-frame work changed.
Tests
visible_windowis pure + unit-tested (top/middle/bottom windows, short-buffer, empty-safe, and a property test that the slice always covers the requested window). Fullcodeoid-tuisuite: 162 passed.cargo fmtclean.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes