# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project overview

MedCare อสม. — a Thai elder-care medication and SOS management app for family caregivers and community health volunteers (อสม.). Two parts in one repo:

- `lib/` — Flutter client (Android/iOS/web/desktop)
- `server/` — Node.js/Express REST API backed by MySQL

## Commands

### Flutter (run from repo root)

- `flutter pub get` — install/update Dart dependencies
- `flutter analyze` — static analysis; should be clean before considering a change done
- `adb reverse tcp:3000 tcp:3000` — tunnel the device/emulator's own `localhost:3000` to the dev machine's backend; re-run it every time the device reconnects (unplug, reboot, `adb kill-server`)
- `flutter run` — run the app; API base URL defaults to `http://localhost:3000/api` on every platform (see `lib/api.dart`), so Android needs the `adb reverse` above
- `flutter run --dart-define=API=http://<lan-ip>:3000/api` — alternative for a real device on the same LAN without the tunnel (or edit the URL live via the "เซิร์ฟเวอร์: ..." link on the welcome screen; note it resets on app restart)
- `flutter test` — run all tests; `flutter test test/widget_test.dart` for a single file

### Backend (run from `server/`)

- `npm install` — install dependencies
- `npm start` — run the API on `:3000`
- `npm run dev` — run with `node --watch` (auto-restarts on file changes)
- `mysql -u root < schema.sql` (e.g. `"C:\xampp\mysql\bin\mysql.exe" -u root < schema.sql` on XAMPP/Windows) — create the database and seed data; MySQL/MariaDB must already be running
- `cp .env.example .env` then set `DB_USER`/`DB_PASSWORD` before first run
- `npm run db:init` — สร้างตารางจาก `schema.sql` บนเครื่องที่ไม่มี mysql CLI (ใช้ตอน deploy)
- `npm run db:dump` / `npm run db:restore -- <ไฟล์>` — ย้ายข้อมูลระหว่างเซิร์ฟเวอร์ (ดู `server/README.md`)
- Health check: `GET http://localhost:3000/api/health` → `{"ok":true,"users":N}`
- Demo accounts (password `1234` for all): `patient@test.com` / `patient2@test.com` (senior), `care@test.com` (caregiver), `vhv@test.com` (อสม.), `doctor@test.com`

## Architecture

### Client ↔ server contract

The Flutter app keeps no local persistence beyond in-memory state — every read/write goes through `lib/api.dart` (`Api.get/post/put/delete`) to the Express routes in `server/server.js`, which talk to MySQL via `server/db.js`. `lib/store.dart`'s `AppStore` (singleton `store`) is the single source of truth on the client: every screen reads from it through `ListenableBuilder(listenable: store, ...)`, and every mutation goes through an `AppStore` method that either calls the API then reloads (`_sync` helper) or updates local state directly and calls `notifyListeners()`.

`GET /api/bootstrap/:userId` is the one call that hydrates almost everything (`current`, `seniors`, `meds`, `doses`, `sos`, `appointments`, `notes`) after login/register — see `AppStore.reload()`.

### Role-based UI

`lib/main.dart`'s `RootPage` switches on `store.current.role` (`Role.senior/caregiver/vhv/doctor`, defined in `lib/models.dart`) to one of four shells: `SeniorShell` (`senior.dart`), `CaregiverShell` (`caregiver.dart`), `VhvShell` (`vhv.dart`), `DoctorShell` (`doctor.dart`). Each shell is a `StatefulWidget` with its own bottom-nav tabs; `ProfilePage` (`profile_page.dart`) and `SosPage`/`SosSentPage` (`sos_page.dart`) are shared across roles.

### Live polling

`AppStore.watchSos()`/`unwatchSos()` starts/stops a 15s `Timer` that calls `reload()`. It's used by `VhvShell` and `CaregiverShell` in `initState`/`dispose` so SOS alerts and senior GPS positions stay fresh without a manual pull-to-refresh. `SeniorShell` deliberately does not use it — a senior doesn't need to poll their own data that aggressively.

### GPS / location

Real device GPS always goes through `lib/location_service.dart` (`LocationService`, wraps the `geolocator`/`geocoding` packages) — don't read `Position` directly from a widget. Two flows:

- One-shot: `SosPage._send()` calls `LocationService.current()` to attach real coordinates to an SOS event, falling back to the profile's stored `lat`/`lng` if GPS is unavailable.
- Continuous: `AppStore.startSharingLocation()`/`stopSharingLocation()` (toggled from `ProfilePage`) subscribes to `LocationService.stream()` and pushes each update through `AppStore.updateMyLocation()` to the dedicated `PUT /api/users/:id/location` endpoint — which only touches `lat`/`lng`, unlike `PUT /api/users/:id` which overwrites the entire profile row.

### Database

`server/schema.sql` defines one `users` table covering all four roles plus `medication`, `schedule`, `med_history`, `sos_event`, `appointment`, `visit_note`, and read-oriented VIEWs (`senior`/`caregiver`/`vhv`/`doctor`) whose column names match the ER diagram and data dictionary in the thesis document (หัวข้อ 3.2.2–3.2.3) rather than the underlying `users` table — check the VIEW definitions, not just the base table, when tracing a field end to end.
