fix(cli): TUI flicker — cursor-collapse + synchronized output#125
Merged
Conversation
Backport of the core foundation pieces from upstream QwenLM#3591 (TUI flicker foundation fixes). Two interventions on stdout, both no-op outside a TTY or under a screen reader: 1. terminalRedrawOptimizer (from upstream QwenLM#3381 → QwenLM#3591): wraps stdout.write to collapse Ink's per-line {ERASE_LINE, CURSOR_UP_ONE} sequences into a single {CURSOR_UP_N, erase_at_each, CURSOR_UP_N, CURSOR_LEFT}. Eliminates the scrollback-bouncing during streaming renders. Bypass via PROTO_LEGACY_ERASE_LINES=1. 2. synchronizedOutput (from QwenLM#3591): wraps each render frame in BSU/ESU escape codes (\\e[?2026h / \\e[?2026l) on supporting terminals (Kitty, WezTerm, iTerm) so Ink frames are committed atomically. Terminal allowlist with auto-detect; opt-out via PROTO_DISABLE_SYNCHRONIZED_OUTPUT=1, force on via PROTO_FORCE_SYNCHRONIZED_OUTPUT=1. Both installed in startInteractiveUI() before the Ink render call; both restored in the registerCleanup callback. Deferred from upstream QwenLM#3591 (too entangled with our fork's useGeminiStream.ts / ToolMessage.tsx divergence; revisit later): - main-stream event buffering with flush timer - tool output pre-slicing by visual height - shell soft-wrap-only rerender suppression The two installed pieces cover the user-visible streaming flicker case directly; the deferred pieces are about huge-tool-output and narrow-terminal soft-wrap edge cases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
WalkthroughThe pull request adds two new terminal output optimization utilities—synchronized output and redraw optimization—along with comprehensive test coverage. The main CLI startup logic is updated to conditionally install both optimizers when running on a TTY, capturing restore functions for cleanup on exit. Changes
Sequence Diagram(s)sequenceDiagram
participant Startup as CLI Startup<br/>(gemini.tsx)
participant SyncOut as Synchronized<br/>Output
participant RedrawOpt as Redraw<br/>Optimizer
participant Process as Process/<br/>stdout
Startup->>SyncOut: terminalSupportsSynchronizedOutput()
SyncOut-->>Startup: boolean (check env/TMUX/SSH/etc)
Startup->>RedrawOpt: installTerminalRedrawOptimizer()
RedrawOpt->>Process: wrap stdout.write
RedrawOpt-->>Startup: restore function
Startup->>SyncOut: installSynchronizedOutput()
SyncOut->>Process: patch stdout.write with frame markers
SyncOut-->>Startup: restore function
note over Process: stdout writes now optimized<br/>with redraw + sync markers
Startup->>Startup: ... UI runs ...
Startup->>SyncOut: call restore function
SyncOut->>Process: restore original stdout.write
Startup->>RedrawOpt: call restore function
RedrawOpt->>Process: restore original stdout.write
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
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.
Summary
Backport of the foundation pieces of upstream QwenLM#3591. Two stdout interventions installed in `startInteractiveUI()`, both no-op outside a TTY or under a screen reader.
1. `terminalRedrawOptimizer` (cursor-collapse)
Wraps `stdout.write` to collapse Ink's per-line `{ERASE_LINE, CURSOR_UP_ONE}` sequences into a single bounded `{CURSOR_UP_N, erase_at_each, CURSOR_UP_N, CURSOR_LEFT}`. This is the main fix for the streaming-render scrollback bounce. Bypass via `PROTO_LEGACY_ERASE_LINES=1`.
2. `synchronizedOutput` (atomic frame commit)
Wraps each render frame in BSU/ESU escape codes on supporting terminals (Kitty, WezTerm, iTerm). Auto-detect via `TERM_PROGRAM`/`KITTY_WINDOW_ID`/`TERM`; opt-out via `PROTO_DISABLE_SYNCHRONIZED_OUTPUT=1`, force-on via `PROTO_FORCE_SYNCHRONIZED_OUTPUT=1`.
What's deferred
Three pieces of upstream QwenLM#3591 not in this PR — they collide with our fork's diverged `useGeminiStream.ts` / `ToolMessage.tsx` and need careful manual ports:
The two pieces in this PR cover the most user-visible flicker case (streaming-render scrollback bouncing) directly. The deferred pieces are about huge-tool-output and narrow-terminal soft-wrap edge cases.
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Tests