HERMES-GIELIE/static
allenliang2022 860a3c2458 fix(webui): keep stale transcript mounted while hidden-tab return reloads new messages (#5177)
After a hidden interval that added persisted messages (post-turn bg-review
writes, sibling-tab writes, the just-finished main turn flushing), switching
the tab back caused refreshActiveSessionIfExternallyUpdated('visible'|'focus')
to hit remoteCount !== localCount and call loadSession(sid, {force:true}),
which synchronously did S.messages = [] before awaiting the metadata +
messages fetches. The visible result was the entire conversation transcript
blanking for the round-trip and reappearing — '对话突然消失,重刷才回来'.

sessions.js itself warns about this 'disappear and reappear' tradeoff at the
top of the remoteCount/remoteLast guard, but only short-circuits the
metadata-only branch (#5061). #5122's 4-probe staged reconnect covers a
different path (SSE error with ready_state=2 on a visible tab) and does not
apply when the SSE error arrives while visibility_state='hidden' — that
bottoms out through _deferStreamErrorIfPageHidden before the reconnect block
ever runs.

Fix:

- refreshActiveSessionIfExternallyUpdated maps the visibility/focus recovery
  reasons ({visible, focus}) to a new keepStaleUntilLoaded option, forwarded
  to loadSession. The post-stream idle reconcile and the poll/external paths
  keep their existing behaviour.
- loadSession ANDs opts.keepStaleUntilLoaded with sameSessionForceReload (so
  cross-session switches still clear synchronously — leaving a prior
  session's transcript on screen during a navigation is the original bug
  the clear was written for) and, on that path, skips the synchronous
  S.messages/S.toolCalls/_messagesTruncated/_oldestIdx clear. The new
  transcript is SWAPPED into S.messages by _ensureMessagesLoaded(sid,
  {force:true}), so the user sees old DOM directly replaced by new DOM in a
  single render frame.
- _ensureMessagesLoaded grows an opts.force escape hatch so its 'messages
  already populated' early-return cannot skip the swap when stale messages
  are still in place.

Verified live on a source 8701 build:
- before: minHtmlObserved 65874, minKidsObserved 11 — 372 samples at 16ms,
  DOM html length NEVER dipped below the pre-call value during the reload.
- loadSession was called with force:true keepStale:true reason:'visible',
  outcome 'reloaded', sMsgs 21 → 1037, after kids 11 → 134 (single-frame
  swap).

Tests:

- tests/test_issue5177_hidden_tab_blank_gap.py (new, 7 cases): source-locks
  the keep-stale guard shape, the recovery-reason map, the
  _ensureMessagesLoaded opts.force escape hatch, and that the synchronous
  clear is wrapped in if(!_keepStaleUntilLoaded).
- Updated the two existing source-lock tests whose anchor strings included
  the loadSession call signature
  (tests/test_webui_external_refresh_frontend.py +
  tests/test_tars_scroll_reset_regressions.py) to match the new
  loadSession(... keepStaleUntilLoaded:_keepStaleUntilLoaded) form. No
  behavioural assertions changed.

Local: node --check static/sessions.js OK. 122 passed across
test_issue5177_hidden_tab_blank_gap.py + the full adjacent set
(external-refresh / journal-frontend / scroll-reset / reconnect-chronology /
inflight-stream-reuse / sse-error-multi-probe / issue4295 / issue4856).
2026-06-29 17:02:09 +00:00
..
vendor fix: protect pipes inside backtick code spans in renderMd tables 2026-06-25 08:25:58 +00:00
apple-touch-icon.png Swap brand to new cyan caduceus: favicon family, titlebar + empty-state logo (theme-safe navy badge), remove per-skin titlebar fill overrides 2026-06-28 07:44:16 +00:00
assistant_turn_anchors.js Add assistant turn anchor renderer snapshot adapter 2026-06-14 02:03:45 +08:00
boot.js Merge branch 'master' into fix/4657-fit-engine-rebased 2026-06-28 19:24:13 -07:00
commands.js fix(steer): rename typo, catch retry rejection, update JSDoc (#4749) 2026-06-25 03:23:36 +00:00
extension_settings.js fix(extensions): prune malformed browser overrides (#5094) 2026-06-28 18:31:43 -04:00
favicon-32.png Swap brand to new cyan caduceus: favicon family, titlebar + empty-state logo (theme-safe navy badge), remove per-skin titlebar fill overrides 2026-06-28 07:44:16 +00:00
favicon-192.png Swap brand to new cyan caduceus: favicon family, titlebar + empty-state logo (theme-safe navy badge), remove per-skin titlebar fill overrides 2026-06-28 07:44:16 +00:00
favicon-512.png Swap brand to new cyan caduceus: favicon family, titlebar + empty-state logo (theme-safe navy badge), remove per-skin titlebar fill overrides 2026-06-28 07:44:16 +00:00
favicon-512.svg Swap brand to new cyan caduceus: favicon family, titlebar + empty-state logo (theme-safe navy badge), remove per-skin titlebar fill overrides 2026-06-28 07:44:16 +00:00
favicon.ico Swap brand to new cyan caduceus: favicon family, titlebar + empty-state logo (theme-safe navy badge), remove per-skin titlebar fill overrides 2026-06-28 07:44:16 +00:00
favicon.svg Swap brand to new cyan caduceus: favicon family, titlebar + empty-state logo (theme-safe navy badge), remove per-skin titlebar fill overrides 2026-06-28 07:44:16 +00:00
i18n.js fix(i18n): restore default-off quota-chip copy (Codex gate #4657) 2026-06-29 02:06:15 +00:00
icons.js stage #4581 onto master (v0.51.556) — surface escape-target symlinks as display-only workspace rows 2026-06-21 09:15:22 +00:00
index.html Merge #5163 into stage/p1a 2026-06-29 04:07:21 +00:00
login.js feat: add passkey sign-in 2026-05-25 00:14:38 +00:00
manifest.json
messages.js fix(webui): render server-initiated turns in a hidden tab without a refresh 2026-06-29 16:28:30 +00:00
onboarding.js fix(onboarding): rehydrate plain-select fallback so saved model isn't reset to option[0] 2026-06-27 04:33:02 +00:00
outline.js fix(outline): keep outline valid on the todos sidebar panel (Opus follow-up) 2026-06-10 23:55:05 +00:00
panels.js fix(workspace): align Workspace Todos rendering with sidebar 2026-06-29 04:44:16 +00:00
pwa-startup.js
sessions.js fix(webui): keep stale transcript mounted while hidden-tab return reloads new messages (#5177) 2026-06-29 17:02:09 +00:00
style.css feat(composer): fit footer controls by overflow (#4657, rebased onto master) 2026-06-29 01:19:02 +00:00
sw.js fix: focus the existing chat tab when a notification is clicked instead of opening a new tab 2026-06-13 14:42:41 -07:00
terminal.js fix(#4313): guard EventSource.close() calls to suppress ERR_ABORTED noise 2026-06-18 17:25:18 +00:00
ui.js fix: authorize session html media artifacts 2026-06-29 05:17:33 +00:00
workspace.js fix(workspace): align Workspace Todos rendering with sidebar 2026-06-29 04:44:16 +00:00