Release — replace cross-profile empty sessions on profile switch (#5460) - #5507
Conversation
…open all-profile rows under owning profile Clean rebase of ruizanthony's #5460 (rebase-first). Co-authored-by: ruizanthony <ruizanthony@users.noreply.github.com>
… on profile switch
|
| Filename | Overview |
|---|---|
| static/panels.js | Core profile-switch logic updated: sessionInProgress promoted to let, post-switch profile mismatch detection added, new _openingExistingSidebarSession fast-path prevents intermediary blank sessions, _openProfileSwitchSessionBrowser helper added and called from both success branches. |
| static/sessions.js | Three duplicated session-open call sites consolidated into _openSidebarSession; _ensureSidebarSessionProfile added for cross-profile switch before loadSession; _showAllProfiles persistence via localStorage with cross-tab storage-event sync; return value of _ensureSidebarSessionProfile discarded in _openSidebarSession. |
| tests/test_profile_switch_ux.py | New static-analysis tests pin the empty-session promotion, session-browser open ordering, and desktop/mobile helper contract; existing tests updated for renamed function paths. |
| tests/test_issue1611_session_profile_filtering.py | Two new tests added: cross-profile switch before loadSession and localStorage persistence of the all-profiles toggle; existing import-payload assertion broadened to match refactored call site. |
| tests/test_issue3603_external_session_import_gate.py | Import-gate assertions migrated from per-call-site pattern checks to the new shared _openSidebarSession helper; coverage preserved for all three open paths. |
| tests/test_issue4662_profile_switch_skeleton_static.py | Skeleton-call detection updated from literal string match to regex search to accommodate optional conditional wrapper; timeoutToast window search tightened to look forward-only from the POST call. |
| tests/test_issue1700_parallel_profile_switch.py | sessionInProgress detection updated from const to let keyword via regex search, preserving coverage of active_stream_id and pending_user_message checks. |
| tests/test_firefox_sidebar_scroll_stability.py | Toggle onclick assertions updated to match the new _setShowAllProfiles helper signature and explicit deferWhileInteracting:false argument. |
| tests/test_session_lineage_collapse.py | Lineage and child-session open assertions updated from loadSession to _openSidebarSession call site. |
| tests/test_session_touch_actions.py | Gesture-finish touch-action test updated from loadSession index search to _openSidebarSession index search; dismiss-before-open ordering assertion preserved. |
| CHANGELOG.md | Release changelog entry added for the profile-switch upload fix and cross-profile sidebar row fix. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User
participant UI as renderSessionListFromCache
participant OSS as _openSidebarSession
participant ESSP as _ensureSidebarSessionProfile
participant STP as switchToProfile
participant LS as loadSession
Note over User,LS: Cross-profile sidebar row click (all-profiles mode)
User->>UI: click session row (other profile)
UI->>OSS: _openSidebarSession(session)
OSS->>OSS: _isExternalSession? → import_cli (if needed)
OSS->>ESSP: _ensureSidebarSessionProfile(session)
ESSP->>ESSP: "_showAllProfiles && profile differs?"
ESSP->>ESSP: "_profileSwitchOpeningExistingSession = true"
ESSP->>STP: switchToProfile(targetProfile)
Note over STP: _openingExistingSidebarSession=true snapshot
STP->>STP: "sessionInProgress=true (skip intermediary blank chat)"
STP-->>STP: POST /api/profile/switch
STP->>STP: "sessionInProgress&&_openingExistingSidebarSession branch"
STP->>STP: renderSessionList() + showToast()
STP-->>ESSP: resolved
ESSP->>ESSP: "_profileSwitchOpeningExistingSession = false (finally)"
ESSP-->>OSS: return profileMatchesTarget
OSS->>LS: loadSession(session.session_id, loadOpts)
LS-->>OSS: session loaded
OSS->>UI: renderSessionListFromCache()
Note over User,LS: Profile chip switch with empty cross-profile session
User->>STP: switchToProfile(newProfile)
STP-->>STP: POST /api/profile/switch → S.activeProfile updated
STP->>STP: "!sessionInProgress && S.session: check session.profile vs target"
STP->>STP: "mismatch → sessionInProgress = true"
STP->>STP: else if(sessionInProgress) branch → newSession()
STP->>STP: renderSessionList() + _openProfileSwitchSessionBrowser()
STP-->>User: sidebar/drawer exposed with new profile's sessions
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User
participant UI as renderSessionListFromCache
participant OSS as _openSidebarSession
participant ESSP as _ensureSidebarSessionProfile
participant STP as switchToProfile
participant LS as loadSession
Note over User,LS: Cross-profile sidebar row click (all-profiles mode)
User->>UI: click session row (other profile)
UI->>OSS: _openSidebarSession(session)
OSS->>OSS: _isExternalSession? → import_cli (if needed)
OSS->>ESSP: _ensureSidebarSessionProfile(session)
ESSP->>ESSP: "_showAllProfiles && profile differs?"
ESSP->>ESSP: "_profileSwitchOpeningExistingSession = true"
ESSP->>STP: switchToProfile(targetProfile)
Note over STP: _openingExistingSidebarSession=true snapshot
STP->>STP: "sessionInProgress=true (skip intermediary blank chat)"
STP-->>STP: POST /api/profile/switch
STP->>STP: "sessionInProgress&&_openingExistingSidebarSession branch"
STP->>STP: renderSessionList() + showToast()
STP-->>ESSP: resolved
ESSP->>ESSP: "_profileSwitchOpeningExistingSession = false (finally)"
ESSP-->>OSS: return profileMatchesTarget
OSS->>LS: loadSession(session.session_id, loadOpts)
LS-->>OSS: session loaded
OSS->>UI: renderSessionListFromCache()
Note over User,LS: Profile chip switch with empty cross-profile session
User->>STP: switchToProfile(newProfile)
STP-->>STP: POST /api/profile/switch → S.activeProfile updated
STP->>STP: "!sessionInProgress && S.session: check session.profile vs target"
STP->>STP: "mismatch → sessionInProgress = true"
STP->>STP: else if(sessionInProgress) branch → newSession()
STP->>STP: renderSessionList() + _openProfileSwitchSessionBrowser()
STP-->>User: sidebar/drawer exposed with new profile's sessions
Reviews (1): Last reviewed commit: "docs(changelog): #5460 replace cross-pro..." | Re-trigger Greptile
| await _ensureSidebarSessionProfile(session); | ||
| await loadSession(session.session_id, loadOpts); |
There was a problem hiding this comment.
_ensureSidebarSessionProfile return value discarded
_ensureSidebarSessionProfile returns false for two semantically different cases: "no switch was needed" (profiles already match) and "the switch failed." When the switch fails, loadSession is still called with the original profile cookie, so the request will be rejected by the backend. The user would receive switchToProfile's own error toast and then a second, less-descriptive error from loadSession, which could be confusing. Checking the return value — or, alternatively, re-checking _profileMatchesActiveProfile after the await — would let the function bail early on a genuine switch failure.
| function _openProfileSwitchSessionBrowser(){ | ||
| try{ | ||
| const isDesktop = (typeof _isDesktopWidth === 'function') ? _isDesktopWidth() : true; | ||
| if(isDesktop){ | ||
| if(typeof expandSidebar === 'function') expandSidebar(); | ||
| return; | ||
| } | ||
| const sidebar=document.querySelector('.sidebar'); | ||
| if(!sidebar)return; | ||
| try{if(typeof _syncMobileSidebarPanelFromMainView==='function')_syncMobileSidebarPanelFromMainView();}catch(_){} | ||
| sidebar.classList.remove('mobile-session-page'); | ||
| sidebar.classList.add('mobile-panel-drawer','mobile-open'); | ||
| }catch(_){} | ||
| } |
There was a problem hiding this comment.
Mobile sidebar opened via direct CSS class manipulation
The mobile path removes mobile-session-page and adds mobile-panel-drawer and mobile-open directly on the .sidebar element, bypassing any dedicated open-sidebar function that might maintain additional state (scroll position, panel-mode tracking, animation classes). If a future refactor centralises mobile sidebar state into a helper, this call site would silently fall behind. It's worth noting that closeMobileSidebar is already used elsewhere — if a symmetric openMobileSidebar (or equivalent) exists or is added, this helper should delegate to it rather than manipulating classes directly.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Release — replace cross-profile empty sessions on profile switch (#5460)
Ships @ruizanthony's #5460 (gate-pass, crown-jewel profile-switch surface). Fixes the upload-404-after-profile-switch bug + makes cross-profile sidebar rows open correctly.
When you switched profiles with an empty current chat, the browser could retain the previous profile's session id, so a later attachment upload posted a stale id → "Session not found" until a hard reload. The empty chat is now replaced during the switch when it belonged to a different profile. Also: clicking a session owned by another profile now switches to its owning profile before loading (instead of 404ing).
Gate (crown-jewel — full re-gate despite gate-cert pass)
test_issue1567,test_issue4536— pre-existing env flakes, unrelated).Co-authored-by: ruizanthony ruizanthony@users.noreply.github.com