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 <rodboev@users.noreply.github.com>
This commit is contained in:
nesquena-hermesandrodboev committed 2026-06-23 01:38:25 +00:00
1 parent 710f2c6934
commit 749f95d01c
2 files changed
+25 -1

No files matched your search

+19
View File
@@ -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
+6 -1
View File
@@ -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){