Commit Graph

235 Commits

Author SHA1 Message Date
admin 8832552321 fix(githooks): write log timestamps in local time (was UTC + Z) 2026-06-16 18:24:27 +08:00
admin 39e7d61c8a fix(auth): re-check auth status after logout; clear NextAuth session after sync
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.
2026-06-16 18:24:23 +08:00
admin a44463b3ee refactor(user): split user-machine into helpers and actors
Mirror the existing chat-module structure (chat-machine.helpers.ts +
chat-machine.actors.ts) for consistency across state-machine modules.

Split:
- user-machine.helpers.ts: pure data layer (no XState dep)
  - userStorage singleton
  - InitData interface
  - toView DTO → UserView mapper
  - readInitData (parallel getUser + getAvatarUrl)
- user-machine.actors.ts: async actor implementations (fromPromise)
  - userRepo / authRepo injection
  - userInitActor / userFetchActor / userLogoutActor
- user-machine.ts: only setup().createMachine() — keeps inline assign
  actions referencing context/event where they belong, imports actors
  (not helpers, per the layered convention).

No behaviour change — only file boundaries moved. Public API (exports of
UserState / UserEvent / initialState / userMachine / UserMachine) stays
identical so external imports are untouched.
2026-06-16 18:12:11 +08:00
admin bde8b99c04 Merge branch 'dev' into test 2026-06-16 17:56:18 +08:00
admin 8b6e38d3cb feat(chat): prepend greeting message when chat history is empty
On first chat open (or when both local and network return empty),
the chat screen would render completely blank, which feels cold and
broken. Prepend a single AI persona greeting as the first message so
the first impression is warm.

Trigger:
- Final returned messages array is empty
- Either network succeeds with empty result, or network fails and local
  is also empty

Design notes:
- Greeting is NOT persisted to local/network — pure UI-layer fallback.
  Once the user sends a real message, network has content and the
  greeting no longer appears.
- If the user closes the app without ever sending a message and reopens,
  the greeting reappears (intentional: "warm first frame on each cold
  start").
- Rendered as an AI bubble (isFromAI: true) so the visual style matches
  the AI persona.
2026-06-16 17:56:03 +08:00
admin 2e238c3df8 Merge branch 'dev' into test 2026-06-16 17:43:39 +08:00
admin c5686d2749 feat(auth): implement Facebook user data fetching from Graph API and persist profile information 2026-06-16 17:43:19 +08:00
admin 82bf917ace feat(auth): enhance AuthStatusChecker with detailed status change handling and prevention of infinite loops 2026-06-16 17:42:37 +08:00
admin 4ee9d6cb81 feat(docs): add git commit guidelines and best practices 2026-06-16 17:31:20 +08:00
admin 200fb1642f fix(auth): surface validation errors in email login/register forms
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)
2026-06-16 17:29:58 +08:00
admin 2792d0c9c5 feat(chat): implement WebSocket message sending and integrate with chat state machine 2026-06-16 16:50:57 +08:00
admin 6692c5a68a Merge branch 'dev' into test 2026-06-16 16:42:57 +08:00
admin 03a2580023 chore: trigger post-receive hook verification 2026-06-16 16:36:52 +08:00
admin 436a0addcc fix(githooks): pin absolute GIT_DIR and unset GIT_WORK_TREE in post-receive 2026-06-16 16:31:35 +08:00
admin 5764b3c433 feat(ui): replace emoji icons with lucide-react icon components 2026-06-16 16:23:36 +08:00
admin db350aae44 fix(githooks): use absolute-git-dir to find worktree in post-receive
`git rev-parse --show-toplevel` in hook context (cwd=GIT_DIR, GIT_DIR
set) returns the .git path itself without erroring, so the previous
`|| echo $PWD` fallback never triggered and the script silently
cd'd into .git/.

Use `git rev-parse --absolute-git-dir` to reliably get the absolute
git-dir, then its parent is the worktree root. This works in both
interactive shells and hook context.
2026-06-16 15:58:38 +08:00
admin b09e2d093d Merge branch 'dev' into test 2026-06-16 15:53:50 +08:00
admin 594682ba6b chore(images): update auth, chat, icons, and splash images 2026-06-16 15:48:10 +08:00
admin 697dce64b1 chore: remove bold markers from Chinese characters in post-receive hook comments 2026-06-16 15:39:00 +08:00
admin bab731bd21 chore(githooks): simplify post-receive hook repo root detection
Remove the workaround that derived REPO_TOPLEVEL from
`git rev-parse --absolute-git-dir` and fall back to
`git rev-parse --show-toplevel` directly. Also drop the
related comments and debug echo. The simpler approach is
sufficient for the hook's needs and makes the script easier
to maintain.
2026-06-16 15:33:22 +08:00
admin 20cb9e80ed Merge branch 'dev' into test 2026-06-16 15:31:10 +08:00
admin 02b1b713a5 style(splash): set Athelas as primary font and italicize button text
- Prioritize Athelas in --font-system font stack with system fonts as fallback
- Apply italic style to splash button heading and label for typography emphasis
2026-06-16 15:30:23 +08:00
admin dcb6312fa3 feat(chat): add quota-exhausted banner for guest users
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.
2026-06-16 15:18:11 +08:00
admin d5ddb3f97f fix(githooks): cd to worktree root before running build
When git invokes post-receive, cwd is GIT_DIR (not worktree root).
`git rev-parse --show-toplevel` refuses to run from a bare context,
so the script would silently cd into .git/. Use `--absolute-git-dir`
and its parent to find the worktree reliably.

Also: receive.denyCurrentBranch=false means push does NOT auto-update
the worktree, so the hook must do `git reset --hard HEAD` to sync
the source the build will use.
2026-06-16 14:18:25 +08:00
admin fa5678ffdf ci: debug post-receive cwd 2026-06-16 14:15:51 +08:00
admin 704e2292fc ci: post-receive hook live test from plan-mode-exec 2026-06-16 14:12:28 +08:00
admin 9ffa30cc03 style: remove markdown bold syntax from code comments
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.
2026-06-16 14:06:31 +08:00
admin 8553f91e5d ci: trigger post-receive hook smoke test #2 (absolute hooksPath) 2026-06-16 14:02:59 +08:00
admin a370518d5f ci: trigger post-receive hook smoke test 2026-06-16 13:27:47 +08:00
admin c24a7751b7 Merge branch 'dev' into test 2026-06-16 13:24:48 +08:00
admin 7506dcae3a chore(githooks): sync worktree to new HEAD in post-receive hook
The post-receive hook now performs a `git reset --hard HEAD` after each push
to ensure the working tree reflects the newly received commits. This is needed
when the server's `receive.denyCurrentBranch` is set to `false`, in which case
Git no longer auto-updates the working tree and downstream build steps would
otherwise run against stale code.
2026-06-16 13:24:37 +08:00
admin c878959dd7 chore: add use client directive and remove outdated dart reference
- Add "use client" directive to device_identifier.ts to enable client-side usage
- Remove outdated Dart source reference comment in pwa-install-overlay.tsx
2026-06-16 13:23:46 +08:00
deploy-bot d4dab061a8 fix(deploy): sync worktree to HEAD inside post-receive
The server-side .git/config sets receive.denyCurrentBranch=updateInstead,
which auto-updates the worktree but bypasses the post-receive hook entirely.
Changing it to false restores hook execution; adding git reset --hard HEAD
inside the hook compensates for the lost auto-update so the build uses the
just-pushed source code.
2026-06-16 13:17:40 +08:00
admin 50ae420871 chore(config): add /ws path suffix to WebSocket URLs
Update WebSocket base URLs across environment examples and default config to include the required `/ws` path suffix that the backend WebSocket endpoint expects. Applies to development, test/local, and production environments.
2026-06-16 12:35:45 +08:00
admin cba52884a3 chore: clean up migration comments and unused params
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.
2026-06-16 12:01:42 +08:00
admin 29776272ad fix(auth): handle null backend values and surface login errors on splash
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.
2026-06-16 11:10:21 +08:00
admin 00651861ce chore(env): update stripe publishable key placeholders in env examples 2026-06-16 11:09:20 +08:00
admin e44cc7e2f3 feat(sidebar): redesign sidebar UI with three user states (light theme)
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>
2026-06-16 10:30:21 +08:00
admin 0548a08cbb feat: integrate Stripe payment with subscription plans
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
2026-06-16 10:21:02 +08:00
admin 17741320ff Implement code changes to enhance functionality and improve performance 2026-06-15 19:13:47 +08:00
admin 6614e788fe chore(gitignore): ignore pictures directory
Add `pictures/` to .gitignore to prevent tracking of local image files that should not be committed to the repository.
2026-06-15 19:03:35 +08:00
admin 7ec76e592f Add new image assets for voice message feature
- Added ic_voicemessage_lock.png for locking voice messages.
- Added pic_bg_vip.png as a background for VIP sections.
- Added pic_popup_voicemessage.png for the voice message popup interface.
2026-06-15 18:53:47 +08:00
admin 7b347a5c30 style(sidebar): retune user area to dark theme with pink accent
Rebase the sidebar's user information area onto the project's
dark sidebar surface (--color-sidebar-background #0d0b14) and
pink accent (--color-accent #f84d96), replacing the previous
white-card / purple-gradient look that did not align with the
design.

- Shell, back link, profile card, guest panel: switch to dark
  surfaces (#1a1625) with white text.
- VIP badge: use --color-accent pink (was purple fallback).
- VIP CTA + Sign up primary link: use --color-button-gradient
  pink gradient (was purple fallback).
- Sign in secondary link: use dialog border on transparent.
- Override nested SettingsSection elements (list/row/button/svg)
  inside the sidebar's settingsArea so the dark theme carries
  through without modifying the shared core component.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-15 18:50:04 +08:00
admin 664ff67ee9 chore(githooks): simplify post-receive hook and overwrite server log
Simplify the repo toplevel detection logic and remove verbose comments from the post-receive hook. Switch next-server.log from append mode to overwrite mode so each deploy starts with a fresh log file.
2026-06-15 18:44:35 +08:00
admin fd5dfe2715 feat(sidebar): show user info with three-state UI (guest/member/vip)
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>
2026-06-15 18:11:39 +08:00
admin 1449acd6c9 chore(barrels): regenerate barrel files via barrelsby
- 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
2026-06-15 17:53:15 +08:00
Claude e88a99626d feat(subscription): add VIP subscription page
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>
2026-06-15 17:49:15 +08:00
admin b9315c9f8b refactor(chat): synchronize guest chat quota updates for messages and images 2026-06-15 17:47:35 +08:00
admin 31f2433c4b refactor: auto-init guest chat quota on first read, clean up logs
- 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`
2026-06-15 17:39:50 +08:00
admin fa694af723 docs(plan): add chat state machine refactor design
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.
2026-06-15 17:05:32 +08:00