fix(tui): avoid destructive redraw on focus regain - #88596
Conversation
|
Superseded by #89623. Your report is right and the flicker is real — I reproduced it: The one thing I changed is where the fix lands. Dropping the clear also drops the healing this handler was added for (#86332): when an emulator throttles hidden-tab output, Ink's virtual frame can think a row is already blank while the physical screen still shows the old status text, and the cell diff skips blank-over-blank — so the stale row stays. Replaying the emitted ANSI into a terminal model, the stale row survives on both alt and main screen on this branch. #89623 keeps your commit and your framing, and fixes the flicker the other way: the clear stays but stops being its own write, queued via Thanks for finding this — the atomicity gap was real and had been sitting in both screens. |
Two defects from the review of the previous commit. Scrollback erase leaked to focus regain. Reusing needsEraseBeforePaint routed focus-in through the same erase selection as resize healing, whose heuristic is TERM_PROGRAM == 'Apple_Terminal' — so on Apple Terminal an ordinary tab or pane switch emitted CSI 3J and wiped the user's scrollback. That erase exists to clear alt-screen reflow artifacts after a resize, which is the only case worth discarding history for. Track the deep erase behind its own flag, set only by resize healing; every other requester gets 2J. Terminal modes were never re-asserted. #88596 dropped reassertTerminalModes(false) from the focus path and the previous commit did not restore it, leaving one caller (onStdinResume). An emulator that cleared the DEC mouse modes while the pane was hidden then stayed dead until the DECRQM watchdog's next 2s probe. Restore the non-destructive call — extended keys plus mouse preset, no alt-screen re-entry, no erase — so it costs a few idempotent bytes and no flicker. Both are covered: reverting either fix fails its test. The mode assertion is checked on the alt screen only, since mouse tracking is alt-screen-scoped and reassertTerminalModes returns early on main. Reported-by: Copilot
…ic-repaint fix(tui): heal focus regain without a separate screen clear (supersedes #88596)
…-regain-atomic-repaint fix(tui): heal focus regain without a separate screen clear (supersedes NousResearch#88596)
What does this PR do?
Makes terminal focus recovery repaint from blank virtual frame buffers without clearing the physical screen or re-asserting terminal modes.
The focus-regain handler previously reused the destructive
/redrawpath. Every focus change sentERASE_SCREEN, resent mouse-tracking DECRESET sequences, and then rebuilt the frame. That caused a visible full-screen redraw and introduced unnecessary terminal-mode output on ordinary tab or pane changes.The replacement keeps the stale-row recovery: it resets the virtual buffers and renders a complete replacement frame. The existing screen remains visible until that frame is ready, and focus regain sends neither the clear-screen sequence nor mouse-mode resets.
Related Issue
Discord support report: https://discord.com/channels/1053877538025386074/1538966610776301719
Type of Change
Changes Made
reassertTerminalModes()plusforceRedraw()calls with a virtual frame reset and direct repaint.ERASE_SCREENorDISABLE_MOUSE_TRACKING.How to Test
npm test -- packages/hermes-ink/src/ink/ink-focus-redraw.test.tsfromui-tui.npm run typecheckfromui-tui.l.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AScreenshots / Logs
Focused Vitest regression, TUI TypeScript check, and targeted ESLint all pass locally. Python tests were not run because this is a TypeScript-only change.