Files
familysync/.planning/PROJECT.md
T

9.0 KiB
Raw Blame History

FamilySync

What This Is

FamilySync is a self-hosted, Dockerized family organization hub for a two-person household, modeled loosely on Skylight. It gives the family one unified, color-coded calendar and shared collaborative lists (groceries, gift ideas), reachable from a single low-friction React PWA — across a mixed Android/Fastmail and Apple ecosystem — built as one custom app on top of the family's existing paid Fastmail account. Email stays with members' existing mail clients and is out of scope.

Core Value

The household can see and co-edit one color-coded family calendar (shared + each member's personal) and shared lists from a single low-friction PWA — cross-ecosystem, no app store, no per-member calendar credential juggling.

Requirements

Validated

  • Unified, color-coded calendar view aggregating all Fastmail-hosted calendars (shared family + each member's personal) — Validated in Phase 2 (calendar-display): read-only day/week/month/agenda views, server-side recurrence expansion (DST-correct), all-day no-shift, color routing by member/shared. Operator UAT approved. (Shared/rose lane activates once a shared calendar is marked — deferred per D-16.)

Active

  • Create / edit / delete events written back to the correct Fastmail calendar via the app's single broker token
  • Shared collaborative lists (groceries, gift ideas) that both members co-edit, stored in MariaDB
  • Live list sync so co-edits appear without manual refresh (Redis optional)
  • Authelia OIDC login for every member (true SSO)
  • React PWA installable on iPhone and Android via "Add to Home Screen" (no App Store)
  • Web Push notifications for event reminders and list changes
  • Low-friction onboarding for the non-technical Apple member — visit one URL, sign in

Out of Scope

  • Wall-display / kiosk dashboard — deferred to v2; calendar + lists are the v1 core, display is the deferrable piece
  • Email features — members keep existing mail clients; never the product's job
  • Self-hosted calendar server (Baikal) — Fastmail hosts all calendars; one fewer service. Reversible if self-hosted calendar is ever wanted
  • Vikunja / external task backend — lists live in the custom MariaDB; cross-ecosystem native task sync is impossible anyway
  • DAVx5 — not needed
  • React Native / App Store publishing — PWA delivers app-like UX without publishing overhead
  • PostgreSQL — not in the existing stack; MariaDB is the database
  • Forking existing apps — the value is the aggregation/display layer, not the backends

Context

  • Household: Primary user (project owner) on Fastmail + Android, uses the Fastmail app natively. Other member (wife) on Apple (iPhone/Mac). Wife adoption is a hard UX constraint.
  • One app to build: backend + MariaDB + React PWA behind Authelia, sitting on top of Fastmail. This is far lighter than the prior Baikal + Vikunja + PWA design that was evaluated and dropped.
  • Calendars are all on Fastmail. Personal calendars are also Fastmail-hosted calendar collections, so the app reads and writes everything (shared + personal) through a single Fastmail JMAP/CalDAV broker token — no external ICS feeds, no per-member credential management. The primary user gets the shared calendar natively in the Fastmail app; Apple members use the PWA (optionally subscribe in native Apple Calendar via CalDAV).
  • Prior exploration: Architecture was revised across two sessions (explore → Opus verification). See .planning/notes/familysync-architecture.md for full reasoning behind the dropped options.
  • Open questions (for phase research):
    • Fastmail API — JMAP vs CalDAV for the app's calendar read/write. Which is cleaner to build against?
    • PWA Web Push — sufficient/reliable enough for family alerts on iOS, or is a fallback needed?
    • Mechanics of surfacing each member's personal Fastmail calendar to the broker token (calendar sharing/ACLs within Fastmail).

Constraints

  • Tech stack: MariaDB for the database — PostgreSQL is not available in the stack
  • Tech stack: Redis available (optional, for live list sync / push)
  • Infrastructure: Unraid host running Docker + Docker Compose
  • Auth: Authelia (already deployed) — OIDC/OAuth2 for the custom app; all members authenticate through it
  • Calendar backend: Fastmail (paid, existing) is the single source for all calendars via JMAP/CalDAV
  • Frontend: React PWA only — no React Native, no App Store
  • Networking: Split-DNS internal domain, private IPs internally; public exposure via Pangolin/Newt tunnel, no open ports
  • UX: Must be slick and low-friction for a non-technical Apple member — hard constraint, drives design tradeoffs
  • Scope: No email features; no forking existing apps

Key Decisions

Decision Rationale Outcome
Calendar hosted on Fastmail, not self-hosted (Baikal dropped) Fastmail reaches the whole household native-or-PWA with one fewer self-hosted service; Fastmail Android app can't show self-hosted CalDAV anyway — Pending
Personal calendars also Fastmail-hosted collections, aggregated via one broker token Avoids external ICS feeds and per-member credential management; uniform read/write path — Pending
Shared lists in custom MariaDB, not Vikunja Cross-ecosystem native task sync is impossible; a list table in the backend being built anyway is trivial vs another container + SSO integration — Pending
React PWA only, no React Native / App Store App-like UX with one codebase for all surfaces; lowest onboarding friction (one URL) — Pending
Authelia OIDC for all members True SSO consistent with existing infra; no separate calendar-credential problem — Pending
Web Push notifications in v1 (not deferred) Family alerts judged essential day one; build push into the foundation — Pending
Personal-calendar overlay in v1 (not shared-only) Unified view of everyone's schedules is the Skylight magic worth shipping early — Validated (CAL-08 GO, Phase 1): per-member app password reaches all of an account's calendars; no cross-account ACL needed
D-14: Defer Phase 1 Gate 2 (live Authelia/Pangolin verification). SSE-over-Pangolin smoke = hard gate before Phase 4; live AUTH smoke incl. iOS standalone-PWA folded into Phase 3; full 2-member prod login verified there. Phases 23 develop behind a documented dev-auth bypass. Gate 2 needs operator infra (Authelia config + tunnel) + docs that didn't exist; deferring unblocks Phase 2/3 code without rework risk, since the broker data path (CAL-01/CAL-08) is already proven live. SSE must still be verified before Phase 4 to avoid building live-sync on an unverified transport (#1034). Tracked: 01-HUMAN-UAT.md, docs/deployment.md
D-15: Validate the real external topology via a local Newt connector + test subdomain through existing Pangolin (Mode A), not an Unraid deploy. Unraid (Mode B) reserved for go-live. Authelia OIDC + SSE pass-through behaviour live in Authelia + Pangolin/Newt, not in where the origin runs — so a local Newt rig faithfully tests both, decoupling "does the topology work" from "is it in production." Newt dials outbound (no open ports). Only shared touch is an additive, reversible Authelia client. — Pending (Gate 2)
D-16 (2026-06-05, Phase 2): No dedicated Fastmail "broker" account. The shared-family calendar is a calendar collection created on the operator's primary Fastmail account (me@lucasberger.ca) and shared out to the wife + others via Fastmail's own calendar sharing. The app's single app password enumerates it like any other collection; the calendars.is_shared flag (operator-set) marks which row is the shared one. Clarified during the Wave 2 checkpoint: "broker account" was only ever the role the primary account's app password plays. id=1 ("Calendar") is the operator's personal calendar, not the shared one — so it must NOT be marked is_shared. Aggregating each other member's personal calendar still follows the D-09 per-member app-password model (open for Phase 3 onboarding: a member may get a personal color lane, or only the shared calendar). — Pending (shared calendar not yet created)

Evolution

This document evolves at phase transitions and milestone boundaries.

After each phase transition (via /gsd-transition):

  1. Requirements invalidated? → Move to Out of Scope with reason
  2. Requirements validated? → Move to Validated with phase reference
  3. New requirements emerged? → Add to Active
  4. Decisions to log? → Add to Key Decisions
  5. "What This Is" still accurate? → Update if drifted

After each milestone (via /gsd-complete-milestone):

  1. Full review of all sections
  2. Core Value check — still the right priority?
  3. Audit Out of Scope — reasons still valid?
  4. Update Context with current state

Last updated: 2026-06-03 after initialization