# Roadmap: FamilySync
## Milestones
- ✅ **v1.0 MVP** — Phases 1–6 (shipped 2026-06-10) — see [`milestones/v1.0-ROADMAP.md`](milestones/v1.0-ROADMAP.md)
- ✅ **v1.1 Operability & Polish** — Phases 7–20 (shipped 2026-06-18) — see [`milestones/v1.1-ROADMAP.md`](milestones/v1.1-ROADMAP.md)
- 🟡 **v1.2 Multi-Provider, Theming & Zero-Setup** — Phases 21–28 (in progress)
## Phases
✅ v1.0 MVP (Phases 1–6) — SHIPPED 2026-06-10
- [x] Phase 1: Foundation + Broker Spike (4/4 plans) — completed 2026-06-04
- [x] Phase 2: Calendar Display (5/5 plans) — completed 2026-06-05
- [x] Phase 3: Event Write-Back + PWA Install (12/12 plans) — completed 2026-06-07
- [x] Phase 4: Shared Lists + Live Sync (7/7 plans) — completed 2026-06-09
- [x] Phase 5: Web Push Notifications (8/8 plans) — completed 2026-06-10
- [x] Phase 6: UX Polish (6/6 plans) — completed 2026-06-10
Full phase detail archived in [`milestones/v1.0-ROADMAP.md`](milestones/v1.0-ROADMAP.md).
✅ v1.1 Operability & Polish (Phases 7–20) — SHIPPED 2026-06-18
- [x] Phase 7: Mobile Test Harness (4/4 plans) — completed 2026-06-11
- [x] Phase 8: Gitea CI (4/4 plans) — completed 2026-06-11
- [x] Phase 9: Faster Write-Back (2/2 plans) — completed 2026-06-12
- [x] Phase 10: Admin Role & Settings (4/4 plans) — completed 2026-06-13
- [x] Phase 11: Per-Event Reminders (5/5 plans) — completed 2026-06-14
- [x] Phase 12: Initial Setup Wizard (7/7 plans) — completed 2026-06-16
- [x] Phase 13: Real Lint Gate (ESLint) (3/3 plans) — completed 2026-06-12
- [x] Phase 14: Desktop E2E Coverage (1/1 plans) — completed 2026-06-12
- [x] Phase 15: Doc-Only CI Skip + Markdown Lint (3/3 plans) — completed 2026-06-12
- [x] Phase 16: CI Dependency Audit, Security & Image Hygiene (6/6 plans) — completed 2026-06-13
- [x] Phase 17: UI Optimization & Polish (6/6 plans) — completed 2026-06-18
- [x] Phase 18: Auto Timezone Detection (4/4 plans) — completed 2026-06-14
- [x] Phase 19: Local Auth (No-OIDC Mode) (5/5 plans) — completed 2026-06-17
- [x] Phase 20: Admin Member Editor & Form Declutter (3/3 plans) — completed 2026-06-18
Full phase detail archived in [`milestones/v1.1-ROADMAP.md`](milestones/v1.1-ROADMAP.md).
### 🟡 v1.2 Multi-Provider, Theming & Zero-Setup (Phases 21–28)
- [ ] **Phase 21: Zero-Setup DB Bootstrap** - App auto-creates/migrates its schema on boot against a bring-your-own MariaDB — no manual `db:migrate`
- [ ] **Phase 22: Provider Abstraction** - Refactor Fastmail/CalDAV behind a `CalendarProvider` interface with no Fastmail regression; widen credential uniqueness to `(user_id, provider_type)`
- [ ] **Phase 23: Multiple Reminders Per Event** - Up to 5 independent reminders per event, serialized to N VALARMs, other-client alarms preserved
- [ ] **Phase 24: Dev/Mock Provider** - In-memory provider lets the dev user + Playwright exercise the full app with no live calendar account
- [ ] **Phase 25: Google Calendar Provider** - Connect a Google account via OAuth; Google events read + write + reminders at Fastmail parity
- [ ] **Phase 26: Self-Service Provider Onboarding** - Members connect/reconnect their own provider (Fastmail app-password / Google OAuth) without admin help, with a one-tap reconnect banner
- [ ] **Phase 27: PWA Dark Mode** - Light/Dark/System theme switch, persisted, no flash-of-wrong-theme, clean across every route incl. Schedule-X
- [ ] **Phase 28: Dependency Updates** - Apply CI-surfaced outdated/vulnerable upgrades, pin-aware and per-ecosystem, with all CI gates green
## Phase Details
### Phase 21: Zero-Setup DB Bootstrap
**Goal**: A bring-your-own MariaDB (creds + DB name only) becomes a working FamilySync schema with no manual migration step — the app migrates itself on boot, idempotently and concurrency-safely, before it serves any request.
**Depends on**: Nothing (touches only `index.ts` startup + `db/migrations`)
**Requirements**: SETUP-05
**Success Criteria** (what must be TRUE):
1. Pointing the app at an empty MariaDB (DB credentials + database name supplied) and starting it produces the full, current schema with no manual `db:migrate` / `docker exec` step.
2. Restarting an already-migrated app is a no-op — no duplicate-migration error, no destructive diff, and existing data is untouched.
3. Two app instances starting against the same fresh database concurrently both come up cleanly — the migration runs exactly once (migration-lock sentinel), the loser waits rather than racing.
4. If the database is unreachable or migration fails, the app refuses to start (fatal) rather than serving on a partial schema.
5. The first request the setup wizard receives already has a complete schema — no setup-wizard call ever hits a missing table.
**Plans**: TBD
### Phase 22: Provider Abstraction (CalDAV only, Fastmail unchanged)
**Goal**: All calendar read, write-back, and reminder operations flow through one `CalendarProvider` seam, with the existing Fastmail/CalDAV broker wrapped behind it verbatim — so future providers plug in without touching the poller, outbox worker, or scheduler, and the live Fastmail path behaves identically.
**Depends on**: Phase 21 (schema auto-migrates before the integration suite runs)
**Requirements**: PROV-01
**Success Criteria** (what must be TRUE):
1. The existing Fastmail calendar continues to read, write (create/edit/delete, recurring + whole-series), and remind with no observable change — every v1.1 outbox/poller/scheduler integration test passes unchanged against the new interface.
2. Optimistic-202, create-before-delete on moves, per-uid exactly-once, the `isDraining` drain guard, and RRULE/VALARM preservation on edit all still hold (no per-provider drain locks introduced).
3. The poller and outbox worker select the implementation per member credential via a `createProvider(cred)` factory keyed on `provider_type`, with `caldav` as the default branch.
4. The `member_credentials` uniqueness constraint is widened to `UNIQUE(user_id, provider_type)` (migrated, no data loss), unblocking one credential per provider per user, and the token-storage shape (separate `provider_tokens` table vs. extended `member_credentials`) is decided and recorded.
**Plans**: TBD
### Phase 23: Multiple Reminders Per Event
**Goal**: A user can set several independent reminders on one event (up to 5), each firing once at its own lead, serialized to the connected provider's native model, without clobbering reminders authored in another client.
**Depends on**: Phase 22 (the CalDAV provider's write path accepts a reminder-leads array)
**Requirements**: CAL-18
**Success Criteria** (what must be TRUE):
1. In the event form a user can add and remove multiple reminder rows (up to 5); on edit, all existing reminders pre-populate.
2. Each reminder fires independently and exactly once — the scheduler dedup key includes the lead (`uid:dtstartMs:lead`), so two reminders on the same event don't collapse or double-fire.
3. Saving an event writes one VALARM per chosen lead to Fastmail (multiple sub-components in the VCALENDAR); reminders authored in another client are preserved on edit (preserve-vs-replace driven by a remindersChanged signal).
4. Existing single-reminder events keep their reminder after the schema migration (the old `reminder_lead_minutes` value is migrated into the new `reminder_leads` JSON array before the old column is dropped).
**Plans**: TBD
### Phase 24: Dev/Mock Provider (full-app exercise without a live calendar)
**Goal**: The dev-bypass user and the Playwright CI harness can exercise the entire app — calendar create/edit/delete, reminders, and lists — with no real calendar account connected, against deterministic seed data.
**Depends on**: Phase 22 (the `CalendarProvider` interface + factory); benefits from Phase 23 (reminder model) for reminder coverage
**Requirements**: TEST-03
**Success Criteria** (what must be TRUE):
1. Under `DEV_AUTH_BYPASS` (dev user, `provider_type='mock'` / no credential row), the calendar populates with seeded events — recurring, all-day, past, and future — with no live provider and no network I/O.
2. The dev user can create, edit, and delete events and set reminders end-to-end through the UI; the mock provider absorbs the outbox writes and reflects them back.
3. The Playwright harness exercises calendar CRUD hermetically (no live Fastmail/Google account), so CI can validate the full flow.
4. The mock path is strictly dev-only (same hard production guard as the existing dev bypass) and never activates in a production image.
**Plans**: TBD
### Phase 25: Google Calendar Provider
**Goal**: A member can connect a Google account and have their Google calendar reach full Fastmail parity — events appear in the unified color-coded view (recurrence + all-day correct), and create/edit/delete (including recurring + whole-series) and reminders write back to Google.
**Depends on**: Phase 22 (provider interface), Phase 23 (reminder serialization contract), Phase 21 (token table exists after migrate-on-boot)
**Requirements**: CAL-16, CAL-17, PROV-02
**Success Criteria** (what must be TRUE):
1. After connecting a Google account, that calendar's events appear in the unified color-coded calendar — recurring events expand correctly (Google recurrence/cancelled-instance model reconciled to the app's RRULE/EXDATE model) and all-day events are not shifted.
2. A user can create, edit, and delete events on the connected Google calendar from the app — including recurring-event create and whole-series delete — written back to Google.
3. Reminders set in the app serialize to Google `overrides` (and parse back), capped at 5, preserving other-client alarms.
4. The OAuth refresh token is stored AES-256-GCM encrypted (never in `app_config`), via the `UNIQUE(user_id, provider_type)` model; access tokens refresh inline and a Google `syncToken` 410 triggers a safe full re-sync without overwriting un-drained local writes.
5. The Google OAuth callback is mounted pre-auth (distinct path + distinct state cookie, redirect URI from `EXTERNAL_BASE_URL`) and round-trips correctly through the Pangolin hostname.
**Plans**: TBD
**UI hint**: yes
### Phase 26: Self-Service Provider Onboarding
**Goal**: A member can connect, re-enter, or reconnect their own provider credential using each provider's native mechanism — Fastmail app-password entry, Google "Connect Google" OAuth — without an admin acting for them, and a clear in-calendar banner makes a broken connection a one-tap fix.
**Depends on**: Phase 25 (the Google OAuth authorize/callback must exist before the UI redirects to it)
**Requirements**: PROV-03, PROV-04
**Success Criteria** (what must be TRUE):
1. A member sees which providers they have connected and can self-service connect their own credential — Fastmail via an app-password form (live-validated), Google via a single "Connect Google Calendar" button — with no admin involvement.
2. When a provider connection expires or is revoked, the user sees a clear banner in the calendar view (not buried in settings), not a silent failure.
3. From that banner the user can reconnect in one tap by re-running the provider's auth flow, without removing and re-adding the account.
4. A member can disconnect a provider, which removes its credential/tokens and its synced calendars + events.
**Plans**: TBD
**UI hint**: yes
### Phase 27: PWA Dark Mode
**Goal**: A user can switch the PWA between Light / Dark / System, the choice persists, and the dark theme renders cleanly everywhere with no flash-of-wrong-theme on load.
**Depends on**: Nothing (pure frontend; parallelizable with Phases 22–26)
**Requirements**: THEME-01
**Success Criteria** (what must be TRUE):
1. A theme toggle in Settings lets the user pick Light / Dark / System; the choice is persisted (per-device localStorage) and survives reload.
2. On load there is no flash-of-wrong-theme — the resolved theme is applied before first paint (inline pre-paint script reading the stored preference).
3. The dark theme renders cleanly across every route — calendar, lists, admin, settings sheet, and login — including the Schedule-X calendar component's `--sx-color-*` variables and the per-member color lanes.
4. "System" tracks the OS `prefers-color-scheme` and updates live when the OS theme changes; the iOS `theme-color` meta reflects the active theme.
**Plans**: TBD
**UI hint**: yes
### Phase 28: Dependency Updates
**Goal**: The outdated/vulnerable dependency upgrades that CI surfaces are applied — selectively and pin-aware — leaving the app on current, non-CVE-bearing dependencies with every CI gate green.
**Depends on**: Phase 27 (Schedule-X is not bumped until dark mode is locked); runs last so all other v1.2 work is already merged
**Requirements**: DEP-01
**Success Criteria** (what must be TRUE):
1. The CI dependency report's outdated/vulnerable packages are upgraded (no bulk `pnpm up` / `audit --fix`), per-ecosystem and pin-aware — ESLint held at 9.x until `eslint-plugin-react` supports 10, Drizzle orm + drizzle-kit bumped together, Schedule-X minor-only.
2. After the updates, all CI gates pass — lint, typecheck, unit, API integration, the Playwright harness, audit, and image hygiene are green.
3. No runtime regression to the calendar, lists, push, provider, or theming paths after the bumps (existing tests + harness still pass).
**Plans**: TBD
## Progress
| Phase | Milestone | Plans Complete | Status | Completed |
| ----- | --------- | -------------- | -------- | ---------- |
| 1. Foundation + Broker Spike | v1.0 | 4/4 | Complete | 2026-06-04 |
| 2. Calendar Display | v1.0 | 5/5 | Complete | 2026-06-05 |
| 3. Event Write-Back + PWA Install | v1.0 | 12/12 | Complete | 2026-06-07 |
| 4. Shared Lists + Live Sync | v1.0 | 7/7 | Complete | 2026-06-09 |
| 5. Web Push Notifications | v1.0 | 8/8 | Complete | 2026-06-10 |
| 6. UX Polish | v1.0 | 6/6 | Complete | 2026-06-10 |
| 7. Mobile Test Harness | v1.1 | 4/4 | Complete | 2026-06-11 |
| 8. Gitea CI | v1.1 | 4/4 | Complete | 2026-06-11 |
| 9. Faster Write-Back | v1.1 | 2/2 | Complete | 2026-06-12 |
| 10. Admin Role & Settings | v1.1 | 4/4 | Complete | 2026-06-13 |
| 11. Per-Event Reminders | v1.1 | 5/5 | Complete | 2026-06-14 |
| 12. Initial Setup Wizard | v1.1 | 7/7 | Complete | 2026-06-16 |
| 13. Real Lint Gate (ESLint) | v1.1 | 3/3 | Complete | 2026-06-12 |
| 14. Desktop E2E Coverage | v1.1 | 1/1 | Complete | 2026-06-12 |
| 15. Doc-Only CI Skip + MD Lint | v1.1 | 3/3 | Complete | 2026-06-12 |
| 16. CI Dep Audit, Sec & Img Hyg | v1.1 | 6/6 | Complete | 2026-06-13 |
| 17. UI Optimization & Polish | v1.1 | 6/6 | Complete | 2026-06-18 |
| 18. Auto Timezone Detection | v1.1 | 4/4 | Complete | 2026-06-14 |
| 19. Local Auth (No-OIDC Mode) | v1.1 | 5/5 | Complete | 2026-06-17 |
| 20. Admin Member Editor & Declutter | v1.1 | 3/3 | Complete | 2026-06-18 |
| 21. Zero-Setup DB Bootstrap | v1.2 | 0/? | Not started | - |
| 22. Provider Abstraction | v1.2 | 0/? | Not started | - |
| 23. Multiple Reminders Per Event | v1.2 | 0/? | Not started | - |
| 24. Dev/Mock Provider | v1.2 | 0/? | Not started | - |
| 25. Google Calendar Provider | v1.2 | 0/? | Not started | - |
| 26. Self-Service Provider Onboarding | v1.2 | 0/? | Not started | - |
| 27. PWA Dark Mode | v1.2 | 0/? | Not started | - |
| 28. Dependency Updates | v1.2 | 0/? | Not started | - |
## Dependencies & Critical Path (v1.2)
```
21 (DB Bootstrap) ──► 22 (Provider Abstraction) ──┬─► 23 (Multiple Reminders) ──┐
│ ├─► 25 (Google) ──► 26 (Onboarding)
└─► 24 (Mock Provider) ───────┘
27 (Dark Mode) — independent, parallelizable with 22–26
28 (Dependency Updates) — last; after 27 (Schedule-X hold) and all other v1.2 work
```
- **Critical path:** 21 → 22 → 23 → 25 → 26, then 28 last.
- **Parallelizable:** 24 after 22 (alongside 23/25); 27 anytime.
- **HIGH-risk gate:** Phase 22 must not regress the live Fastmail path — golden-path integration tests pass unchanged before 23/24/25 build on it.
## Backlog
### Phase 999.21: PWA modern visual styling refresh — contemporary look across the app (BACKLOG)
**Goal:** [Captured for future planning] A broader "more modern, visually appealing" styling pass across the PWA — beyond the bounded in-system polish of Phase 17. Candidate scope: a contemporary refresh of high-visibility surfaces (login, calendar shell, event form, lists, admin), revisiting elevation/shadows, radii, spacing rhythm, typography scale, and control states, potentially reworking specific component layouts. Explicitly **flagged for a future milestone**, not v1.2 — it is a visual-overhaul track with real redesign risk and should be scoped/sequenced on its own rather than bolted onto a polish phase. Best sequenced after the Phase 17 token groundwork and Phase 27 (dark mode) so the refresh is theme-aware from the start.
**Context:** Deferred out of Phase 17 (2026-06-17) during `/gsd-discuss-phase 17`. The user scoped Phase 17 to layout polish + branding (logo/favicon/icon assets) + theme-token groundwork, and routed the open-ended styling refresh here for a future milestone to avoid an unbounded redesign inside a polish phase. Related: Phase 17 (the polish baseline), Phase 27 / 999.20 (dark mode / theming). Tags: pwa, ui, styling, redesign, design-system, future-milestone.
**Requirements:** TBD
**Plans:** 0 plans
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
---
_Backlog items 999.1 (provider abstraction), 999.5 (self-service onboarding), 999.18 (CI dependency updates), 999.19 (dev-user full-app exercise), and 999.20 (dark mode) were promoted into v1.2 Phases 22, 26, 28, 24, and 27 respectively. Historical promotion provenance for v1.0/v1.1 backlog items (999.1/4/5/10/11/12/13/14/15/16) is preserved in `milestones/v1.1-ROADMAP.md`._