# Walking Skeleton — FamilySync

**Phase:** 1
**Generated:** 2026-06-04

## Capability Proven End-to-End

A member reaches the app over the real Pangolin tunnel, authenticates through Authelia (OIDC SSO), and lands on a single screen showing their name, their auto-assigned color, and one real event read from their Fastmail calendar via the CalDAV broker — proving Browser → Hono API → MariaDB cache → Fastmail all work together in the production topology.

## Architectural Decisions

| Decision            | Choice                                                                                                               | Rationale                                                                                                                |
| ------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Monorepo            | pnpm workspace, `apps/api` + `apps/pwa`                                                                              | Shared TypeScript, single repo; matches RESEARCH recommended structure                                                   |
| Backend framework   | Hono 4.12.23 on Node 22 (@hono/node-server)                                                                          | Locked in CLAUDE.md; Web-Standards-native, built-in streamSSE, RPC type sharing                                          |
| Data layer          | MariaDB 11 + Drizzle ORM 0.45.2 via mysql2 3.22.4                                                                    | Locked stack; no PostgreSQL; type-safe SQL, no binary engine (vs Prisma)                                                 |
| Schema apply        | `drizzle-kit push` against live MariaDB                                                                              | Greenfield Phase 1; push is the [BLOCKING] gate before verification (Drizzle types come from schema.ts, not the live DB) |
| Auth                | Authelia OIDC via @hono/oidc-auth 1.8.3 (authorization-code + PKCE S256, client_secret_basic)                        | Authelia already deployed; backend confidential client holds refresh token (D-12), no iframe                             |
| Identity            | `oidc_iss + oidc_sub` composite key, never email                                                                     | D-10 — email is mutable in Authelia                                                                                      |
| Member color        | Auto-assigned round-robin from a curated accessible palette, persisted on the user row                               | D-06 — stable across sessions, no settings UI in v1                                                                      |
| Calendar access     | CalDAV via tsdav 2.2.2; per-member Fastmail app passwords                                                            | D-02/D-09 — JMAP unavailable on Fastmail; per-member app passwords eliminate cross-account ACL risk                      |
| Credential storage  | AES-256-GCM (node:crypto), key from `APP_PASSWORD_ENCRYPTION_KEY` env, backend-only                                  | D-04 — encrypted at rest, never exposed to frontend                                                                      |
| Calendar cache      | `calendar_events`: raw VEVENT blob + `dtstart_utc` (timed) / `dtstart_date` (all-day) split; ctag/sync-token polling | D-13 — all-day never coerced to DATETIME (Pitfall 3); cache-first reads                                                  |
| Background sync     | node-cron 4 every 5 min, ctag change detection                                                                       | RESEARCH poller pattern; sync-token with ctag fallback from day one                                                      |
| Real-time transport | SSE (`streamSSE`); WebSocket rejected                                                                                | Pangolin WS upgrade known-broken (issue #1034); SSE smoke-tested in Phase 1 to de-risk Phase 4                           |
| Frontend            | Vite 8 + React 19; TanStack Query (server state) + Zustand (UI state)                                                | Locked stack; React Query owns server data, Zustand UI-only                                                              |
| Deployment target   | Docker Compose on Unraid, public via Pangolin/Newt tunnel (same parent domain as Authelia)                           | D-07 — validate real OIDC/HTTPS/cookie topology from day one                                                             |
| Directory layout    | `apps/api/src/{auth,broker,db,routes}`; broker is a hard module boundary                                             | RESEARCH structure; only `broker/` imports tsdav + Fastmail credentials                                                  |

## Stack Touched in Phase 1

- [x] Project scaffold (pnpm workspace, Hono + Vite, tsconfig, Dockerfile, Vitest) — Plan 01
- [x] Routing — `/health` (public), `/callback`, `/api/me`, `/api/events`, `/api/sse/heartbeat` — Plans 01/02/03/04
- [x] Database — real read AND write: `/health` round-trip (Plan 01) + broker event cache upsert + `/api/events` read (Plan 03) — schema applied via `drizzle-kit push` (Plan 01)
- [x] UI — React shell fetches `/health`, `/api/me` (member + color), and `/api/events` (EventProof) — Plans 01/02/04
- [x] Deployment — full stack through the real Pangolin tunnel; Authelia login + SSE smoke test verified live — Plan 04

## Out of Scope (Deferred to Later Slices)

- Calendar UI / views (day/week/month/agenda) — Phase 2
- Wife's app password onboarding flow + her credential — Phase 2 (encrypted credential table built now to support it)
- Event write-back (create/edit/delete) to Fastmail — Phase 3
- PWA manifest + service worker + guided iOS install — Phase 3
- Shared lists + live SSE co-edit sync (the SSE _transport_ is only smoke-tested here) — Phase 4
- Web Push notifications (VAPID) — Phase 5
- User-pickable color picker (settings UI) — deferred, v1.x
- Single-occurrence recurring edits — never in v1

## Subsequent Slice Plan

Each later phase adds one vertical slice on top of this skeleton without altering its architectural decisions:

- Phase 2: Unified color-coded read-only calendar (day/week/month/agenda) over the confirmed broker; add wife's credential
- Phase 3: Event write-back (CRUD) to Fastmail + PWA install (manifest, service worker, guided iOS onboarding)
- Phase 4: Shared named lists with item CRUD + real-time SSE co-edit sync (uses the SSE transport proven here)
- Phase 5: VAPID Web Push for event reminders, event changes, and list changes (iOS subscription health-check)
