From 749f95d01c7aa2b353e8a3e528fac28cb1ea0df2 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Tue, 23 Jun 2026 01:38:25 +0000 Subject: [PATCH] docs(#4671): clarify dead-assignment backstop + add four-guard contract comment (Opus polish) Opus ship-gate non-blocking observations (no MUST-FIX), applied to lower future-maintainer risk on the crown-jewel profile-switch surface: - clarified the belt-and-suspenders _sessionListSkeletonActive=false at the real-paint site - added a FOUR-GUARD CONTRACT doc-comment at the top of switchToProfile naming the four interacting state pieces (embargo / invalidation / skeleton-flag / _wsTreeGen + _switchGen) so a future edit touching one without the others doesn't silently reopen a window. Co-authored-by: rodboev --- static/panels.js | 19 +++++++++++++++++++ static/sessions.js | 7 ++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/static/panels.js b/static/panels.js index bbd938f5..36140823 100644 --- a/static/panels.js +++ b/static/panels.js @@ -5897,6 +5897,25 @@ window.addEventListener('resize',()=>{ }); async function switchToProfile(name) { + // ── #4671 profile-switch loading-skeleton — FOUR-GUARD CONTRACT ─────────────── + // The skeleton must never be clobbered by the OLD profile's content and must never + // strand. Four interacting pieces of state cooperate; an edit touching one without + // the others can silently reopen a clobber/strand window, so keep them in sync: + // 1. _profileSwitchListEmbargo (sessions.js) — set BEFORE the skeleton, drops EVERY + // session-list payload (success + fetch-failure) during the switch window; lifted + // immediately before the switch-owned renderSessionList(), on failure-restore, and + // in the _switchGen-guarded finally. Closes the "render that STARTS mid-switch, + // before the new-profile cookie is set, fetched the old profile" window. + // 2. _invalidateSessionListRenders() (sessions.js) — bumps _renderSessionListGen + + // clears pending/queued at switch start; discards renders already in flight/queued. + // 3. _sessionListSkeletonActive (sessions.js) — renderSessionListFromCache() bails + // while true; cleared ONLY on fresh data (_applySessionListPayload), fetch-error, + // and failure-restore — so a bail can't strand the skeleton. + // 4. _wsTreeGen (workspace.js) — bumped UNCONDITIONALLY here (incl. panel-closed, since + // loadDir('.') still runs); loadDir rejects stale /api/list whose gen is superseded. + // Plus _profileSwitchGeneration / _switchGen — guards superseded switches so a slower + // earlier switch can't clobber a newer one's skeleton/embargo. + // ────────────────────────────────────────────────────────────────────────────── // No-op self-switch guard: bail before showing any loading skeleton if we're // already on this profile, so paths like activateCurrentProfile() (which // doesn't pre-check) can't flash a skeleton→restore for a click that changes diff --git a/static/sessions.js b/static/sessions.js index 25aa72e6..7e3d5842 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -5534,7 +5534,12 @@ function renderSessionListFromCache(){ const committedSwipeReflowDelay=Math.max(0,committedSwipeDuration-SESSION_SWIPE_REFLOW_LEAD_MS); const listScrollTopBeforeRender=list.scrollTop||0; list.innerHTML=''; - // A real render has arrived — the skeleton (if any) is now replaced. (#4662) + // #4671: belt-and-suspenders. The authoritative skeleton-clear happens in + // _applySessionListPayload (once fresh data is in hand) BEFORE this function is + // reached, and the guard at the top of renderSessionListFromCache bails while the + // flag is still true — so by the time we paint here the flag is already false. Keep + // this assignment as a defensive backstop for any future non-switch caller that + // reaches a real paint with the flag somehow still set. _sessionListSkeletonActive=false; // Batch select bar (when in select mode) if(_sessionSelectMode){