fix(tui): stop live-region output duplicating on terminal resize#469
Conversation
The sticky task list, composer, and status bar live in Ink's live region (not <Static>). Ink redraws that region by erasing the previous frame's *stored* line count, but on resize the terminal has already reflowed that frame to a different height, so the erase misses lines and the stale frame is pushed into scrollback — one duplicate per resize event, producing an avalanche of repeated task/status blocks during a drag that only a manual clear (Cmd+K) cleaned up. The prior fix (#467) cleared on resize but was debounced (trailing only), so it never fired during a continuous drag and the avalanche built until the drag stopped. Replace it with a leading+trailing throttle driven by a direct stdout 'resize' listener: refreshStatic() repaints a single clean frame at a bounded cadence (every 80ms) *during* the drag and once more when it settles. This also subsumes the <Static> history reflow #467 addressed. Listening on stdout directly (not useTerminalSize's React state) keeps the handler synchronous on the event and immune to React batching. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
👀 Quinn is reviewing — verdict (PASS / WARN / FAIL) + findings to follow. |
|
Warning Review limit reached
Next review available in: 23 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
QA Audit — PR #469 | fix(tui): stop live-region output duplicating on terminal resize
VERDICT: PASS (interim)
CI Status
- Lint: in_progress
- CodeQL: in_progress
Diff Review
- Replaces debounced width-effect with direct
stdout'resize'listener + leading/trailing throttle (80ms) — correct approach for Ink's live-region redraw bug - No-op resize guard prevents unnecessary repaints; listener cleanup on unmount prevents leaks
- Tests rewritten (6 cases) covering mount, leading edge, burst throttle, no-op, height-only, unmount
Observations
- LOW: Visual fix requires real-TTY verification (noted by author); wiring/throttle tests are solid
- INFO:
RESIZE_THROTTLE_MS=80is tunable if flicker is observed
CI is still settling — the approve-on-green policy will promote this to APPROVED once all checks pass.
— Quinn, QA Engineer
|
Submitted COMMENT review on #469. |
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. |
Problem
Resizing the terminal duplicates the live region — the sticky task list, composer, and status bar — over and over into scrollback (see the stacked "N tasks (…)" blocks in the report). Only a manual clear (Cmd+K) recovers.
This is distinct from #467, which fixed the
<Static>history reflow. This one is the live (Ink-managed) region.Root cause
Ink redraws its live region by erasing the previous frame using
log-update's stored line count (ink/build/index.js→throttledLog). On resize (resized → onRender), the terminal has already reflowed that frame to a different physical height, soeraseLines(storedCount)erases the wrong number of lines — the stale frame survives and is pushed into scrollback. One duplicate per resize event, so a continuous drag produces an avalanche.refreshStatic()(externalclearTerminal+<Static>remount → clean full reprint) fixes it, but #467 called it on a trailing-only debounce, which never fires during a continuous drag — so the avalanche builds until you stop.Fix
Drive
refreshStatic()from a directstdout'resize'listener with a leading + trailing throttle (RESIZE_THROTTLE_MS = 80):stdoutdirectly (notuseTerminalSizeReact state) so the handler runs synchronously on the event, immune to React batching.This subsumes the
<Static>reflow #467 added (refreshStatic handles both), so the debounced width-effect is removed.Tests
DefaultAppLayout.test.tsxrewritten (6 cases): no repaint on mount; immediate repaint on the leading edge; a burst throttles to leading + one trailing; no-op resizes ignored; height-only changes repaint; listener removed on unmount.Typecheck / ESLint / Prettier clean.
Terminal-resize duplication is a real-TTY behavior —
ink-testing-libraryuses a fake stdout with no real cursor/scrollback, so neither unit tests nor a headless run can observe it. The wiring/throttle is unit-tested, but the visual fix needs confirmation in a real terminal drag. There's a UX trade-off: frequent clears during a drag may flicker;RESIZE_THROTTLE_MScan be tuned if it's too aggressive or not aggressive enough.🤖 Generated with Claude Code