Architecture
Android UI → OfflineRepository → Room + outbox ─┐
├→ sync API → Prisma → PostgreSQL
Web UI → local actions → Dexie + outbox ───────┘
The UI reads local state. The outbox stores unsent intent; canonical state is the last known server row, not necessarily the user's current draft.
| Path | Owns |
|---|---|
apps/mobile | Kotlin/Compose Android UI, auth, Room storage, sync; experimental Wasm target |
apps/calendar-web | React/Vite/Tailwind v4 UI, IndexedDB storage, web sync |
apps/api | Auth, validation, sync application; Prisma schema and migrations |
packages/contracts | Zod wire contracts used to generate OpenAPI and Kotlin SDK |
packages/design-tokens | Shared generated design tokens |
docs | Docusaurus site, installed and built separately |
Three rules
- Calendars, events, tasks, and reminders have no separate CRUD routes.
/api/sync/*is their API; JWT ownership scopes access. - Protocol 2 checks
baseVersion, rejects stale writes, and records a replayable receipt for eachmutationId. New conflicts remain local; historicalSyncConflictendpoints are not the new conflict model. Sync details. - Store a local edit with its intent atomically, and a pull page with its cursor atomically. Preserve pending drafts and fence responses from old account sessions.
Change contracts before regenerating the Kotlin SDK; never hand-edit generated files. Database changes require migrations, including sync triggers and constraints—db:push is insufficient. Data model · commands.