perf(tui): cut redundant redraws and diff re-tokenization - #9
Merged
Merged
Conversation
The TUI felt laggy under a few over-frequent redraw paths, all on the UI thread. No blocking async was involved — the cost was redraw frequency and re-tokenization, not per-cell render work. - Diff review: the background stats prefetch raised the content-level Changed event after every file, so the *currently open* file was re-tokenized and recomposed once per file in the PR (N-file PR → N re-tokenizes) while the reviewer tried to scroll. Split a StatsChanged event from Changed: prefetch never alters the selected file/CurrentDiff, so the view now refreshes only the title totals and file-row stats and skips the diff-pane rebuild. - Shell chrome: a routine status/log message ran a whole-app LayoutAndDraw to update one fixed-layout label. Mark the chrome labels dirty and let the run loop repaint them instead. - PR list: a burst of per-PR comment-count arrivals re-formatted every row and reset the source once per count (O(rows^2) over a load of 50–200 PRs). Coalesce the burst into a single re-render. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018R2B8VAVaMVc8LK7EfKRBb
The two prefetch tests observed/cancelled on vm.Changed, which the stats prefetch no longer raises (it now raises StatsChanged, leaving the displayed diff untouched). Point them at StatsChanged and assert the prefetch does not raise the content-level Changed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018R2B8VAVaMVc8LK7EfKRBb
jinyeow
added a commit
that referenced
this pull request
Jul 18, 2026
* origin/main: perf(round-2): diff-render, transport & input hot-path optimizations (#18) chore(tui): clear two CodeQL quality alerts in TextInputDialog (#12) perf(pr): make PR review feel local — and fix four wrong-file bugs found on the way (#11) feat(tui): lazygit-style shell chrome — context keybar, showcmd, real tab strips (#27) perf(tui): cut redundant redraws and diff re-tokenization (#9) docs: add CLAUDE.md project guidance for Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The TUI felt laggy on navigation. Two investigations converged on the same conclusion: the cost is redraw frequency and re-tokenization on the UI thread, not blocked async — the sync-over-async audit came back clean (the ADO client is fully async and never called from a key handler). This PR fixes the three highest-impact, low-risk redraw paths.
What changed
A — Diff review re-tokenization storm (biggest win). The background stats prefetch raised the content-level
Changedevent after every file, so the currently-open file was fully re-tokenized and recomposed once per file in the PR (an N-file PR → N re-tokenizes) while the reviewer tried to scroll. A newStatsChangedevent is split fromChanged: prefetch provably never changes the selected file /CurrentDiff, so the view now refreshes only the title totals and file-row stats and skips the diff-pane rebuild.B — Whole-app redraw on every status message.
CobaltShell.RefreshChromeran a full-appLayoutAndDrawto update one fixed-layout label. It now marks just the chrome labels dirty and lets the run loop repaint them.C — PR-list comment-count burst. Each per-PR comment-count arrival re-formatted every row and reset the list source (O(rows²) over a load of 50–200 PRs). The burst now coalesces into a single re-render.
Deliberately not included: the per-keystroke
LayoutAndDraw(true)on the main lists — it is intentionallytrueper ADR 0016 to avoid the "needs a second keypress" symptom under a multiplexer, and changing it needs a real-terminal UAT.Tests
PrDiffViewModelTests— prefetch raisesStatsChangedper file and never raises the content-levelChanged.DiffReviewDialogKeyTests— a stats refresh leaves the diff pane's data source instance in place (no rebuild).Fixes B/C are behavior-preserving redraw optimizations covered by the existing suite.
🤖 Generated with Claude Code
Generated by Claude Code