# Halo Bestie — Client App

Flutter mobile application for end users (clients) seeking mental health support.

> See root `CLAUDE.md` for full project context and architectural decisions.

## Stack

- **Framework:** Flutter (iOS + Android)
- **Auth:** Self-managed (Phase 3.4). Anonymous-first + phone OTP + (Google / Apple when creds arrive).
  - Access token in memory on `AuthBridge`; refresh token persisted via `flutter_secure_storage`.
  - Google + Apple SDKs installed but buttons are hidden behind `--dart-define=ENABLE_SOCIAL_AUTH=true` until backend OAuth credentials exist.
  - `firebase_auth` removed; `firebase_messaging` kept for FCM push.
- **API:** Calls public Fastify backend (`/api/client/` and `/api/shared/` routes). Refresh + logout live on `shared.auth`.
- **Payment:** Xendit (paid sessions, optional trial)

## Key Concepts

- Users are **clients** — they seek mental health support ("curhat")
- Core flow: **server-issued anonymous** → optional phone/Google/Apple identity upgrade (same customer row via `anonymous_customer_id`) → browse/match with mitra → book session → chat → pay
- Anonymity toggle: if `/api/shared/config/anonymity` reports `anonymity_enabled = false`, the router shows `ForceRegisterScreen` until the user identifies

## Conventions

- Never call `/api/mitra/` or `/internal/` routes from this app
- API calls go through `ApiClient`; it auto-attaches the JWT from `AuthBridge` and auto-refreshes on 401
- WebSocket handshake (`/api/shared/ws`) reads the access token from `AuthBridge` in the first frame's `{type:"auth", token, session_id?}` message
- Use `const bool.fromEnvironment('ENABLE_SOCIAL_AUTH')` (via `social_auth_enabled.dart`) to gate any Google/Apple UI — never call `loginGoogle` / `loginApple` from a path reachable without that flag
