Previously, the Skip button (guest login entry) relied on the auth
state machine's `pendingRedirect` flag to trigger the navigation
to /chat after guest login completed. That introduced a
non-obvious coupling: the button dispatched an event, the machine
ran an actor, the auth screen (or splash useEffect) saw the flag
and redirected.
This refactor moves the redirect into the splash screen itself:
- `SplashButton` now takes an `onSkip` prop and no longer knows
about auth dispatch. Pure presentation component.
- `SplashScreen` provides `handleSkip` that dispatches the
`AuthGuestLoginSubmitted` event AND immediately calls
`router.replace(/chat)` for snappier perceived navigation.
- `auth-machine.ts`: `loadingGuestLogin.onDone` no longer sets
`pendingRedirect: true` (splash already navigated). Sets
it to `false` explicitly so other screens (auth, sidebar) that
also react to `pendingRedirect` don't double-navigate if the
user triggers guest login from a non-splash surface in the future.
No behavior change for the happy path: Skip → /chat works the same.
The refactor is purely about responsibility allocation and
component decoupling.
The AuthState.isLoading derivation in auth-context.tsx covered the
OAuth redirect phase (loadingOAuth) but NOT the backend-sync phase
that runs after the OAuth callback returns
(syncingGoogleBackend / syncingFacebookBackend). Any UI that
read isLoading would incorrectly report "not loading" during
the backend token-exchange round-trip.
Add the two missing state.matches() lines so isLoading is true
for the entire OAuth login window (button click → OAuth redirect
→ callback → backend sync).
Note: in the current OAuth flow the user is on /chat during the
sync phase (NextAuth redirected them there), so the splash/auth
button is not actually visible to reflect the new true value.
The fix is still correct because:
- Conceptually aligned: any in-flight login op is "loading"
- Defense in depth: future UI on /chat that consumes isLoading
will get accurate feedback
- Non-regression: only makes isLoading more permissive (true more
often), never false-fires
A visible /chat overlay during the sync phase would be a separate,
larger change — noted in the planning doc but not in this commit.
The previous event name implied "re-check status", but the actual
semantics is just "read storage on app start and sync loginStatus into
the machine". It was being dispatched from three sites:
1. AuthStatusChecker mount useEffect (the legit one-time init)
2. AuthStatusChecker loginStatus-watching useEffect (dead code — the
machine's onDone already writes loginStatus, re-reading storage
just returns the same value as a no-op)
3. Sidebar post-logout effect (also dead code — AuthReset directly
sets loginStatus to initialState's "notLoggedIn", and
userLogoutActor already cleared storage, so the values are
already aligned)
This commit:
- Renames event AuthStatusCheckSubmitted → AuthInit in:
* auth-events.ts (type union)
* auth-machine.ts (handler + transition target: checkingAuthStatus →
initializing, mirroring UserInit / initializing in user-machine)
* auth-status-checker.tsx (single dispatch site)
* root-providers.tsx (one comment)
- Simplifies auth-status-checker.tsx from 74 → ~40 lines:
* Removes useEffect ② (loginStatus-watching re-check)
* Removes prevLoginStatusRef + skipNextChangeRef + useAuthState
(dead loop-guard machinery no longer needed)
- Removes the post-logout re-check dispatch from sidebar-screen.tsx:
* AuthReset alone is sufficient — userLogoutActor cleared storage
and AuthReset writes back initialState, so they match by
construction. No re-verification needed.
Net: -44 lines, no behavior change for the happy paths (startup,
login, logout), one fewer source of false re-checks.
1) Sidebar logout flow:
- After AuthReset, also dispatch AuthStatusCheckSubmitted so the auth
machine re-verifies storage (not just resets to initialState). This
makes sure the redirect to /chat reflects the actual storage state,
not just the forcibly-cleared context.
2) OAuthSessionSync:
- After dispatching AuthGoogleSyncSubmitted / AuthFacebookSyncSubmitted,
call signOut({ redirect: false }) to drop the NextAuth session.
The OAuth idToken / accessToken has already been handed to the
backend; clearing the browser-side session prevents lingering OAuth
credential exposure. Fire-and-forget — once status flips to
"unauthenticated", the useEffect early-returns so no re-entry.
Note: auth-machine.ts lost a 3-line stale comment block about XState v5
type inference for inline assign; bundled with this commit.
Validation failure was previously silent — the form's submit() returned
without dispatching or showing anything on screen, so users could not tell
why the login/register button appeared to do nothing.
Changes:
- Add validationError local state in EmailLoginForm and EmailRegisterForm
- Display first validation error via AuthErrorMessage, prioritised over
the server's globalError (validation errors feel more immediate)
- Clear validation error on field change (standard "error fades as you
fix it" UX) — implemented as onChange handlers, not useEffect, to
avoid React's cascading-render anti-pattern
- Add observability to the entire email flow (was previously silent end
to end): form submit ENTRY, validator rejection, actor ENTRY, actor
DONE — mirrors the logging style already used by guestLoginActor
- Add entry: assign({ errorMessage: null }) to loadingEmailLogin and
loadingEmailRegister so stale errors clear on transition into the
loading state (matches loadingGuestLogin)
Remove `**...**` emphasis markers from inline comments and JSDoc blocks across
auth and chat components, machine mappers, and quota helpers. These are
plain code comments, not rendered markdown, so the bold syntax was noise.
Files touched:
- src/app/auth/components/auth-screen.tsx
- src/app/chat/components/chat-header.tsx
- src/app/chat/components/chat-screen.tsx
- chat machine mapper / quota helpers
No functional or behavioral changes.
Remove obsolete Dart migration notes and route handling comments from splash-button.tsx and auth-machine.ts, and drop the unused `self` parameter from `chatWebSocketActor` callback signature.
Replace the dark sidebar theme with a light-themed three-state UI
matching the design references:
- guest : pink "login" pill in user row, no status pill on VIP card,
"Activate VIP Membership" button shown
- member : "VIP membership not activated" subtitle, no status pill,
"Activate VIP Membership" button shown
- vip : pink "VIP Member" pill with diamond icon, "Activated" pill
in VIP card header, no Activate button
Decompose into BackBar, UserHeader, VipBenefitsCard, and VoicePackageCard
under src/app/sidebar/components/. Delete obsolete GuestPanel,
UserInfoCard, and VipCta.
Lift VIP_BENEFITS to a shared src/data/constants/vip-benefits.ts so the
sidebar and subscription page render identical benefit copy.
Add five sidebar tokens (--color-card-surface, --color-voice-gradient-*
--border-card, --shadow-card, --color-pill-bg) to src/tokens/colors.css.
Includes barrelsby regeneration for src/app/sidebar/components and
side-effect barrel updates under src/stores/{auth,chat,sidebar,user},
plus package-lock.json from npm install (required for lint/typecheck).
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix chat components barrel to properly export all component modules
- Remove interceptor exports from API services barrel
- Regenerate store barrel files (auth, chat, sidebar, user) using barrelsby
Move the file-local Helpers block (readGuestId) to auth-helpers.ts and the
Actors block (authRepo + 7 fromPromise actors) to auth-actors.ts. Public API
(authMachine, AuthEvent, AuthState, initialState, AuthMachine) is unchanged;
auth-context.tsx and the barrel index.ts need no edits.
- Inline NextAuth v4 handler directly in `[...nextauth]/route.ts`, removing
the `@/lib/auth/nextauth` abstraction layer
- Add Google/Facebook providers with JWT/session callbacks that expose
`idToken` and `accessToken` on the session for backend exchange
- Refactor `AuthPlatform` from constructor-based to static methods
(`googleSignIn()` / `facebookSignIn()`)
- Update `auth-machine.ts` and layout comment reference to match new structure
- Align with original Dart `nextauth-helpers.{googleLogin, facebookLogin}()`
naming and keep persistence concerns (unstorage, backend, custom cookies)
out of scope for this iteration
Add console.log instrumentation across the guest login pipeline (auth-machine, authRepository, authApi) to trace request lifecycle and surface Zod parsing failures with detailed issue info. Useful for diagnosing lastMessageAt schema mismatches.
Skip /auth/guest round-trip on splash Skip when localStorage already
holds a valid guestToken. Avoids redundant API call (and the
fingerprintjs deviceId probe) every time the user reopens splash.
Trade-off: a server-revoked token still resolves "guest" until the
first protected API call returns 401, which the HTTP layer handles.
Co-Authored-By: Claude <noreply@anthropic.com>
- Splash Skip button now dispatches `AuthGuestLoginSubmitted` instead of
direct routing, keeping guest auth under the state machine
- Update PWA install dialog copy ("Add to Home Screen") and drop favicon
entry from manifest icons
- Add debug logging and routing sequence docs to splash-button
Add AuthStatusChecker mounted in RootProviders to dispatch AuthStatusCheckSubmitted on mount. The new checkAuthStatusActor retrieves the device id, checks for an existing login or guest token, and falls back to a guest login API call when neither is present. Wires the new event/actor through the auth machine to enable automatic session restoration and guest-mode bootstrap.
Wire the Google/Facebook OAuth callback flow end-to-end so the provider's id_token (Google) and access_token (Facebook) captured by NextAuth are forwarded to the backend in exchange for business tokens:
- Extend the NextAuth config with jwt/session callbacks that surface `account.id_token` / `account.access_token` to the client during first sign-in
- Add an `OAuthSessionSync` bridge mounted inside `RootProviders` that listens to `useSession()` and dispatches new `AuthGoogleSyncSubmitted` / `AuthFacebookSyncSubmitted` events
- Add corresponding actors in the auth XState machine that call `authRepository.googleLogin` / `facebookLogin`, persisting the backend's `LoginResponse` through the existing repository path
This keeps all authentication orchestrated by the auth state machine while preserving NextAuth's OAuth redirect UX.
Move new AuthPlatform(provider).signIn() out of UI components and into
the auth state machine. UI now dispatches AuthGoogle/FacebookLoginSubmitted
events; a new oauthSignIn actor inside the machine calls next-auth/react's
signIn(provider). Errors and the OAuth redirect window are exposed via
the existing state.isLoading / state.errorMessage surface, removing the
local busy / error state in auth-facebook-panel.
Co-Authored-By: Claude <noreply@anthropic.com>
- Move state management from `src/contexts` to `src/stores` (zustand-based)
- Update barrelsby config to include new stores directory
- Add bottom-sheet and auth-background components
- Auto-open browser on dev server ready in VS Code launch config
- Refresh generated barrel index files
Replace the separate FacebookLogin and GoogleLogin classes with a single
AuthPlatform class that takes a provider name ("facebook" | "google") as a
constructor argument. This consolidates duplicate OAuth sign-in logic behind
one entry point while preserving the existing NextAuth flow.
- Add new src/lib/auth/auth_platform.ts exporting AuthPlatform and
AuthProvider type
- Update auth-facebook-panel.tsx and splash-button.tsx to use the new API
- Rename initialContext → initialState in the auth machine for consistency
- Update inline docs and comments to reference AuthPlatform
No behavioral change for end users; both providers still route through
NextAuth's signIn() with the same callbacks and cookies.
Extract `<Name>Context` interface + `initialContext` const into
`<name>-context.ts` and the event union into `<name>-events.ts` for
all four state machines (auth, chat, sidebar, user) under src/stores/.
- `*-machine.ts` keeps helpers, actors, actions, and the
`setup().createMachine()` body; re-exports the types/initial value
to preserve its public API.
- `*-types.ts` re-exports from the new files for backward compatibility
(sidebar keeps enum re-exports, chat keeps GuestChatQuota).
- `index.ts` barrels updated to re-export the new files.
- Removed unused model imports (AuthMode, AuthPanelMode) from
auth-machine.ts; kept LoginType, UiMessage, UserView where still
used by actors/helpers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Migrate from function-based social login helpers (`facebookLogin`, `googleLogin`)
to class-based services (`new FacebookLogin().signIn()`, `new GoogleLogin().signIn()`),
updating call sites in `AuthFacebookPanel` and `SplashButton` with try/catch error
handling. The NextAuth route handler is also refactored to the v4 pattern, importing
pre-built `GET` / `POST` exports from `@/lib/auth/nextauth` instead of constructing
the handler inline with `NextAuth(authOptions)`.