fix(cli): TUI flicker — deferred pieces (event buffering, pre-slicing, shell wraps, Alacritty)#127
Conversation
… docs Auto-detect Alacritty (ALACRITTY_WINDOW_ID, TERM=alacritty[-direct]) and Ghostty (TERM_PROGRAM=ghostty, GHOSTTY_RESOURCES_DIR). Both support DEC mode 2026 — Alacritty since v0.14 (Jan 2024), Ghostty since launch. Docs: README "Environment variable overrides" table now lists the three flicker-related env vars (PROTO_LEGACY_ERASE_LINES, PROTO_FORCE_SYNCHRONIZED_OUTPUT, PROTO_DISABLE_SYNCHRONIZED_OUTPUT) and adds a "TUI flicker mitigation" section explaining the two interventions and the auto-detected terminal allowlist. Tests: 5 new entries in the parametrized auto-detect test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backport of upstream QwenLM#3591 (commits 172df3d + 9ac3c11). Adds sliceTextForMaxHeight() to ToolMessage.tsx that walks tool output by code-point and tracks visual width per terminal column, slicing to maxHeight before the string enters Ink's <Text wrap="wrap">. Without this, a single 160k-character JSON/base64 line never gets sliced (lines.length === 1 <= maxHeight), but Ink still computes layout over the full unbounded string — the source of the "Ink hangs on huge tool output" symptom. The hidden line count is passed to MaxSizedBox via additionalHiddenLinesCount so the "+ N hidden lines" indicator stays accurate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backport of upstream QwenLM#3591 (commit b4428f3). Wraps the for-await event loop with a per-stream buffered queue that coalesces consecutive Content (or Thought) events into a single React state update. Without this, every streamed token chunk triggered an Ink redraw — the main source of streaming flicker. - bufferedEvents queue + flushTimer (STREAM_UPDATE_THROTTLE_MS = 60). - flushBufferedStreamEvents() merges consecutive same-kind events before dispatching to handleContentEvent / handleThoughtEvent. - All non-Content/Thought events flush immediately to preserve order. - Retry path discards buffered partial content from the failed attempt. - try/finally guarantees a final flush on stream end or throw. - flushBufferedStreamEventsRef exposes the flusher to cancelOngoingRequest so partial output still commits to history when the user hits Esc. Adapted from upstream: - Our fork removed dualOutput.startAssistantMessage/processEvent calls. - Our HookSystemMessage handler treats the message as content (Ralph Loop iteration info) rather than upstream's stop-hook history item; we flush before processing to preserve ordering. - Upstream's UserPromptSubmitBlocked / StopHookLoop / dualOutput helpers don't exist in our fork — handled implicitly via the exhaustive switch. 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 (9)
WalkthroughThis PR enhances terminal UI rendering, streaming performance, and terminal compatibility. It expands synchronized output detection for additional terminals (Ghostty, Alacritty), introduces event buffering/throttling for streaming updates, implements height-aware text rendering, refactors terminal buffer serialization with line unwrapping capability, adds TUI flicker mitigation documentation, and improves output de-duplication logic. Changes
Sequence Diagram(s)sequenceDiagram
participant Stream as Gemini Stream
participant Hook as useGeminiStream Hook
participant Buffer as Buffered Events<br/>(bufferedEvents)
participant Timer as Flush Timer<br/>(STREAM_UPDATE_THROTTLE_MS)
participant Handler as Content/Thought<br/>Handler
Stream->>Hook: Streaming Chunk (Thought/Content)
Hook->>Buffer: Accumulate in bufferedEvents
activate Timer
Timer-->>Hook: Throttle timeout
deactivate Timer
Hook->>Handler: Flush buffered content<br/>(merged updates)
Stream->>Hook: Discrete Event (Tool, Error, etc.)
Hook->>Buffer: Flush any pending<br/>buffered events first
Hook->>Handler: Process discrete event<br/>(immediate, ordered)
sequenceDiagram
participant Terminal as Terminal Buffer
participant Serializer as serializeTerminalToText/<br/>serializeTerminalToObject
participant Unwrap as Line Unwrapping<br/>(unwrapWrappedLines)
participant Service as shellExecutionService
participant Dedup as De-duplication<br/>(Token-by-Token)
participant Output as Emit 'data' Event
Terminal->>Serializer: Active buffer (with wrapped lines)
Serializer->>Unwrap: Process with unwrapWrappedLines: true
Unwrap->>Serializer: Merge soft-wrapped lines<br/>(matching styles)
Serializer->>Service: Return unwrapped AnsiOutput
Service->>Dedup: Compare current vs prior<br/>token-by-token
alt Content matches prior (after unwrap)
Dedup->>Service: Skip duplicate
else Content differs
Dedup->>Output: Emit 'data' event
end
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. |
Summary
Completes the backport of upstream QwenLM#3591 by porting the pieces deferred from #125, plus adding Alacritty/Ghostty auto-detect (which #125 didn't cover).
What's in this PR
Streaming flicker fix (the headline)
Tool output handling
Shell rendering (3 cherry-picks from upstream)
Terminal allowlist + docs
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Performance