From a5209e08ae00bb795bd58e7d521a874acf6cdc20 Mon Sep 17 00:00:00 2001 From: megaproxy Date: Fri, 22 May 2026 21:53:34 +0100 Subject: [PATCH] Debounce PTY resize during drag to stop SIGWINCH-spam corrupting prompts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What the user saw: dragging a gutter filled the affected panes with many overlapping bash prompts, some corrupted mid-print (megaproxy@DESKTOP-megaproxy@DESKTOP-SSAQG5 etc). Root cause: every resizePane() call sends SIGWINCH to the shell, which makes bash redraw its prompt. The previous fix coalesced the local xterm fit() into one per rAF, but still fired resizePane on every rAF — 60+ SIGWINCHes per second during a drag, faster than bash can finish one prompt redraw before the next interrupts it. Fix: separate the two concerns. fit() + term.refresh() still run every rAF (the visual must stay smooth). But resizePane() is debounced to fire 150 ms after the LAST rAF — i.e. only when you stop dragging — so bash gets one clean SIGWINCH at the final size and produces a single tidy prompt redraw. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/XtermPane.tsx | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/components/XtermPane.tsx b/src/components/XtermPane.tsx index 3a6594b..242fa27 100644 --- a/src/components/XtermPane.tsx +++ b/src/components/XtermPane.tsx @@ -165,12 +165,16 @@ export default function XtermPane({ if (ta) ta.addEventListener("focus", () => onFocusRef.current?.(), true); } - // Re-fit on container resize; forward new size to the PTY. We - // coalesce multiple ResizeObserver firings into one rAF so a single - // gutter drag tick = one fit/resize/repaint, then explicitly call - // term.refresh() so the DOM renderer fully repaints (without this, - // shrinking a pane sometimes leaves stale glyphs in the freed rows). + // Re-fit on container resize. xterm.fit() + a forced refresh run + // immediately (visual must stay smooth during a drag), but the + // actual PTY resize call is debounced: every SIGWINCH makes bash + // redraw the prompt, and if we send 60+ of them per second during a + // gutter drag, the redraws corrupt each other and the terminal + // fills with garbled half-prompts. The debounce means the PTY + // hears about resizes ~150 ms after you stop dragging, at the + // final size — bash gets a single clean redraw. let resizeRaf: number | null = null; + let resizePtyTimer: number | null = null; ro = new ResizeObserver(() => { if (resizeRaf != null) return; resizeRaf = requestAnimationFrame(() => { @@ -178,10 +182,14 @@ export default function XtermPane({ if (!term) return; try { fit.fit(); - if (paneId != null) { - void resizePane(paneId, term.cols, term.rows); - } term.refresh(0, term.rows - 1); + if (resizePtyTimer != null) clearTimeout(resizePtyTimer); + resizePtyTimer = window.setTimeout(() => { + resizePtyTimer = null; + if (paneId != null && term) { + void resizePane(paneId, term.cols, term.rows); + } + }, 150); } catch (e) { console.warn("resize failed", e); }