From 23ad2e95ce4d7bc9f01a415753311785c34d6fcf Mon Sep 17 00:00:00 2001 From: krux Date: Wed, 16 Sep 2026 01:47:15 +0200 Subject: [PATCH] fix(term): defer controller resync until terminal data is loaded handleTermData drops xterm.js replies until initTerminal has loaded the initial terminal data, but the "initial resize" resync started the shell before that. fish sends a Primary Device Attribute (DA1) query at startup, never got the reply, and waited 10s before printing a warning on every new tab. Hold resync requests made before loading finishes and run the latest one once the terminal is loaded. Fixes #3162 Co-Authored-By: Claude Opus 5 --- frontend/app/view/term/termwrap.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/app/view/term/termwrap.ts b/frontend/app/view/term/termwrap.ts index d10b600459..15680b1b82 100644 --- a/frontend/app/view/term/termwrap.ts +++ b/frontend/app/view/term/termwrap.ts @@ -89,6 +89,7 @@ export class TermWrap { heldData: Uint8Array[]; handleResize_debounced: () => void; hasResized: boolean; + pendingResyncReason: string; multiInputCallback: (data: string) => void; sendDataHandler: (data: string) => void; onSearchResultsDidChange?: (result: { resultIndex: number; resultCount: number }) => void; @@ -437,6 +438,11 @@ export class TermWrap { await this.loadInitialTerminalData(); } finally { this.loaded = true; + if (this.pendingResyncReason != null) { + const reason = this.pendingResyncReason; + this.pendingResyncReason = null; + fireAndForget(() => this.resyncController(reason)); + } } this.runProcessIdleTimeout(); } @@ -553,6 +559,11 @@ export class TermWrap { } async resyncController(reason: string) { + if (!this.loaded) { + // replies to the shell's startup queries (e.g. fish's DA1) are dropped until loaded + this.pendingResyncReason = reason; + return; + } dlog("resync controller", this.blockId, reason); const rtOpts: RuntimeOpts = { termsize: { rows: this.terminal.rows, cols: this.terminal.cols } }; try {