refactor(render): remove the resize-settle hold and sweep transition - #353
Merged
Conversation
Issue: The sweeping resize animation fired constantly with no resize involved — scrolling a Claude transcript in full view replayed a pixelated, upscaled frame followed by the sweep, twice, before stopping. Ghostty resizes the same long-history sessions instantly with no such machinery, which called the whole hold/freeze/settle approach into question. Solution: The hold froze the pre-resize texture until output stayed silent for 400 ms, then swept the new layout in over 1400 ms — roughly 1.8 s of animation per grid/full toggle to conceal a repaint that Claude finishes in about 30 ms. Its re-arm heuristic treated any 2 KB drain within ~4.4 s of a resize as a further repaint wave, which ordinary scroll output trips, since chunk size cannot distinguish an agent redrawing from a user scrolling. Architect's VT resize already mirrors Ghostty's Termio.resize call for call, so the repaint now renders live exactly as it does there; an agent that leaves garbage mid-resize and converges once settled is exhibiting its own behavior, not something the terminal should hide.
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.
Solution
Every terminal resize used to start a resize-settle hold: the renderer froze the pre-resize cached texture until the session's output stayed silent for
resize_settle_quiet_ms(400 ms), then revealed the new layout with aresize_settle_transition_ms(1400 ms) diagonal sweep. This PR deletes that subsystem and renders the post-SIGWINCH repaint live, the way Ghostty does.Why it had to go
It fired when nothing was resized. A debug log captured while scrolling a Claude transcript in full view shows the hold re-engaging twice on plain scroll output:
Each re-arm redisplayed the stale grid-sized (
122x16) texture stretched over the full-screen rect — the "pixelated expanded view" — then swept again. One view toggle produced ~5 s of frozen-and-sweeping content. It stopped only becauseresize_settle_max_rearms(2) was exhausted.The re-arm predicate was "a drain call of ≥2 KB within the 1400 ms sweep or 3000 ms after it." Chunk size cannot distinguish an agent redrawing after a resize from a user scrolling, and in Full view wheel events are forwarded to the app as SGR mouse escapes whenever it has mouse tracking on (Claude does), so scrolling reliably trips it.
It was expensive even when working correctly. Every non-re-armed release in that log landed at 433–483 ms — the 400 ms silence floor plus a few ms. Claude's entire repaint completes in ~30 ms, then the code spends 400 ms waiting for silence it already had and 1400 ms animating. That is ~1.8 s of mandatory animation per grid↔full toggle to hide something already finished.
The VT layer never needed help.
applyTerminalResizeinapp/layout.zigmirrors Ghostty'sTermio.resizecall for call — sameTerminal.resize, same pixel-size update, samesynchronized_outputclear, same DEC 2048 in-band report. Ghostty carries no hold, freeze, or sweep anywhere inTermio.zig,Surface.zig, orrenderer/, and resizes long-history sessions instantly. The multi-second repaint the feature was built for is codex-specific; an agent that leaves garbage mid-resize and converges once settled is exhibiting its own behavior, which the terminal should show rather than conceal.What was removed
session/state.zig— nine tuning constants, theResizeSettleReleaseenum, sixSessionStatefields, eight methods, the re-arm hook in the PTY drain loop, and eleven tests.quietDurationMsstays; DEC 2026 synchronized output still uses it.render/renderer.zig—resizeSettleHoldsCache,maybeBeginSettleTransition,renderSettleTransition,renderResizeSettleHold,releaseTransitionTextures, the threetransition_*cache fields, the settle branch in the expand/collapse path, andanyDirty'snow_msparameter.gfx/shimmer.zig—drawSweepRevealwith itsrevealMarginandsweep_strip_hhelpers;sweepGeometryno longer takes a margin now that only the cyclic wait shimmer calls it. The quit-teardown overlay'sshimmer.drawis untouched.app/layout.zig/app/runtime.zig—applyTerminalResizedropsnow_ms, cascading through both layout wrappers, twelve call sites, and a deadRuntimeScaleChangeContext.nowfield.Deliberately kept: the DEC 2026 synchronized-output hold (
synchronizedOutputHoldsCache). It honors an explicit\e[?2026hfrom the application rather than guessing from silence, and Ghostty implements it too.Tests
The two
applyTerminalResizetests that asserted on settle state now assert the opposite guarantee — that a resize bumpsrender_epochso new content publishes immediately with nothing able to defer it. Verified non-tautological: commenting out themarkDirty()call makes it fail; restoring it makes it pass.zig build,zig build test(319 tests),just lint, andzig fmt --checkall pass.docs/ARCHITECTURE.mdnow records why Architect deliberately has no such mechanism, including the cost and the reason the re-arm heuristic is unfixable in principle, so this does not get reinvented.No linked issue — this came out of a debugging session rather than a filed report.
Test plan
Rendering behavior can't be checked by CI, so these need a human at the window:
gfx/shimmer.zig, which this PR modifies.