Address two suggestions from the PR agent review:
1. lib/authResume.ts — catch isAuthed() exceptions in
consumeResumeSnapshot. The network/timeout path now returns
null (snapshot already removed earlier to prevent the play-page
bootstrap's retryBootstrap loop from re-entering this path).
Document the intentional removeItem-before-isAuthed ordering.
2. components/AuthModal.tsx — wrap onBeforeOAuth in try-catch so
a snapshot failure (e.g. sessionStorage blocked in privacy mode)
does not abort the OAuth flow and leave the UI stuck in loading.
Extract the page-agnostic resume primitives into lib/authResume.ts:
- isAuthed() — single login check (was duplicated in app/page.tsx)
- writeResumeSnapshot(key, primary, fallbacks) — quota-safe sessionStorage
write with ordered lighter-payload fallbacks (was hand-rolledTry/catch
in both pages)
- consumeResumeSnapshot<T>(key) — consume-once resume gate that verifies
the user is signed in before returning the snapshot, else clears it
Both pages now share this plumbing while keeping their own snapshot shapes
and restore side effects (home: form fields + start(); play: Session +
restorePlayResume + deferred action replay).
Unify the persist trigger: home previously snapshotted eagerly inside
start() before opening the modal, while play snapshotted in
AuthModal.onBeforeOAuth at redirect time. Move home to the same
onBeforeOAuth trigger so both pages persist at the single OAuth-redirect
instant — the eager-snapshot special case is gone, and OTP (no redirect)
keeps its in-place onSuccess resume on both pages.
Net: -21 lines. Behavior preserved for OTP; OAuth resume now consistent.