mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +02:00
fix(sse): stop reloading the whole terminal when a reconnect lands on the same session
handleInit() did not distinguish a first load from an SSE reconnect: it always cleared the terminal caches in _resetAllAppState() and re-ran selectSession() for the session that was already on screen. Every reconnect therefore refetched up to 1 MiB of buffer and reset+rewrote xterm. On a link that drops a connection about once a minute (measured at ~57s intervals against a healthy server) that reads as the page refreshing itself and throwing away your reading position. A reconnect that lands back on the still-open session now keeps the terminal caches and activeSessionId and resyncs through _onSessionNeedsRefresh(). That path still reloads the buffer, so output produced during the outage is not lost, but it preserves distance-from-bottom — the same rule #259 established for a refresh the server triggered rather than the user. The WS is reconnected explicitly when it is not already on that session, since skipping selectSession() skips its _connectWs() call. First load (gen === 1) takes exactly the path it took before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rv24Pk4qzrsDYdVyDyJQmT
This commit is contained in:
+48
-13
@@ -3535,13 +3535,20 @@ class CodemanApp {
|
|||||||
* Reset all app state maps, timers, and handlers to a clean baseline.
|
* Reset all app state maps, timers, and handlers to a clean baseline.
|
||||||
* Called by handleInit() on SSE reconnect / page reload to prevent
|
* Called by handleInit() on SSE reconnect / page reload to prevent
|
||||||
* memory leaks and stale data.
|
* memory leaks and stale data.
|
||||||
|
*
|
||||||
|
* @param {boolean} [preserveTerminal] Keep the terminal caches. Set when an SSE
|
||||||
|
* RECONNECT lands back on the session already on screen: the buffers still
|
||||||
|
* describe that session, and dropping them forces a full refetch + xterm
|
||||||
|
* reset that throws away the user's scroll position (see handleInit).
|
||||||
*/
|
*/
|
||||||
_resetAllAppState() {
|
_resetAllAppState(preserveTerminal = false) {
|
||||||
this.sessions.clear();
|
this.sessions.clear();
|
||||||
this.ralphStates.clear();
|
this.ralphStates.clear();
|
||||||
this.terminalBuffers.clear();
|
if (!preserveTerminal) {
|
||||||
this.terminalBufferCache.clear();
|
this.terminalBuffers.clear();
|
||||||
this._xtermSnapshots?.clear();
|
this.terminalBufferCache.clear();
|
||||||
|
this._xtermSnapshots?.clear();
|
||||||
|
}
|
||||||
this.projectInsights.clear();
|
this.projectInsights.clear();
|
||||||
this.teams.clear();
|
this.teams.clear();
|
||||||
this.teamTasks.clear();
|
this.teamTasks.clear();
|
||||||
@@ -3668,7 +3675,23 @@ class CodemanApp {
|
|||||||
// Stop any active voice recording on reconnect
|
// Stop any active voice recording on reconnect
|
||||||
VoiceInput.cleanup();
|
VoiceInput.cleanup();
|
||||||
|
|
||||||
this._resetAllAppState();
|
// A RECONNECT that lands back on the same session must not become a full
|
||||||
|
// reload. This used to clear the terminal caches and re-run selectSession()
|
||||||
|
// unconditionally, so every SSE reconnect refetched the buffer (up to 1 MiB)
|
||||||
|
// and reset+rewrote xterm. On a link that drops a connection about once a
|
||||||
|
// minute that reads as the page refreshing itself and losing your place.
|
||||||
|
// Keep the caches and the active id here; the restore block below resyncs
|
||||||
|
// through _onSessionNeedsRefresh(), which still reloads the buffer (so
|
||||||
|
// output produced during the outage is not lost) but preserves the reading
|
||||||
|
// position.
|
||||||
|
const activeBefore = this.activeSessionId;
|
||||||
|
const keepTerminal =
|
||||||
|
gen > 1 &&
|
||||||
|
!!activeBefore &&
|
||||||
|
Array.isArray(data.sessions) &&
|
||||||
|
data.sessions.some((s) => s.id === activeBefore);
|
||||||
|
|
||||||
|
this._resetAllAppState(keepTerminal);
|
||||||
|
|
||||||
data.sessions.forEach(s => {
|
data.sessions.forEach(s => {
|
||||||
this.sessions.set(s.id, s);
|
this.sessions.set(s.id, s);
|
||||||
@@ -3798,20 +3821,32 @@ class CodemanApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const previousActiveId = this.activeSessionId;
|
const previousActiveId = this.activeSessionId;
|
||||||
this.activeSessionId = null;
|
if (this.sessionOrder.length === 0) {
|
||||||
if (this.sessionOrder.length > 0) {
|
this.activeSessionId = null;
|
||||||
|
} else {
|
||||||
// Priority: current active > localStorage > first session
|
// Priority: current active > localStorage > first session
|
||||||
let restoreId = previousActiveId;
|
let restoreId = previousActiveId;
|
||||||
if (!restoreId || !this.sessions.has(restoreId)) {
|
if (!restoreId || !this.sessions.has(restoreId)) {
|
||||||
try { restoreId = localStorage.getItem('codeman-active-session'); } catch {}
|
try { restoreId = localStorage.getItem('codeman-active-session'); } catch {}
|
||||||
}
|
}
|
||||||
// `auto`: the app is restoring a session on load, not a human opening
|
if (keepTerminal && restoreId === previousActiveId && this.sessions.has(restoreId)) {
|
||||||
// one, so a pending idle alert on that tab stays armed until it is
|
// Reconnect onto the session already on screen. renderSessionTabs() ran
|
||||||
// actually tapped (see the userInitiated note in selectSession).
|
// above and activeSessionId never changed, so the tab strip is already
|
||||||
if (restoreId && this.sessions.has(restoreId)) {
|
// correct; only the buffer needs to catch up. The WS has its own
|
||||||
this.selectSession(restoreId, { auto: true });
|
// backoff reconnect, but if it is not on this session (dead socket, or
|
||||||
|
// a give-up) nothing else would re-establish it from here.
|
||||||
|
if (this._wsSessionId !== restoreId) this._connectWs(restoreId);
|
||||||
|
void this._onSessionNeedsRefresh({ id: restoreId });
|
||||||
} else {
|
} else {
|
||||||
this.selectSession(this.sessionOrder[0], { auto: true });
|
this.activeSessionId = null;
|
||||||
|
// `auto`: the app is restoring a session on load, not a human opening
|
||||||
|
// one, so a pending idle alert on that tab stays armed until it is
|
||||||
|
// actually tapped (see the userInitiated note in selectSession).
|
||||||
|
if (restoreId && this.sessions.has(restoreId)) {
|
||||||
|
this.selectSession(restoreId, { auto: true });
|
||||||
|
} else {
|
||||||
|
this.selectSession(this.sessionOrder[0], { auto: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user