Found by the #386 browser journey (PR #429) on its very first run — the class of bug epic #402 exists to catch.
Repro
A browser holding a valid sapling_session cookie but NO sapling_user localStorage entry loads /dashboard → middleware admits the request (cookie verifies) → the page renders its loading skeleton forever.
Root cause
UserContext bootstraps client identity only from the sapling_user localStorage entry, which is written solely by the sign-in flows (setActiveUser). It never falls back to cookie-based GET /api/auth/me — even though that endpoint already identifies the user from the cookie alone (the OAuth callback calls it exactly that way).
When this bites real users
Any state where the HttpOnly cookie outlives localStorage: cleared site data (localStorage only), private-window edge cases, another device/profile restore, or the stale-cookie flows previously seen around account deletion (#285 family).
Fix sketch
On bootstrap with no localStorage identity but a session cookie present, call /api/auth/me and hydrate (then write-through setActiveUser), falling back to signed-out state on 401.
Discovered-by: #386 / PR #429 (the E2E harness worked around it by seeding the localStorage entry into storageState — the workaround is test-side only; the product gap remains).
🤖 Generated with Claude Code
Found by the #386 browser journey (PR #429) on its very first run — the class of bug epic #402 exists to catch.
Repro
A browser holding a valid
sapling_sessioncookie but NOsapling_userlocalStorage entry loads/dashboard→ middleware admits the request (cookie verifies) → the page renders its loading skeleton forever.Root cause
UserContextbootstraps client identity only from thesapling_userlocalStorage entry, which is written solely by the sign-in flows (setActiveUser). It never falls back to cookie-basedGET /api/auth/me— even though that endpoint already identifies the user from the cookie alone (the OAuth callback calls it exactly that way).When this bites real users
Any state where the HttpOnly cookie outlives localStorage: cleared site data (localStorage only), private-window edge cases, another device/profile restore, or the stale-cookie flows previously seen around account deletion (#285 family).
Fix sketch
On bootstrap with no localStorage identity but a session cookie present, call
/api/auth/meand hydrate (then write-throughsetActiveUser), falling back to signed-out state on 401.Discovered-by: #386 / PR #429 (the E2E harness worked around it by seeding the localStorage entry into storageState — the workaround is test-side only; the product gap remains).
🤖 Generated with Claude Code