fix(cli): replace clearTerminal with targeted repaint on resize (#3967)
Some checks are pending
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Waiting to run
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run

Add repaintStaticViewport() that uses cursorTo(0,0)+eraseDown instead
of the full clearTerminal (ESC[2J ESC[3J ESC[H]) sequence. Hook it to
terminal width changes via a new useEffect that guards against no-op
repaints when width is unchanged.

clearTerminal remains in use for explicit refreshStatic() calls (model
switches, /clear, etc.) where a full history remount is intended.
previousTerminalWidthRef ensures the resize effect only fires on actual
column changes, not height-only resizes.

Closes the full-screen flash that occurs on every terminal width change.

Generated with AI

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
ChiGao 2026-05-09 18:27:03 +08:00 committed by GitHub
parent 8255027426
commit 4bab7a1ad6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -374,6 +374,7 @@ export const AppContainer = (props: AppContainerProps) => {
// Terminal and layout hooks
const { columns: terminalWidth, rows: terminalHeight } = useTerminalSize();
const previousTerminalWidthRef = useRef(terminalWidth);
const { stdin, setRawMode } = useStdin();
const { stdout } = useStdout();
@ -570,6 +571,13 @@ export const AppContainer = (props: AppContainerProps) => {
remountStaticHistory();
}, [remountStaticHistory, stdout]);
// Targeted repaint for resize events: move cursor to top-left and erase
// downward instead of a full clearTerminal, avoiding the full-screen flash.
const repaintStaticViewport = useCallback(() => {
stdout.write(`${ansiEscapes.cursorTo(0, 0)}${ansiEscapes.eraseDown}`);
remountStaticHistory();
}, [remountStaticHistory, stdout]);
// Keep the static header in sync with model changes without polling.
// Ink's <Static> output is append-only, so model changes must explicitly
// clear and remount the static region to redraw the banner at the top.
@ -1765,6 +1773,14 @@ export const AppContainer = (props: AppContainerProps) => {
}
}, [terminalWidth, availableTerminalHeight, activePtyId]);
useEffect(() => {
if (previousTerminalWidthRef.current === terminalWidth) {
return;
}
previousTerminalWidthRef.current = terminalWidth;
repaintStaticViewport();
}, [terminalWidth, repaintStaticViewport]);
useEffect(() => {
if (ideNeedsRestart) {
// IDE trust changed, force a restart.