mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
fix: prevent duplicate terminal output on tab switch to busy sessions
Two fixes for the tab-switching corruption bug: 1. _finishBufferLoad() now discards queued SSE events instead of flushing them. The loaded API buffer is the source of truth — queued events overlap with it, and flushing them writes duplicate Ink cursor-up redraws that corrupt the terminal display (garbled text, wrong cursor positions). 2. Skip stale cache write for busy sessions. When a session is actively working, the cache is always outdated — writing it first and then rewriting with the fresh API buffer caused a jarring double-render flash. Now busy sessions get a single clean clear+write transition. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+15
-7
@@ -2153,11 +2153,14 @@ class CodemanApp {
|
||||
// canvas may be at stale dimensions — content would render at wrong width.
|
||||
if (this.fitAddon) this.fitAddon.fit();
|
||||
|
||||
// Instant cache restore — show previous buffer via chunked write to avoid WebGL GPU stalls.
|
||||
// Direct terminal.write() of large cached buffers (256KB+) can block the main thread
|
||||
// for 5+ seconds while the WebGL renderer processes ReadPixels synchronously.
|
||||
// Instant cache restore for IDLE sessions only.
|
||||
// For busy sessions, the cache is always stale — writing it first causes a
|
||||
// jarring double-render: stale content appears, then the terminal flashes
|
||||
// blank and rewrites with fresh data. Skip the cache and write the fresh
|
||||
// buffer once for a single clean transition.
|
||||
const cachedBuffer = this.terminalBufferCache.get(sessionId);
|
||||
if (cachedBuffer) {
|
||||
const sessionIsBusy = session && (session.status === 'busy' || session.status === 'working');
|
||||
if (cachedBuffer && !sessionIsBusy) {
|
||||
_crashDiag.log(`CACHE_WRITE: ${(cachedBuffer.length/1024).toFixed(0)}KB`);
|
||||
this.terminal.clear();
|
||||
this.terminal.reset();
|
||||
@@ -2165,6 +2168,11 @@ class CodemanApp {
|
||||
if (selectGen !== this._selectGeneration) { if (this._isLoadingBuffer) this._finishBufferLoad(); this._restoringFlushedState = false; return; }
|
||||
this.terminal.scrollToBottom();
|
||||
_crashDiag.log('CACHE_DONE');
|
||||
} else if (sessionIsBusy) {
|
||||
// Clear stale content immediately — fresh buffer is being fetched
|
||||
this.terminal.clear();
|
||||
this.terminal.reset();
|
||||
_crashDiag.log('CACHE_SKIP_BUSY');
|
||||
}
|
||||
|
||||
_crashDiag.log('FETCH_START');
|
||||
@@ -2206,9 +2214,9 @@ class CodemanApp {
|
||||
this.terminal.reset();
|
||||
}
|
||||
|
||||
// Buffer load complete — unblock live SSE writes and flush any queued events.
|
||||
// chunkedTerminalWrite calls _finishBufferLoad internally, but if we skipped
|
||||
// the chunked write (small buffer, cache hit, or empty), we must call it here.
|
||||
// Buffer load complete — unblock live SSE writes (queued events are discarded
|
||||
// to prevent duplicate content). chunkedTerminalWrite calls _finishBufferLoad
|
||||
// internally, but if we skipped the write (cache hit or empty), call it here.
|
||||
if (this._isLoadingBuffer) {
|
||||
this._finishBufferLoad();
|
||||
}
|
||||
|
||||
@@ -1109,18 +1109,18 @@ Object.assign(CodemanApp.prototype, {
|
||||
},
|
||||
|
||||
/**
|
||||
* Complete a buffer load: unblock live SSE writes and flush any queued events.
|
||||
* Complete a buffer load: unblock live SSE writes.
|
||||
* Called when chunkedTerminalWrite finishes (or is skipped for empty buffers).
|
||||
*
|
||||
* Queued SSE events are DISCARDED, not flushed. The loaded buffer from the API
|
||||
* is the source of truth up to the response timestamp. SSE events queued during
|
||||
* the fetch+write overlap with the buffer — flushing them writes duplicate data
|
||||
* (especially Ink cursor-up redraws), corrupting the terminal display.
|
||||
* After unblocking, new SSE/WS events deliver subsequent output normally.
|
||||
*/
|
||||
_finishBufferLoad() {
|
||||
const queue = this._loadBufferQueue;
|
||||
this._isLoadingBuffer = false;
|
||||
this._loadBufferQueue = null;
|
||||
if (queue && queue.length > 0) {
|
||||
for (const data of queue) {
|
||||
this.batchTerminalWrite(data);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user