You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Terminal font zoom and window-resize bursts caused the interactive dashboard to repaint repeatedly. Even a simple debounce still allowed unrelated React updates during the quiet window to paint a stale-width frame before the final settled layout.
Root cause
Ink observed physical terminal dimensions immediately while CodeBurn separately rerendered after resize events. Width and height could therefore advance through different render paths, and state updates or refreshes could write an intermediate frame against dimensions the terminal had already left.
Change
Hold interactive terminal dimensions stable during resize bursts.
Suppress synchronized application frames while a resize is pending while preserving imperative terminal-control writes and write completion semantics.
Publish one coherent settled resize to Ink, useWindowSize, and the dashboard before requesting the final rerender.
Preserve mounted period, view, and logical viewport state.
Cancel pending delivery and remove all resize relays during teardown and render failures.
Normalize invalid terminal dimensions to safe defaults.
User impact
Zooming the terminal font or rapidly resizing the window now produces one final reflow after dimensions settle instead of repeated stale or partial dashboard redraws. The user remains at the same logical reading position and terminal modes are restored cleanly on exit.
Preservation and out of scope
Static and non-interactive output are unchanged.
Periodic refresh behavior is unchanged.
One final synchronized repaint after settlement remains expected.
Testing
Credible RED on the prior candidate: a state update during pending resize wrote an old 100×24 frame followed by a mixed 80×20 prop / 100×24 hook frame.
Focused real-Ink suites: 73/73.
Full root suite: 2,633 passed, 5 skipped.
Serial lock suite: 26/26.
Full desktop suite: 468/468.
Root, dashboard, and desktop TypeScript checks passed.
CLI, dashboard, and desktop production builds passed.
Exact-SHA independent spec and hostile standards reviews approved.
Real controlling-PTY storm across width/height breakpoints: zero pre-settle bytes, one BSU/ESU transaction, one complete final frame, zero later frames, and clean mouse/alternate-screen teardown.
Reviewed against current main (rebased locally; two trivial conflicts). The root-cause analysis is right — Ink registers its own resize handler and repaints per event, so a local debounce alone wouldn't fix #977 — and the frozen-dimensions + private resize emitter part is sound.
One regression that blocks merge as-is: the write() interception that suppresses frames during the burst can drop an update permanently. Ink dedupes at two layers (log-update and Ink.lastOutput); when the proxy swallows a frame, Ink.lastOutput still advances, so if the settled render produces the same string nothing is ever written. That happens whenever a burst nets to no dimension change while a state update lands in the quiet window — font zoom in then back out, drag-and-return, or a spurious SIGWINCH with unchanged dims (tmux/kitty emit these). Repro against the branch:
burst of 20 resizes → one paint at final width ✅
state update mid-burst, size changes → one frame, new data + width ✅
state update mid-burst, burst nets to no size change → zero bytes written after settle ❌
spurious resize (identical dims) + state update → zero bytes written ❌
no resize → identical to plain render() ✅
app.clear() before the settled rerender doesn't rescue it (Instance.clear() resets log-update's state but not Ink.lastOutput).
Suggested shape: drop the write() interception / finalFramePreamble / suppressingFrame / BSU-ESU handling; keep frozen columns/rows + the private resize emitter + dispose; on settle call app.rerender(...) (Ink recomputes Yoga from the facade's columns), plus app.clear() when the terminal shrank. Accepts one transient old-width frame in the rare state-update-during-burst case, never loses an update, and is ~120 lines smaller in src/ (the EventEmitter-facade fidelity surface — emit, setMaxListeners, eventNames, rawListeners… and its ~150 lines of tests — only Ink and useWindowSize consume this stream, via on/off). Please add the two ❌ cases as regression tests.
Rebase note: tests/dashboard.test.ts on main (#1020's connector test) still passes windowColumns: 120; with this PR's terminalSize rename it fails at runtime (tests aren't type-checked), so update that call when you rebase.
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
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.
Problem
Terminal font zoom and window-resize bursts caused the interactive dashboard to repaint repeatedly. Even a simple debounce still allowed unrelated React updates during the quiet window to paint a stale-width frame before the final settled layout.
Root cause
Ink observed physical terminal dimensions immediately while CodeBurn separately rerendered after resize events. Width and height could therefore advance through different render paths, and state updates or refreshes could write an intermediate frame against dimensions the terminal had already left.
Change
User impact
Zooming the terminal font or rapidly resizing the window now produces one final reflow after dimensions settle instead of repeated stale or partial dashboard redraws. The user remains at the same logical reading position and terminal modes are restored cleanly on exit.
Preservation and out of scope
Testing
Fixes #977.