diff --git a/CHANGELOG.md b/CHANGELOG.md index a990d204..66f29e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## [Unreleased] +### Fixed + +- Visible but unfocused chat windows now still attempt the immediate SSE reconnect for the current session; only a real session switch skips the reconnect path. (Refs #3040) + ## [v0.51.152] — 2026-05-28 — Release DX (stage-batch34 — single-PR optional gateway-backed browser chat) ### Added diff --git a/static/messages.js b/static/messages.js index c2195150..5884dfad 100644 --- a/static/messages.js +++ b/static/messages.js @@ -2155,7 +2155,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){ // If the user has switched to a different session, don't attempt to // reconnect — the old stream's EventSource was closed intentionally // during session switch and reconnecting would leak a background stream. - if(!_isSessionActivelyViewed(activeSid)) return; + if(!_isSessionCurrentPane(activeSid)) return; if(_terminalStateReached || _streamFinalized){ return; } diff --git a/tests/test_issue3040_reattach_focus_gate.py b/tests/test_issue3040_reattach_focus_gate.py new file mode 100644 index 00000000..f860d28b --- /dev/null +++ b/tests/test_issue3040_reattach_focus_gate.py @@ -0,0 +1,37 @@ +"""Regression coverage for #3040 item 4: visible-but-unfocused stream reattach.""" +from __future__ import annotations + +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +MESSAGES_JS = (REPO_ROOT / "static" / "messages.js").read_text(encoding="utf-8") + + +def _chat_error_handler() -> str: + return MESSAGES_JS.split("source.addEventListener('error',async e=>{", 1)[1].split( + "source.addEventListener('cancel'", 1 + )[0] + + +def test_visible_unfocused_current_session_still_attempts_sse_reconnect(): + """The immediate chat SSE error path should only require current-pane ownership. + + `_isSessionActivelyViewed()` also gates on document focus/visibility. Hidden tabs + are already deferred by `_deferStreamErrorIfPageHidden(source)`, so the direct + reconnect branch must not skip a visible-but-unfocused current session. + """ + handler = _chat_error_handler() + assert "if(!_isSessionCurrentPane(activeSid)) return;" in handler + assert "if(!_isSessionActivelyViewed(activeSid)) return;" not in handler + assert handler.index("if(!_isSessionCurrentPane(activeSid)) return;") < handler.index( + "if(!_reconnectAttempted && streamId)" + ) + + +def test_reconnect_gate_comment_matches_current_pane_contract(): + handler = _chat_error_handler() + guard_idx = handler.index("if(!_isSessionCurrentPane(activeSid)) return;") + comment_window = handler[max(0, guard_idx - 260):guard_idx] + assert "different session" in comment_window + assert "visible" not in comment_window.lower() + assert "focused" not in comment_window.lower()