Files
familysync/.planning/milestones/v1.0-phases/01-foundation-broker-spike/SKELETON.md
T

4.7 KiB

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

  • Project scaffold (pnpm workspace, Hono + Vite, tsconfig, Dockerfile, Vitest) — Plan 01
  • Routing — /health (public), /callback, /api/me, /api/events, /api/sse/heartbeat — Plans 01/02/03/04
  • 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)
  • UI — React shell fetches /health, /api/me (member + color), and /api/events (EventProof) — Plans 01/02/04
  • 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)