From 9827b016884f2ae8879872f395cad821e78f81f9 Mon Sep 17 00:00:00 2001 From: megaproxy Date: Fri, 22 May 2026 22:12:06 +0100 Subject: [PATCH] Skip PTY resize when cols/rows didn't actually change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most ResizeObserver firings in XtermPane don't change xterm's cell grid — just shuffle a few pixels around. We were still calling resizePane() (which SIGWINCHs bash, which redraws its prompt, which emits data, which resets the idle timer). With many panes, this created a self-reinforcing flap loop where the idle indicator would toggle every few seconds. Now: track the last cols/rows we actually sent to the backend in the mount effect's closure. If a debounced fit() ends up at the same grid dimensions, skip the resizePane call entirely. No SIGWINCH, no prompt redraw, no data event, no idle flap. Zero functional change for actual resizes; only suppresses redundant PTY syscalls. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/XtermPane.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/XtermPane.tsx b/src/components/XtermPane.tsx index 242fa27..910a3de 100644 --- a/src/components/XtermPane.tsx +++ b/src/components/XtermPane.tsx @@ -175,6 +175,8 @@ export default function XtermPane({ // final size — bash gets a single clean redraw. let resizeRaf: number | null = null; let resizePtyTimer: number | null = null; + let lastSentCols = -1; + let lastSentRows = -1; ro = new ResizeObserver(() => { if (resizeRaf != null) return; resizeRaf = requestAnimationFrame(() => { @@ -186,9 +188,17 @@ export default function XtermPane({ if (resizePtyTimer != null) clearTimeout(resizePtyTimer); resizePtyTimer = window.setTimeout(() => { resizePtyTimer = null; - if (paneId != null && term) { - void resizePane(paneId, term.cols, term.rows); + if (paneId == null || !term) return; + // Skip if the cell grid didn't actually change — saves a + // pointless SIGWINCH that would make bash redraw its prompt + // (which feeds back into onDataReceived and causes the idle + // indicator to flap; see the analysis around v0.2.2). + if (term.cols === lastSentCols && term.rows === lastSentRows) { + return; } + lastSentCols = term.cols; + lastSentRows = term.rows; + void resizePane(paneId, term.cols, term.rows); }, 150); } catch (e) { console.warn("resize failed", e);