fix(sessions): reset busy/stream flags before the visit-ack repaint (gate)

Gate finding: the idle-session S.busy/S.activeStreamId reset ran AFTER
_acknowledgeSessionVisit(), so the ack's sidebar repaint could inherit the
PREVIOUS session's busy state and manufacture a phantom unread/streaming
indicator on the freshly-loaded idle session. Moved the activeStreamId calc +
idle reset BEFORE the acknowledge call. Co-authored-by: neaucode-bot.
This commit is contained in:
nesquena-hermes committed 2026-07-11 05:52:56 +00:00
1 parent 05f6e8c7f4
commit cb94cecc1a
1 file changed
+24 -20
+24 -20
View File
@@ -1779,6 +1779,30 @@ async function loadSession(sid){
if(typeof syncTopbar==='function') syncTopbar();
// Acknowledge the visit as soon as the session metadata is accepted for the
// in-flight load: clears the viewed count + any stale completion-unread marker
// `let` (not const): re-read below, after the awaited _ensureMessagesLoaded,
// so a server_turn_started that attaches a live stream MID-RELOAD is honored
// by the attach/idle decision instead of being clobbered by the stale snapshot.
let activeStreamId=S.session.active_stream_id||null;
// If the server says the session is idle, reset browser-side streaming flags
// NOW — BEFORE _acknowledgeSessionVisit() below (whose sidebar repaint would
// otherwise inherit the PREVIOUS session's busy/stream state) and before the
// async _ensureMessagesLoaded gap. Without this, S.busy can remain true from a
// still-running stream in the PREVIOUS session while S.session.session_id has
// already advanced to the new one. _isSessionLocallyStreaming() checks
// (isActive && S.busy), so the new session would appear locally-streaming
// (sidebar spinner, Stop button, thinking state on an idle chat) and the visit
// repaint would manufacture a phantom unread. Also clears stale INFLIGHT
// entries left behind by a crashed/restarted stream. (#5917 gate: reset must
// precede the acknowledge repaint.)
if(!activeStreamId){
S.activeStreamId=null;
S.busy=false;
if(INFLIGHT[sid]){
delete INFLIGHT[sid];
if(typeof clearInflightState==='function') clearInflightState(sid);
}
}
// and syncs the polling snapshot so a deferred /api/sessions poll landing
// during the async message-load gap below cannot re-flag a stale unread dot.
_acknowledgeSessionVisit(
@@ -1790,26 +1814,6 @@ async function loadSession(sid){
_setActiveSessionUrl(S.session.session_id);
if(typeof startSessionStream==='function') startSessionStream(S.session.session_id);
// `let` (not const): re-read below, after the awaited _ensureMessagesLoaded,
// so a server_turn_started that attaches a live stream MID-RELOAD is honored
// by the attach/idle decision instead of being clobbered by the stale snapshot.
let activeStreamId=S.session.active_stream_id||null;
// If the server says the session is idle, reset browser-side streaming flags
// NOW — before the async _ensureMessagesLoaded gap below. Without this,
// S.busy can remain true from a still-running stream in the PREVIOUS session
// while S.session.session_id has already advanced to the new one.
// _isSessionLocallyStreaming() checks (isActive && S.busy), so during the
// async window the new session would appear locally-streaming (sidebar spinner,
// Stop button, thinking state on an idle chat). Also clears stale INFLIGHT
// entries left behind by a crashed/restarted stream.
if(!activeStreamId){
S.activeStreamId=null;
S.busy=false;
if(INFLIGHT[sid]){
delete INFLIGHT[sid];
if(typeof clearInflightState==='function') clearInflightState(sid);
}
}
function _mergePendingSessionMessage(session,messages){
if(!Array.isArray(messages)) return false;