Expo (React Native) + WatermelonDB
Aspect	Why
Platforms	Single codebase → iOS, Android, Web (Expo for Web)
Offline-first	WatermelonDB is built for this - local SQLite, reactive queries, lazy loading
Sync	Custom sync adapter to your Postgres API (REST/GraphQL)
Team fit	React/TypeScript native
UI	Matches your spec (React Native components, Reanimated for animations)
Alternative: Expo + PowerSync
- Newer, SQL-based local DB with built-in sync protocol
- Handles conflict resolution, partial replication
- Requires PowerSync service (self-hosted or cloud) between app and your Postgres
Architecture Overview
┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   Expo App  │────▶│ WatermelonDB │────▶│  Your API   │
│ (iOS/Android│     │  (SQLite)    │     │  (Postgres) │
│    Web)     │◀───▶│  + Sync      │◀───▶│             │
└─────────────┘     └──────────────┘     └─────────────┘
Key Libraries
- Navigation: Expo Router (file-based, works on web)
- State: WatermelonDB (no Redux needed - DB is source of truth)
- UI: React Native Paper or Tamagui (cross-platform components)
- Date/Time: @react-native-community/datetimepicker + date-fns
- Forms: React Hook Form + Zod validation
Sync Strategy (Custom API)
1. WatermelonDB synchronize() pulls changes since last sync token
2. Your API exposes /sync endpoint accepting lastPulledAt cursor
3. Returns { changes: [...], serverTimestamp }
4. Push local changes via /sync/push batch endpoint
5. Handle conflicts: last-write-wins or user resolution