Problem
Every time the iOS app is cold-started (process killed by OS while backgrounded), users see a loading spinner for 1-3 seconds before sessions appear. This happens on every reopen because:
- Zustand store initializes with
isDataReady: false and empty sessions
SessionsListWrapper shows a spinner when useSessionListViewData() returns null
- The app must fetch
/v2/sessions, decrypt all session encryption keys (sequential loop), decrypt metadata + agentState per session, then call applyReady()
Sessions are not persisted to MMKV — only drafts, permission modes, and settings are cached locally.
Root Cause Analysis
In sessionSnapshot.ts, both decryption loops are fully sequential (await inside for):
- Lines 75-90: Sequential encryption key decryption (
decryptEncryptionKey per session)
- Lines 95-149: Sequential session metadata/agentState decryption
In sync.ts:
applyReady() (the only place isDataReady becomes true) is called in exactly one place: #init() line 527, after sessionsSync.awaitQueue() + machinesSync.awaitQueue() complete
isDataReady starts as false and is never persisted
Proposed Fix
Phase 1: Parallelize decryption (low risk, high impact)
- Use
Promise.all (with concurrency limit) for the key decryption loop
- Use
Promise.all for the session metadata/agentState decryption loop
Phase 2: Cache session list to MMKV (medium risk, highest impact)
- After
applySessions(), serialize sessionListViewData to MMKV
- On cold start, load cached data immediately before fetch completes (stale-while-revalidate)
- Set
isDataReady: true immediately if cache exists
Environment
- iOS (Happier mobile app)
- Affects all cold starts (any time iOS kills the app process in background)
- More noticeable with many sessions (50+)
Problem
Every time the iOS app is cold-started (process killed by OS while backgrounded), users see a loading spinner for 1-3 seconds before sessions appear. This happens on every reopen because:
isDataReady: falseand empty sessionsSessionsListWrappershows a spinner whenuseSessionListViewData()returnsnull/v2/sessions, decrypt all session encryption keys (sequential loop), decrypt metadata + agentState per session, then callapplyReady()Sessions are not persisted to MMKV — only drafts, permission modes, and settings are cached locally.
Root Cause Analysis
In
sessionSnapshot.ts, both decryption loops are fully sequential (awaitinsidefor):decryptEncryptionKeyper session)In
sync.ts:applyReady()(the only placeisDataReadybecomestrue) is called in exactly one place:#init()line 527, aftersessionsSync.awaitQueue()+machinesSync.awaitQueue()completeisDataReadystarts asfalseand is never persistedProposed Fix
Phase 1: Parallelize decryption (low risk, high impact)
Promise.all(with concurrency limit) for the key decryption loopPromise.allfor the session metadata/agentState decryption loopPhase 2: Cache session list to MMKV (medium risk, highest impact)
applySessions(), serializesessionListViewDatato MMKVisDataReady: trueimmediately if cache existsEnvironment