Commit Graph

894 Commits

Author SHA1 Message Date
admin 851c1d4f11 Merge branch 'dev' into test 2026-06-16 19:06:19 +08:00
admin d6f2cb7c56 fix(pwa): enhance PWA install overlay logic for daily display limits and environment handling 2026-06-16 19:05:50 +08:00
admin 5be40949a1 feat(pwa): implement PwaUtil class for PWA support and installation checks 2026-06-16 18:59:17 +08:00
admin 8d786f1e04 fix(chat): align user bubble to match AI bubble for consistent layout 2026-06-16 18:58:42 +08:00
admin 88b94a9f1a Merge branch 'dev' into test 2026-06-16 18:35:26 +08:00
admin 0a9abc2502 fix(auth): stop parsing logout response body (avoids ZodError on null data)
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.
2026-06-16 18:34:55 +08:00
admin 63704a7309 Merge branch 'dev' into test 2026-06-16 18:24:49 +08:00
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 634c70a56a chore: verify local-time hook timestamps 2026-06-16 18:05:39 +08:00
admin f6735c75c8 fix(githooks): write log timestamps in local time (was UTC + Z) 2026-06-16 18:05:03 +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