Release v0.51.291 — Release JG (stage-s2 — preserve live turn content on switch-away #3668) (#3704)
Some checks failed
Release & Docker / release (push) Has been cancelled

* fix(#3668): snapshot live turn before stream teardown on session switch

The 'stays gone' variant: switching away from a streaming session during a quiet
window (mid tool-exec / silent thinking, between content SSE events) left a
stale/absent live-turn snapshot, so restoreLiveTurnHtmlForSession() failed on
switch-back and loadSession()'s fallback rebuilt with an empty appendThinking(),
permanently losing streamed thinking/tool content (only the elapsed clock
survived). closeLiveStream() now snapshots the live-turn DOM via
snapshotLiveTurnHtmlForSession(sessionId) BEFORE closing the source + tearing
down LIVE_STREAMS, so switch-back always restores the exact state shown at
switch-away. + regression test asserting snapshot precedes teardown.

* docs(changelog): v0.51.291 — Release JG (stage-s2, #3668)

---------

Co-authored-by: nesquena-hermes <[email protected]>
This commit is contained in:
nesquena-hermes
2026-06-05 22:26:43 -07:00
committed by GitHub
parent 47020925d5
commit 534280e058
3 changed files with 55 additions and 0 deletions

View File

@@ -3,6 +3,11 @@
## [Unreleased]
## [v0.51.291] — 2026-06-06 — Release JG (stage-s2 — preserve live turn content when switching away mid-stream)
### Fixed
- **Switching away from a streaming session no longer loses the in-progress thinking/tool content.** When you clicked to another chat while a session was streaming during a quiet window (mid tool-execution or silent reasoning, between content events) and then switched back, the live turn's tool cards and thinking could disappear permanently — only the elapsed-time clock survived — until the response finished and the transcript re-rendered from the server. Cause: the live-turn DOM snapshot was only captured on content/`tool_complete` SSE events, so the switch-away teardown could run with a stale-or-absent snapshot, and the switch-back fallback rebuilt an empty thinking card. `closeLiveStream()` now snapshots the live turn **before** tearing the stream down, so switching back restores the exact state shown at switch-away. (#3668)
## [v0.51.290] — 2026-06-06 — Release JF (stage-s1 — profile provider/model now respected in session resolution)
### Fixed

View File

@@ -787,6 +787,15 @@ function closeLiveStream(sessionId, streamId, source){
if(!live) return;
if(streamId&&live.streamId!==streamId) return;
if(source&&live.source!==source) return;
// Snapshot the current live-turn DOM BEFORE tearing the stream down. The
// per-event snapshot (snapshotLiveTurn) only fires on content/tool_complete
// SSE events, so switching away during a quiet window (mid tool-exec, silent
// thinking) would leave a stale-or-absent snapshot — on switch-back
// restoreLiveTurnHtmlForSession() then fails and loadSession()'s fallback
// rebuilds with an EMPTY appendThinking(), permanently losing the streamed
// thinking/tool content (only the elapsed clock survives). Capturing here
// guarantees switch-back restores the exact state shown at switch-away. (#3668)
if(typeof snapshotLiveTurnHtmlForSession==='function') snapshotLiveTurnHtmlForSession(sessionId);
try{live.source.close();}catch(_){ }
delete LIVE_STREAMS[sessionId];
// closeLiveStream() is called during session-switch teardown for any session

View File

@@ -229,3 +229,44 @@ def test_sse_initial_event_reshows_pending_prompt():
assert "showApprovalForSession(sid" in MESSAGES_JS
# the 'initial' SSE event specifically is wired (not only the live event).
assert "addEventListener('initial'" in MESSAGES_JS
def test_close_live_stream_snapshots_turn_before_teardown():
"""#3668 'stays gone' variant: switching away from a streaming session during
a quiet window (mid tool-exec / silent thinking, between content SSE events)
must still preserve the live thinking/tool content on switch-back.
The per-event snapshot (snapshotLiveTurn) only fires on content/tool_complete
events, so closeLiveStream() — the switch-away teardown — must capture a DOM
snapshot BEFORE closing the source. Otherwise restoreLiveTurnHtmlForSession()
finds no/stale snapshot and loadSession()'s fallback rebuilds with an EMPTY
appendThinking(), permanently losing the streamed content (only the elapsed
clock survives — the reported signature).
"""
# Extract the closeLiveStream body and assert the snapshot precedes teardown.
start = MESSAGES_JS.index("function closeLiveStream(")
brace = MESSAGES_JS.index("{", start)
depth = 0
body = ""
for i in range(brace, len(MESSAGES_JS)):
if MESSAGES_JS[i] == "{":
depth += 1
elif MESSAGES_JS[i] == "}":
depth -= 1
if depth == 0:
body = MESSAGES_JS[brace + 1 : i]
break
assert body, "closeLiveStream body not found"
snap_idx = body.find("snapshotLiveTurnHtmlForSession(sessionId)")
close_idx = body.find("live.source.close()")
delete_idx = body.find("delete LIVE_STREAMS[sessionId]")
assert snap_idx != -1, (
"closeLiveStream() must snapshot the live turn (snapshotLiveTurnHtmlForSession) "
"before tearing the stream down, or switch-away during a quiet window loses content (#3668)."
)
assert close_idx != -1 and snap_idx < close_idx, (
"the snapshot must be taken BEFORE live.source.close()."
)
assert delete_idx != -1 and snap_idx < delete_idx, (
"the snapshot must be taken BEFORE LIVE_STREAMS teardown."
)