# PropertyLab B2C App

Flutter mobile app (Android + iOS). Flutter **3.35.7 via fvm** (`.fvmrc`), Dart 3.9.

## Commands (always via fvm — never bare `flutter`)
| Task | Command |
|---|---|
| First-time setup | `make setup` (fvm install, pub get, creates `.env.*` from example) |
| Run | `make run` (dev) · `make run ENV=staging` · `make run-prod` |
| Analyze / format / test | `make analyze` · `make format` · `make test` |
| Build | `make build-android ENV=prod` · `make build-ios ENV=prod` |
| Add package | `fvm flutter pub add <pkg>` |

Before finishing any change: `fvm dart format lib test && fvm flutter analyze && fvm flutter test` must all be clean.

## Skills
Follow `.claude/skills/flutter-best-practices` for all Dart code. Key rules are repeated below.

## Architecture
Feature-first. `lib/main.dart` → `app/app.dart` (MaterialApp.router) → `core/` (config, router, network) → `features/<name>/{data,domain,presentation}` → `shared/`.
State: Riverpod. Routing: go_router. HTTP: Dio.

## Non-negotiables
1. **Env in one place.** `lib/core/config/env.dart` (`Env`) is the only reader of `.env` / dart-defines. Add a typed getter there + the key to `.env.example` and every `.env.*`. Never hardcode URLs, keys, timeouts, feature flags.
2. **Routes in one place.** Paths in `AppRoute` enum (`core/router/app_routes.dart`); tree in `appRouterProvider` (`core/router/app_router.dart`). Navigate with `context.goNamed(AppRoute.x.name)`. No string paths in widgets.
3. **Theme in one place.** `app/theme/app_theme.dart`. No raw colors in widgets.
4. **Dio only via `dioProvider`.** Repositories wrap calls in `mapToFailure` and throw sealed `AppFailure`s (`core/errors/`); UI never sees Dio or status codes. Auth header/401 handled in `core/network/auth_interceptor.dart`; tokens only through `SessionStorage`.
5. **Tokens, not magic numbers.** `AppSpacing`/`AppRadius`/`AppDurations` in `app/theme/app_tokens.dart`.
6. Features don't import each other's `presentation/`.
7. Standard states: `AsyncValueView`, `ErrorState`, `EmptyState` from `shared/widgets/`. Logging via `AppLogger`, never `print`.
8. Keep screens under ~300 lines — split into `presentation/widgets/`.

## Flavors
`--dart-define=ENV=dev|staging|prod` picks `.env.<flavor>` (loaded as an asset — all three must exist; they're gitignored, `.env.example` is the template). VS Code launch configs exist for each.

## Adding a feature (checklist)
1. `lib/features/<name>/presentation/<name>_page.dart`
2. Add `AppRoute.<name>('/<name>')` + `GoRoute` in `app_router.dart`
3. Repository in `data/` (copy `features/home/data/health_repository.dart`), models in `domain/`, providers in `presentation/`
4. Test in `test/features/<name>/`

## Dev VM & visual loop (GCP `petav3-dev`)
This checkout lives on the GCP VM that also serves the Laravel backend. Toolchain (fvm, Android SDK + Java 17, headless Chrome, tmux) is wired by `tool/dev/env.sh` (sourced by `~/.bashrc`). The e2 machine has **no KVM → no Android emulator**; the visual loop is **Flutter Web in headless Chrome**. `tool/dev/*.sh` switch to adb automatically if an emulator ever comes online.

| Skill | Does |
|---|---|
| `/run-app [web\|android] [dev\|staging\|prod]` | (re)start `flutter run` in tmux `flutter`, open in Chrome, screenshot |
| `/reload [r\|R]` | hot reload/restart → surface run-log errors → screenshot |
| `/screenshot` | capture `/tmp/propertylab/shot.png` and look at it |
| `/ui-drive tap X Y` · `type "…"` · `key Enter` · `scroll X Y DY` · `back` | drive the UI, then screenshot |
| `/test-gate [--backend[=Filter]]` | format + analyze + test (+ pint/phpunit in peta) |
| `/build-apk [env]` · `/build-web [env]` | publish `https://wk.propertylab.com.my/builds/latest.apk` · `/app/` |
| `/backend-change` | the recipe for editing the Laravel API |

Rules
- After ANY UI change: `/reload`, then READ the screenshot. Never claim UI works without having seen it.
- Exactly one `flutter run` at a time, always via `/run-app` — never a bare `fvm flutter run`.
- Coordinates you read off a screenshot are what `/ui-drive` takes; no conversion.
- Human view: `ssh -N -L 8080:localhost:8080 <vm>` → http://localhost:8080 (live, hot-restarts show instantly), or install `latest.apk`.

## Backend (Laravel "peta") — `/var/www/html/peta`
The API contract is the source of truth; this app has no backend of its own.
- A Flutter change that needs a new/changed endpoint, field, validation rule or migration → edit peta directly (route, controller, FormRequest, Resource, migration, tests). Follow `/backend-change` every time.
- Inside peta, **its** rules win: `peta/CLAUDE.md`, `peta/GUIDELINES.md` (mandatory), `peta/CLAUDE.local.md`. Essentials: branch **`dev-wk`** only — never commit or push `master`; the working tree **is the live prod site** (`wk.propertylab.com.my`): PHP is live on save, but migrations, `config/`/`routes/` caches and Horizon workers are not → run peta's `/live-update` steps after such changes.
- Mobile API: `Route::prefix('api')` in `routes/agent-api.php` (JWT guard `api`, staff-only). B2C endpoints go in a new `routes/b2c-api.php` registered the same way — never onto the agent routes.
- No staging clone yet → all three flavors' `API_BASE_URL` point at prod (`https://wk.propertylab.com.my/api`). Migrations additive-only unless the user OKs otherwise.
- Commit the two repos separately with matching messages (`feat(auth): add otp`); Flutter after `/test-gate`, peta after its tests. **Branches: this repo commits/pushes on `main`; peta only ever on `dev-wk`** (the vibe-coding branch — never `master`). Push/deploy of peta is the user's `/sync` — never do it yourself.
- Other sessions may have uncommitted work in peta — check `git -C /var/www/html/peta status` first and leave what isn't yours alone.
