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/)
Root cause: backend `/auth/logout` returns `{ success: true, data: null }`
(no business payload for a fire-and-forget endpoint). The previous
`AuthApi.logout` ran `unwrap(env)` (which passes null through, since
null is defined) and then `LogoutResponse.fromJson(null)`, which
Zod-parses as `z.object(...)` and throws "Invalid input: expected
object, received null".
The error was caught in `AuthRepository.logout` with
`console.warn(... clearing local anyway)` — logout actually still
worked (local clear ran), but a noisy red ZodError hit the console on
every logout click.
Fix: align `AuthApi.logout` with the existing fire-and-forget pattern
used by `register` and `sendCode` — call `httpClient` and ignore the
response body. Drop the now-unused `LogoutResponse` import.
`AuthRepository.logout` already discards the return value, so the
`Promise<LogoutResponse> → Promise<void>` signature change is safe.
Bundled in this commit (unrelated):
- chat-machine.actors.ts / user-machine.ts: removed stale design-doc
comment blocks (IDE/linter cleanup)
- user-machine.actors.ts: removed redundant `userStorage.clearUserData()`
from `userLogoutActor` — `authRepo.logout` already clears local
user data, so this was a no-op duplicate.
- sidebar-screen.tsx: removed one stale comment line.
Introduce ChatQuotaExhaustedBanner component that displays a pink gradient
banner with an "Unlock your membership to continue" CTA when a guest user's
free chat quota has been exhausted. The banner is shown in ChatScreen when
isGuest is true, quota has loaded, and the chat state machine's
quotaExceededTrigger has been incremented by a quota guard. Default click
navigates to /subscription, with an optional onUnlock callback for
overrides/tests. Styles align with the existing splash and sidebar VIP
card visual language.
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.
Two related fixes for OAuth login failures:
- `UserSchema`: backend (Facebook/Apple) returns `null` for fields like
`email`; Zod's `.default("")` rejects `null`, causing silent login
failures. Introduce `stringOrEmpty` / `numberOrZero` / `booleanOrFalse`
helpers (`nullable().transform(v => v ?? x).default(x)`) and apply
across the schema so input accepts `string | null | undefined` while
output stays the typed primitive.
- `SplashScreen`: render `state.errorMessage` (set by auth state machine
`onError`, e.g. Facebook sync / schema mismatch) so users no longer
get stuck on splash without feedback.
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>
Add Stripe payment integration across the project:
- Add @stripe/stripe-js dependency
- Configure Stripe environment variables (secret key, publishable key, webhook secret) for dev, local, and production environments
- Add Product/Price IDs for monthly, quarterly, and annual subscription tiers
- Extend subscription plan data with voiceMinutesPerDay quotas (30/45/60 minutes per tier)
- Update .gitignore to exclude implementation_plan.md
Sidebar screen now primarily displays user-related information
and derives three render states from loginStatus + currentUser.isVip:
- guest (notLoggedIn): welcome panel + Sign in / Sign up entries
- member (logged in, no VIP): user info card + Get VIP CTA + Log out
- vip (logged in, VIP): user info card with VIP badge + Manage VIP CTA
+ Log out
Adds UserView.isVip (default false), patches user-machine.toView()
to populate it, and introduces three presentational components
(GuestPanel, UserInfoCard, VipCta) co-located under src/app/sidebar.
Preserves existing UserInit + logout-redirect behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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
Add a new /subscription route showing three VIP membership plans (Monthly,
Quarterly, Annual) with a pink→peach banner, per-plan gradient price bars,
a benefits card, a mock activate CTA, and an agreement checkbox.
- New reusable <Checkbox /> in src/app/_components/core for the legal
agreement toggle
- Hardcoded SUBSCRIPTION_PLANS constant in src/data/constants
- New SubscriptionScreen composed of 7 colocated components (back link,
user row, banner, plan card, benefits card, CTA button) with CSS
Modules matching the existing design-token system
- Route registered as protected and reached from /sidebar "Membership"
- Mock CTA: window.alert + router.push(/chat) — no payment calls
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove unused `quotaWarningThreshold` and `warningThreshold` getter from `GuestChatQuota` DTO
- Apply get-or-init pattern in `ChatStorage.getGuestDailyChatQuota` and `getGuestTotalQuota`: when no existing value, initialize with DTO defaults (`threshold` = max per day, `totalQuotaDefault`) and return the seeded data
- Fall back to original `null` result if initialization write fails
- Consolidate logging strategy: drop per-actor trace logs in `chat-machine.ts`, keep business-decision logs in 3 assign actions, move all actor ENTRY/API/DONE chains to `chat-machine.actors.ts` and helper load logs to `chat-machine.helpers.ts`
Update implementation_plan.md with a detailed refactor plan that splits
`chatInitActor` into two independent actors (`loadQuotaActor` +
`loadHistoryActor`) and redesigns the history loading flow to follow
local → network → save semantics so the UI sees local data first,
then network data.
Key changes outlined in the plan:
- Add new events: `ChatQuotaLoaded`, `ChatHistoryLocalLoaded`,
`ChatHistoryNetworkLoaded`, `ChatHistorySyncDone`
- Remove dead-code `ChatInit` event
- Extend `ChatState` with `quotaLoaded` and `historyLoaded` flags
- Use an `always` barrier in `guestSession.initializing` and
`userSession.initializing` so both tasks must complete before
transitioning to `ready`
- Guest init runs `loadQuota` + `loadHistory` in parallel; non-guest
init runs `chatWebSocket` + `loadHistory` as independent tasks
Also adds `implementation_plan` to .gitignore.
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.
- 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
Introduce xstate v5 and @xstate/react to manage user authentication and profile state. Implement userMachine with actors for initialization, fetching, and logout operations, along with actions for updating user data and clearing state.
Add auto-generated barrel index files for auth, chat, sidebar, and user contexts, and update the services barrel with comprehensive exports for api, dto, and schema modules to streamline imports across the application.