AuthReset was declared in the AuthEvent union and handled in
auth-machine.idle.on with `assign(() => initialState)`, but
nothing in the codebase actually dispatches it anymore.
History of dispatch sites (now all gone):
- sidebar-screen.tsx previously dispatched AuthReset post-logout
alongside router.replace(ROUTES.chat) — that was simplified to
just dispatch AuthLogoutSubmitted, the state machine's
loggingOut state now does the context reset itself in onDone
via the same `assign(() => initialState)` action.
- chat-screen / splash / auth screens never dispatched it.
- The /auth screen's pendingRedirect-based redirect loop doesn't
need it (the redirect is one-shot, not a state that needs
resetting).
So the event handler is unreachable. Remove the type member
from auth-events.ts and the handler from auth-machine.ts.
No behavior change: the only path that 'reset auth state' was
already covered by loggingOut.onDone.
Verified via `grep -rn 'AuthReset' src/`: only 2 hits remain
after this commit — both are the type declaration and the
machine handler, which is now also gone (zero hits).
The three defensive Zod helpers (stringOrEmpty, numberOrZero,
booleanOrFalse) used by user.ts are general-purpose Zod utilities
that any schema may need — they're not user-specific. The original
inline declaration in user.ts had two downsides:
1. Any new schema (chat, auth, metrics, etc.) that needs the same
'null | undefined → default scalar' pattern would have to
duplicate the helper.
2. The long defensive-parse comment block (11 lines) lived inside
user.ts and didn't explain the helpers' general purpose.
Move them to src/data/schemas/nullable-defaults.ts (top-level
sibling of auth/, chat/, metrics/, user/ subdirs) so any schema
can import them. Keep the original explanation verbatim at the
top of the new file.
Bundled in this commit:
- user.ts imports the helpers from the new file (drops 11 lines
of comment + 3 const declarations)
- 3 auto-generated barrel index files regenerated by barrelsby
to pick up the new exports (chat/, subscription/, stores/user/)
The previous install flow had a timing race: the
`beforeinstallprompt` event fires at unpredictable times after
page load (often ~30s+ into the session, when Chrome's heuristic
finally decides the user is engaged). If the listener was
attached only inside the dialog's onClick handler, by the time
the user clicked OK the event had already fired into the void
and the deferred prompt was null — so `install()` returned
"unavailable" every time.
The new flow attaches the listener as early as possible:
1. `pwaUtil.prepareInstallPrompt()` — new public method that
calls the private `attachListener()`. Idempotent (guard via
`listenersAttached`).
2. `pwaUtil.canPromptInstall()` — companion getter that returns
whether a deferred event is currently cached. Useful for UI
to decide whether to even show the install CTA.
3. `pwa-install-overlay.tsx` — calls `prepareInstallPrompt()`
right after the isSupported / isInstalled guards. The overlay
mounts when the user enters /chat, so this is the earliest
practical point in the user flow.
4. `pwa-screen.tsx` — calls `prepareInstallPrompt()` BOTH at
module load (synchronous, so the listener is attached before
the splash screen's useEffects run) AND in a useEffect for
robustness. The splash screen is the very first screen the
user sees, so attaching here is the earliest possible.
5. `pwa-install-dialog.tsx` — `handleInstall` is now async,
and `onInstall` accepts `() => void | Promise<void>`. The
overlay calls `await pwaUtil.install()` so the deferred
prompt flow can complete before the dialog unmounts.
Together: the listener is now attached from the moment the
splash screen module loads, the deferred prompt is captured
whenever Chrome decides to fire the event, and the dialog's
install button can reliably `prompt()` against the cached
deferred. The full install flow now works end-to-end.