From 736f35da7fe7c2b087863a94880f6c7a0c4220f0 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Fri, 14 Aug 2026 00:54:56 +0200 Subject: [PATCH] fix(terminal): bail the backpressure refresh on a mid-fetch tab switch The refresh can now issue two fetches (full history, then the tail as a downgrade fallback), which widens an existing window where the user switches tabs mid-flight and this session's history gets painted into the terminal they are now looking at. Guard it the way _maybeRefetchFullHistory already does. Co-Authored-By: Claude Opus 5 (1M context) --- src/web/public/app.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/web/public/app.js b/src/web/public/app.js index 1498e8cc..b1de02d4 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -2213,6 +2213,10 @@ class CodemanApp { res = await fetch(`/api/sessions/${sessionId}/terminal?tail=${TERMINAL_TAIL_SIZE}`); data = (await res.json())?.data ?? {}; } + // Bail on a tab switch mid-fetch: writing here would paint this session's + // history into the terminal the user is now looking at. The window is two + // fetches wide in the fallback case, so this guard is not optional. + if (this.activeSessionId !== sessionId) return; if (data.terminalBuffer) { // This refresh is SERVER-triggered, so a user quietly reading scrollback // did not ask for it and must not be dragged to the bottom by it (#259). @@ -2225,7 +2229,7 @@ class CodemanApp { await this.chunkedTerminalWrite(data.terminalBuffer); // A tail fetch can be partial, and the banner would otherwise keep // describing the pre-refresh buffer (#258). - if (this.activeSessionId === sessionId) this._setHistoryTruncation(sessionId, data); + this._setHistoryTruncation(sessionId, data); const target = computeRewriteScrollLine({ linesFromBottom, baseY: this.terminal.buffer?.active?.baseY ?? 0,