Architecture: How to persist activeSessionId across page refreshes without...
Documentation for Sessions
🏗️ Architecture: How to persist activeSessionId across page refreshes without...
Date: 2026-03-03
Time: 22:30:02 UTC
Session: gspace1-166.13
Agent: storage
Confidence: 97%
Status: ✅ Documented
📋 Decision Context
Question:
How to persist activeSessionId across page refreshes without it being lost or overwritten?
Task: Session 170: Fix activeSessionId lost on page refresh
🎯 Decision Made
Chosen:
Extracted activeSessionId out of autoFlashState into a dedicated signal() + localStorage. The signal is initialized synchronously from localStorage.getItem() in the field declaration. A separate setActiveSessionId() method writes to both signal and localStorage. ALL code paths that read or write activeSessionId were migrated from state().activeSessionId to _activeSessionId() / setActiveSessionId(). autoFlashState still stores session data (messages, titles) but no longer controls which session is active.
Options Considered:
- Keep activeSessionId in autoFlashState (broken — async hydration overwrites)
- Skip first effect run (failed — multiple signal changes re-trigger)
- localStorage read + state.update in constructor (failed — autoFlashState hydration overwrites the update)
- Separate signal + localStorage (CHOSEN — synchronous, deterministic, immune to hydration)
💭 Reasoning
ROOT CAUSE: activeSessionId was stored INSIDE autoFlashState alongside all session data. autoFlashState hydration is async (setInterval 50ms polling). On page refresh: (1) autoFlashState returns DEFAULT_STATE with activeSessionId:null, (2) constructor effect fires and sees null → creates/selects wrong session, (3) 50ms later IndexedDB hydrates but the damage is done. MULTIPLE FAILED ATTEMPTS: (a) Skip-first-effect-run — failed because effect re-fires from multiple signal changes. (b) localStorage read + state.update in constructor — failed because state.update triggers async dispatch which autoFlashState hydration then overwrites. (c) waitingForBackend flag — failed because setBackendSessions() also overwrote activeSessionId. FINAL FIX: Completely decouple activeSessionId from autoFlashState. A plain signal() initialized from localStorage is synchronous, deterministic, and immune to IndexedDB hydration timing. This matches the industry pattern (Slack, Discord use URL/localStorage for active conversation, not IndexedDB). Also fixed setBackendSessions() which had a secondary bug: it checked state().sessions[activeId] (local only) instead of mergedSessions()[activeId], always found null, and overrode the active session.
📁 Files Involved
libs/libs-ai/vitto-chat/src/lib/services/vitto-chat-persistence.service.ts
🔗 Related
- Decision ID:
dev_1772577002114_hbls5ii - Session: 166
- Workspace: gspace1
Auto-generated by MCP Smart Consolidation