docs: create milestone v1.2 roadmap (8 phases)

This commit is contained in:
Lucas Berger
2026-06-19 14:09:29 -04:00
parent 67430bb670
commit 700294a643
3 changed files with 171 additions and 253 deletions
+25 -14
View File
@@ -59,20 +59,31 @@ Each requirement maps to exactly one roadmap phase (see Traceability).
## Traceability ## Traceability
Maps each REQ-ID to its phase. v1.2 phases continue prior numbering (v1.1 ended at Phase 20) → v1.2 starts at **Phase 21**. _Filled by the roadmapper._ Maps each REQ-ID to its phase. v1.2 phases continue prior numbering (v1.1 ended at Phase 20) → v1.2 starts at **Phase 21**.
| REQ-ID | Phase | Status | | REQ-ID | Phase | Status |
| -------- | ----- | ----------- | | -------- | -------- | ----------- |
| SETUP-05 | TBD | Not started | | SETUP-05 | Phase 21 | Not started |
| PROV-01 | TBD | Not started | | PROV-01 | Phase 22 | Not started |
| PROV-02 | TBD | Not started | | CAL-18 | Phase 23 | Not started |
| PROV-03 | TBD | Not started | | TEST-03 | Phase 24 | Not started |
| PROV-04 | TBD | Not started | | CAL-16 | Phase 25 | Not started |
| CAL-16 | TBD | Not started | | CAL-17 | Phase 25 | Not started |
| CAL-17 | TBD | Not started | | PROV-02 | Phase 25 | Not started |
| CAL-18 | TBD | Not started | | PROV-03 | Phase 26 | Not started |
| THEME-01 | TBD | Not started | | PROV-04 | Phase 26 | Not started |
| TEST-03 | TBD | Not started | | THEME-01 | Phase 27 | Not started |
| DEP-01 | TBD | Not started | | DEP-01 | Phase 28 | Not started |
**Coverage:** 11 requirements, mapping pending roadmap creation. **Coverage:** 11/11 requirements mapped to exactly one phase — no orphans, no duplicates.
| Phase | Requirements |
| ----- | ------------ |
| Phase 21 — Zero-Setup DB Bootstrap | SETUP-05 |
| Phase 22 — Provider Abstraction | PROV-01 |
| Phase 23 — Multiple Reminders Per Event | CAL-18 |
| Phase 24 — Dev/Mock Provider | TEST-03 |
| Phase 25 — Google Calendar Provider | CAL-16, CAL-17, PROV-02 |
| Phase 26 — Self-Service Provider Onboarding | PROV-03, PROV-04 |
| Phase 27 — PWA Dark Mode | THEME-01 |
| Phase 28 — Dependency Updates | DEP-01 |
+133 -230
View File
@@ -4,8 +4,7 @@
-**v1.0 MVP** — Phases 16 (shipped 2026-06-10) — see [`milestones/v1.0-ROADMAP.md`](milestones/v1.0-ROADMAP.md) -**v1.0 MVP** — Phases 16 (shipped 2026-06-10) — see [`milestones/v1.0-ROADMAP.md`](milestones/v1.0-ROADMAP.md)
-**v1.1 Operability & Polish** — Phases 720 (shipped 2026-06-18) — see [`milestones/v1.1-ROADMAP.md`](milestones/v1.1-ROADMAP.md) -**v1.1 Operability & Polish** — Phases 720 (shipped 2026-06-18) — see [`milestones/v1.1-ROADMAP.md`](milestones/v1.1-ROADMAP.md)
- 🟡 **v1.2 Multi-Provider, Theming & Zero-Setup** — Phases 2128 (in progress)
> Next milestone not yet defined — start with `/gsd-new-milestone`.
## Phases ## Phases
@@ -45,6 +44,111 @@ Full phase detail archived in [`milestones/v1.1-ROADMAP.md`](milestones/v1.1-ROA
</details> </details>
### 🟡 v1.2 Multi-Provider, Theming & Zero-Setup (Phases 2128)
- [ ] **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 2226)
**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 ## Progress
| Phase | Milestone | Plans Complete | Status | Completed | | Phase | Milestone | Plans Complete | Status | Completed |
@@ -69,240 +173,36 @@ Full phase detail archived in [`milestones/v1.1-ROADMAP.md`](milestones/v1.1-ROA
| 18. Auto Timezone Detection | v1.1 | 4/4 | Complete | 2026-06-14 | | 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 | | 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 | | 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 2226
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 ## Backlog
### Phase 999.1: Treat Fastmail as one calendar provider; framework supports adding more providers (BACKLOG)
**Goal:** [Captured for future planning] Abstract the calendar backend behind a provider interface so Fastmail/CalDAV is one implementation among potentially many. Shipping with a single provider is fine, but the broker, sync, and event-expansion layers should be structured so additional providers (e.g. other CalDAV hosts, Google Calendar, generic ICS feeds) can be added without rework. Captures the "provider" seam as an explicit architectural concern.
**Requirements:** TBD
**Plans:** 6/6 plans complete
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
### Phase 999.4: Per-event reminder configuration (VALARM authoring + scheduler honors it) (BACKLOG)
**Goal:** [Captured for future planning] End-to-end per-event reminders — let the user choose *when* (or whether) to be reminded per event, and make the push scheduler honor that choice instead of a hardcoded lead.
**Half A — author the VALARM (event form):** The event create/edit form has no UI to set a reminder ("remind me 10 min / 1 hour / 1 day before", or **no reminder**), so the written `.ics` carries no `VALARM` and no reminder can fire — in native clients or via web push. Add a reminder selector (including an explicit "none"), serialize chosen offsets as `VALARM` (TRIGGER) on write-back, and parse existing `VALARM`s on read so edits preserve them. Feeds the Phase 5 web-push requirement (push needs reminder data to notify about).
**Half B — scheduler honors the provider's value (NEW, surfaced 2026-06-10):** Today `apps/api/src/broker/reminderScheduler.ts` runs a **hardcoded 15-minute** scan for shared timed events (`index.ts:139` "starting in ~15 min"; reminderScheduler header "15-min reminder scan") and never reads the event's actual alarm. So every reminder fires 15 min before regardless of what the event (or the calendar provider) specifies, and an event with **no** alarm still gets a 15-min push. Change the scheduler to read each event's `VALARM` `TRIGGER` (the value written in Half A / set in Fastmail or another native client) and fire at that lead — and fire **nothing** when the event has no alarm. The current fixed 15-min window/dedup logic (catch-up scan, per-uid exactly-once — see quick 260610-hbu) must be generalized to a variable per-event lead.
**Boundary:** preserve the reminder scheduler's resilience guarantees (catch-up on a missed tick, per-uid exactly-once dedup). This makes the lead per-event/variable rather than constant; it is not a rewrite of the scan/dedup design.
**Severity:** medium — feature gap surfaced during Phase 03 Gate 2 testing; Half B surfaced 2026-06-10. Tags: phase-03, phase-05, calendar, write-back, reminders, valarm, push, scheduler, phase-05-dependency.
**Requirements:** TBD
**Plans:** 0 plans
> **Promoted into v1.1 Phase 11 (Per-Event Reminders) — CAL-13/CAL-14/NOTIF-04/05/06.** Backlog entry retained for history.
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
### Phase 999.10: Admin Settings / Administration section — manage app passwords + designate the shared calendar via UI (BACKLOG)
**Goal:** [Captured for future planning] Add an in-app **Settings/Administration** section, gated to an administrator role, for configuration that today requires manual backend/DB steps:
- **View/update per-member Fastmail app passwords** (stored encrypted via `APP_PASSWORD_ENCRYPTION_KEY`, existing crypto path) — rotate or re-enter a member's credential and re-trigger sync.
- **Designate which synced calendar is the "shared" calendar** by toggling `calendars.is_shared` from the UI. Today this is a manual DB write: e.g. `UPDATE calendars SET is_shared=1 WHERE id=<row>` — done by hand on 2026-06-10 to mark the "FamilySync" calendar (id 10) shared after the poller synced it (D-16). The admin should pick the shared calendar from a list of synced collections instead of relying on a backend process. (The poller's upsert already leaves `is_shared` untouched, so a UI-set flag persists.)
**Context:** Motivated by the manual D-16 resolution (2026-06-10). **Related:** 999.5 (per-member first-login app-password onboarding) — this is the ongoing admin-managed counterpart; and 999.11 (initial setup wizard) — bootstrap-time vs. ongoing config. Tags: admin, settings, calendar, app-passwords, D-16.
> **Promoted into v1.1 Phase 10 (Admin Role & Settings) — ADMIN-01/ADMIN-02/ADMIN-03.** Backlog entry retained for history.
**Requirements:** TBD
**Plans:** 0 plans
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
### Phase 999.11: Initial setup wizard — first-run config of env vars, app passwords, DB connection (BACKLOG)
**Goal:** [Captured for future planning] Add a first-run **setup wizard** that walks the administrator through defining all bootstrap configuration instead of hand-editing `.env` / `docker-compose.yml`:
- **App environment variables:** OIDC client id/secret/issuer/redirect URI + external URL, session signing secret (`OIDC_AUTH_SECRET`), `APP_PASSWORD_ENCRYPTION_KEY`, and the **VAPID keypair** (subject + public + private).
- **MariaDB connection:** host/port/user/password/db, with a connectivity test.
- **First Fastmail app password** for the initial member, encrypted on save.
Wizard should **validate inputs before completing** — e.g. VAPID private key decodes to 32 bytes AND pairs with the public key, OIDC discovery resolves, DB connects, app-password reaches CalDAV.
**Context:** Motivated by setup friction observed 2026-06-10 — a VAPID private key truncated on paste into `.env` silently broke push (`setVapidDetails failed — 32 bytes`), and `DB_HOST` / dev overrides must currently be set by hand. A guided + validated wizard would have caught these. **Related:** 999.10 (ongoing admin Settings) and 999.5 (member onboarding). Tags: onboarding, setup, install, env, vapid, mariadb, oidc.
> **Promoted into v1.1 Phase 12 (Initial Setup Wizard) — SETUP-01/02/03/04.** Backlog entry retained for history.
**Requirements:** TBD
**Plans:** 0 plans
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
### Phase 999.12: Assistant-driven mobile-browser UI testing (mobile viewport + authed PWA) (BACKLOG)
**Goal:** [Captured for future planning] Give the assistant a way to validate UI/UX changes in a **mobile** browser experience, not just desktop Chromium. Today `playwright-cli` drives a desktop viewport, and the prod stack enforces OIDC (Authelia) so the authed PWA can't be reached headlessly — which is exactly why a string of mobile-only defects this milestone (silent Android notifications, the dead "How to enable" link, iOS/Android session-cookie persistence, install/standalone behaviour) could only be found by the operator on real devices, not by the assistant.
**What this needs (any subset):**
- **Mobile viewport + UA emulation** in the browser harness (e.g. Playwright device descriptors — iPhone/Pixel viewport, touch, mobile user-agent) so layout, tap targets, and responsive behaviour can be checked.
- **An authenticated entry path for automated runs** so the assistant can reach the real PWA past Authelia — e.g. a reusable saved storage-state/cookie, a test-only bypass on a non-prod host, or driving the Authelia login once and reusing the session. (Note: this overlaps the existing `DEV_AUTH_BYPASS`, but that only works on the host-side dev stack, not the prod-mode PWA that has the real service worker. A mobile, authed, SW-enabled target is the gap.)
- Optionally: a documented way to point the harness at the Pangolin HTTPS URL with a persisted session, and/or remote-debug a real device.
**Boundary:** genuinely device-only behaviour (iOS-Safari standalone push, real APNs/FCM delivery, OS notification-channel importance) still needs a human — this item is about everything SHORT of that (responsive layout, tap flows, in-page notification UI states, auth redirects) which a mobile-emulated authed browser *could* cover but currently can't.
**Context:** Surfaced 2026-06-10 during Phase 5 UAT — repeated mobile-only bugs were caught only by the operator because the assistant had no mobile, authenticated browser to test in. **Related:** [[feedback-playwright-verify]] (use playwright-cli over manual verification — this extends it to mobile/authed). Tags: testing, playwright, mobile, pwa, oidc, dx.
> **Promoted into v1.1 Phase 7 (Mobile Test Harness) — TEST-01/TEST-02.** v1.1 scopes the `DEV_AUTH_BYPASS` dev-build path; the prod-SW authed-mobile target stays deferred. Backlog entry retained for history.
**Requirements:** TBD
**Plans:** 0 plans
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
### Phase 999.13: Reduce event write-back latency to the calendar provider (outbox drain) (BACKLOG)
**Goal:** [Captured for future planning] Calendar create/edit/delete writes are enqueue-only (`calendarOutbox`, 202 optimistic-accept; D-12/D-05 — no Fastmail call in the route) and flushed to Fastmail by `runOutboxDrain` on a **15-second `setInterval`** (`apps/api/src/broker/outboxWorker.ts`). So a change can take up to ~15s to land in Fastmail (and longer to reflect back in the app, which depends on the separate 5-min poller). Reduce that perceived sync delay so edits feel near-immediate.
**Options to weigh when picking this up:**
- **Event-driven drain (preferred):** trigger an outbox drain immediately after a successful enqueue (in-process signal, or Redis pub/sub which is already available) so the write fires within ~1s instead of waiting for the next tick — keep the 15s `setInterval` as a fallback/retry sweep. Must preserve the existing per-row etag/412 handling and the rapid-successive-edit ordering (see outboxWorker comments ~L312 — each edit carries its enqueue-time etag).
- **Shorter interval:** simplest, but more idle DB polling; a floor (e.g. 35s) trades latency for load.
- **Faster read-back too:** the user also sees latency from the 5-min poller reflecting the change back. Consider invalidating/short-poll after a local write, or optimistic UI already covering it — confirm whether the perceived delay is the write (15s) or the read-back (5min).
**Boundary:** the optimistic 202 + outbox durability design (create-before-delete, drain concurrency guard, fresh-etag-before-PUT) must be preserved — this is a latency tune, not a rewrite of the write path.
**Context:** Surfaced 2026-06-10. Tags: calendar, write-back, outbox, latency, redis, performance.
> **Promoted into v1.1 Phase 9 (Faster Write-Back) — CAL-15.** In-process EventEmitter chosen (not Redis); the drain is single-process by design. Backlog entry retained for history.
**Requirements:** TBD
**Plans:** 0 plans
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
### Phase 999.14: Gitea CI — full regression on PR to main + build/publish Docker image (BACKLOG)
**Goal:** [Captured for future planning] The repo is committed against a self-hosted Gitea instance with a registered Actions runner, but there is no CI yet (no `.gitea/workflows/` or `.github/workflows/`). Two things should run automatically: (1) **full regression** on every PR targeting `main` — gating the merge; (2) **build the app's Docker image and publish it** to the Gitea container registry.
**Options / decisions to make when picking this up:**
- **Test scope:** "full regression" = lint + typecheck + unit + the API integration tests. Integration tests need a real MariaDB (see [[api-integration-test-db]]) — the workflow must spin up a MariaDB service container, bind it, and set `DB_HOST=127.0.0.1` + `.env` creds. The PWA build/test also runs.
- **Monorepo:** pnpm workspace (`apps/api`, `apps/pwa`, shared). Cache the pnpm store.
- **Docker images:** only `apps/api/Dockerfile` exists today — there is no PWA Dockerfile yet. Decide one image (API) vs. also building/serving the PWA. Tag scheme + when to publish (only on merge to `main`? on tags? per-PR?).
- **Registry auth:** push to the Gitea registry using the runner's Gitea-provided token or a dedicated package-write token.
- Gitea Actions are GitHub-Actions-compatible syntax but run on the self-hosted runner — confirm runner labels and available images, and that Actions is enabled, before authoring.
**Likely shape:** a `.gitea/workflows/ci.yml``on: pull_request` (to `main`) → install (pnpm), lint, typecheck, unit, API integration vs. a `mariadb` service container, PWA build; `on: push` to `main`/tag → `docker build apps/api/Dockerfile`, login, push tagged image.
**Context:** Promoted from STATE.md pending todo (`.planning/todos/pending/2026-06-10-gitea-ci-regression-and-docker-publish.md`), surfaced 2026-06-10. Tags: tooling, ci, gitea, docker, mariadb, monorepo.
> **Promoted into v1.1 Phase 8 (Gitea CI) — CI-01/CI-02.** v1.1 also extends CI-01 to run the Phase 7 mobile harness as a UI-regression step (CI brings up the dev stack in the runner). Backlog entry retained for history.
**Requirements:** TBD
**Plans:** 0 plans
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
### Phase 999.15: Desktop e2e coverage — add a Desktop Playwright profile + desktop-safe specs (BACKLOG)
**Goal:** [Captured for future planning] The Playwright harness (`apps/pwa/playwright.config.ts`) defines only **mobile** device profiles — `iphone` (iPhone 14 / WebKit) and `pixel` (Pixel 7 / Chromium), both with touch and a mobile viewport. The Phase 8 CI regression gate runs `pnpm test:e2e`, so it currently validates the **mobile experience only**. Add desktop coverage so the regression gate exercises the desktop layout/flows as well.
**Options / decisions to make when picking this up:**
- **Add a Desktop profile:** a new `desktop` project in `playwright.config.ts` (e.g. `devices['Desktop Chrome']`, no `hasTouch`, wide viewport). Optionally a Desktop WebKit/Safari profile too — but the family's Apple member is already covered on mobile Safari via `iphone`; Desktop Chrome is likely sufficient for a shared/wall browser.
- **Spec-compat pass (the real work):** the existing e2e specs were authored for mobile — they may assume touch gestures, a mobile nav/drawer, or mobile-only layout. Each spec needs review/adjustment so it passes (or is appropriately skipped) on a no-touch, wide-viewport desktop. This is harness/spec work, not CI plumbing.
- **Gating choice:** decide whether desktop runs block the merge immediately, or run advisory (non-blocking) until the specs are confirmed desktop-safe.
**Boundary:** Phase 8 deliberately reused the Phase 7 harness **unchanged** (CI owns only stack bring-up + readiness waits, not spec content), which is why this was deferred. Once a Desktop project is added to the config, Phase 8 CI picks it up automatically via `pnpm test:e2e` — no CI changes needed beyond whatever runtime/wait the desktop profile requires.
**Context:** Deferred from Phase 8 (Gitea CI) planning, 2026-06-11 — user wants both mobile and desktop validated, but desktop needs a config addition + spec review that is out of Phase 8's CI-plumbing scope. Tags: testing, playwright, e2e, desktop, harness, ci.
> **Promoted into v1.1 Phase 14 (Desktop E2E Coverage) — 2026-06-11.** Backlog entry retained for history.
**Requirements:** TBD
**Plans:** 0 plans
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
### Phase 999.16: Wire a real linter (ESLint) so the CI lint gate actually fails on violations (BACKLOG)
**Goal:** [Captured for future planning] The Phase 8 CI `fast-checks` job runs `pnpm lint`, but **no linter exists** in the repo — the root `lint` script is `pnpm -r --if-present lint`, which finds no package-level lint script and exits 0. The lint gate is a hollow placeholder that can never fail. Wire up a real linter so it runs and gates merges on lint violations. (`typecheck`/tsc already gates type errors meanwhile.)
**Options / decisions to make when picking this up:**
- **Tooling:** ESLint flat config (`eslint.config.js`) with `typescript-eslint`; add React + react-hooks plugins for `apps/pwa`. Add `eslint` (+ plugins) as devDeps and a `lint` script to `apps/api` and `apps/pwa``pnpm -r --if-present lint` then picks them up automatically, no CI change needed.
- **Rule strictness:** pick a baseline (recommended vs strict-type-checked). Stricter = more upfront violations to fix.
- **Violation cleanup (the real work):** the first run surfaces existing violations across both apps. Decide per-rule: fix, downgrade to warn, or disable. The gate must end green.
- **Gating choice:** blocking on merge immediately, or advisory (warn-only) until the codebase is clean.
**Boundary:** Phase 8 deliberately scoped lint wiring out (CI-plumbing-only); it shipped the gate slot wired to auto-activate once a package `lint` script lands. This item is that follow-up.
**Context:** Raised during Phase 8 execution, 2026-06-11 — user noted the `--if-present` lint step "didn't fix the linter, just made it so it didn't have to exist to proceed" and wants a lint gate that actually fails. Tags: ci, lint, eslint, typescript-eslint, quality, gitea.
> **Promoted into v1.1 Phase 13 (Real Lint Gate / ESLint) — 2026-06-11.** Backlog entry retained for history.
**Requirements:** TBD
**Plans:** 0 plans
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
### Phase 999.18: Update dependencies as found during CI (BACKLOG)
**Goal:** [Captured for future planning] When the CI dependency-audit gate (Phase 16) surfaces outdated or vulnerable packages, bump them rather than letting the report accumulate. Establish a lightweight, recurring "act on the CI dependency report" loop so the two-person household app doesn't drift onto stale/CVE-bearing deps. Scope is the upkeep workflow (review → bump → verify gate green), not a one-time audit.
**Context:** Captured 2026-06-13 during Phase 10 work. Companion to the audit *reporting* shipped in Phase 16 (CI Dependency Audit) — that phase makes outdated/vulnerable deps *visible*; this item is the standing follow-through to *resolve* what it finds. Tags: ci, dependencies, maintenance, security, upkeep.
**Requirements:** TBD
**Plans:** 0 plans
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
### Phase 999.19: Dev user exercises full app functionality without syncing to a real calendar (BACKLOG)
**Goal:** [Captured for future planning] Let the `DEV_AUTH_BYPASS` dev user (currently hardcoded `DEV_USER` id 1 in `apps/api/src/auth/devBypass.ts`) exercise the full app — create/edit/delete events, set per-event reminders, manage lists — against a local/in-app calendar store, WITHOUT requiring a connected Fastmail/CalDAV provider and WITHOUT writing anything to a real calendar. Today the dev user has no `member_credentials` row and no `calendars`, so `writable-calendars` is empty and `POST /api/events/create` returns `422 "No writable calendar found for user"` — making hands-on UAT of event/reminder features impossible in dev. Options to explore: seed the dev user a fake local calendar + short-circuit the outbox/CalDAV write path under dev-bypass (no Fastmail round-trip), or a dev-only in-memory calendar provider. Must stay strictly dev-only (same hard `NODE_ENV !== 'production'` guard) and never ship in production images.
**Context:** Captured 2026-06-14 during Phase 11 (Per-Event Reminders) UAT. The reminder picker and backend were verified via automated tests + a route-mocked playwright smoke, but the operator could not manually create an event to see reminders end-to-end because no provider is connected in the dev DB (`needsProviderSetup: true`). This is a recurring dev-testability friction (see MEMORY: "Dev user 1 has no calendars"). Tags: dev-tooling, dev-bypass, testability, calendars, outbox, uat.
**Requirements:** TBD
**Plans:** 0 plans
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
### Phase 999.20: PWA dark mode / theming — ship a full dark theme + light/dark/system switch (BACKLOG)
**Goal:** [Captured for future planning] Ship a complete dark theme for the PWA plus a light/dark/system theme switch. **Phase 17 lays the token-architecture groundwork** — it restructures `apps/pwa/src/styles/tokens.css` from a single light `:root` into a themeable semantic-token layer that can be swapped via `data-theme` / `prefers-color-scheme`, with light staying the default and only-shipped theme. This backlog item is the follow-through that consumes that seam: author the actual dark palette values (including the Schedule-X `--sx-color-*` calendar overrides at the bottom of tokens.css), wire `prefers-color-scheme`, add a persisted in-app toggle in the /admin or Settings surface (light / dark / system), and verify both themes render cleanly across every route (calendar, lists, admin, settings sheet, login) via `playwright-cli` + the Phase 7 `layout.spec` profiles.
**Context:** Deferred out of Phase 17 (2026-06-17) during `/gsd-discuss-phase 17` to keep that phase scoped to phone-layout polish + branding assets. Phase 17's token restructure is the explicit enabling groundwork, so this should be cheap to pick up afterward. Related: Phase 17 (UI Optimization & Polish — the groundwork), 999.21 (modern styling refresh). Tags: pwa, theming, dark-mode, tokens, accessibility, settings, prefers-color-scheme.
**Requirements:** TBD
**Plans:** 0 plans
Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready)
### Phase 999.21: PWA modern visual styling refresh — contemporary look across the app (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.1 — 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 999.20 (dark mode) so the refresh is theme-aware from the start. **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), 999.20 (dark mode / theming). Tags: pwa, ui, styling, redesign, design-system, future-milestone. **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 **Requirements:** TBD
**Plans:** 0 plans **Plans:** 0 plans
@@ -311,3 +211,6 @@ Plans:
- [ ] TBD (promote with /gsd-review-backlog when ready) - [ ] 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`._
+12 -8
View File
@@ -3,10 +3,10 @@ gsd_state_version: 1.0
milestone: v1.2 milestone: v1.2
milestone_name: Multi-Provider, Theming & Zero-Setup milestone_name: Multi-Provider, Theming & Zero-Setup
status: planning status: planning
last_updated: "2026-06-19T13:13:25.671Z" last_updated: "2026-06-19T14:30:00.000Z"
last_activity: 2026-06-19 last_activity: 2026-06-19
progress: progress:
total_phases: 0 total_phases: 8
completed_phases: 0 completed_phases: 0
total_plans: 0 total_plans: 0
completed_plans: 0 completed_plans: 0
@@ -20,14 +20,16 @@ progress:
See: .planning/PROJECT.md (updated 2026-06-18 after v1.1 milestone) See: .planning/PROJECT.md (updated 2026-06-18 after v1.1 milestone)
**Core value:** One color-coded family calendar (shared + personal) and shared lists from a single low-friction PWA — cross-ecosystem, no app store **Core value:** One color-coded family calendar (shared + personal) and shared lists from a single low-friction PWA — cross-ecosystem, no app store
**Current focus:** Planning next milestone — run `/gsd-new-milestone` **Current focus:** Phase 21 — Zero-Setup DB Bootstrap (v1.2 roadmap created; awaiting plan)
## Current Position ## Current Position
Phase: Not started (defining requirements) Phase: 21 of 28 (Zero-Setup DB Bootstrap) — first v1.2 phase
Plan: — Plan: — (not yet planned)
Status: Defining requirements Status: Ready to plan
Last activity: 2026-06-19 — Milestone v1.2 started Last activity: 2026-06-19 — v1.2 ROADMAP.md created (Phases 2128); 11/11 requirements mapped, no orphans
Progress: [░░░░░░░░░░] 0% (0/8 v1.2 phases)
### ✅ Resolved Checkpoint — Phase 15 Plan 15-03 Task 2 (human-action) ### ✅ Resolved Checkpoint — Phase 15 Plan 15-03 Task 2 (human-action)
@@ -206,6 +208,8 @@ Recent decisions affecting current work:
### Roadmap Evolution ### Roadmap Evolution
- **v1.2 roadmap created (2026-06-19):** 8 phases (2128), continuing v1.1 numbering (last phase 20). 11/11 requirements mapped to exactly one phase, no orphans, no duplicates. Dependency-ordered per research ARCHITECTURE.md "Dependency-Ordered Build Sequence": **21 Zero-Setup DB Bootstrap** (SETUP-05, no deps, unlocks auto-migrate) → **22 Provider Abstraction** (PROV-01, HIGH-risk, CalDAV-only/Fastmail unchanged, carries the `UNIQUE(user_id, provider_type)` migration) → **23 Multiple Reminders** (CAL-18, dep 22) and **24 Dev/Mock Provider** (TEST-03, dep 22) → **25 Google Calendar Provider** (CAL-16/17/PROV-02, deps 22/23/21) → **26 Self-Service Onboarding** (PROV-03/04, dep 25). **27 PWA Dark Mode** (THEME-01) is independent/parallelizable with 2226. **28 Dependency Updates** (DEP-01) is last — after 27 (Schedule-X hold) and all other v1.2 work. Backlog promotions: 999.1→Provider Abstraction (22), 999.20→Dark Mode (27), 999.5→Self-Service Onboarding (26), 999.19→Dev/Mock Provider (24), 999.18→Dependency Updates (28). DEP-01 kept a standalone closing phase (not folded) to preserve its hard "last, after dark-mode-locked" ordering. Critical path: 21→22→23→25→26, then 28.
- Phase 6 added (2026-06-07): UX Polish — all-day visual distinction, event-form date/recurrence behavior, recurring-series edit, auth-flow smoothing. Candidate scope pulls from backlog 999.2/999.3/999.6/999.7/999.8/999.9. - Phase 6 added (2026-06-07): UX Polish — all-day visual distinction, event-form date/recurrence behavior, recurring-series edit, auth-flow smoothing. Candidate scope pulls from backlog 999.2/999.3/999.6/999.7/999.8/999.9.
- Phase 6 complete (2026-06-10): all 6 plans executed + 2 phase-level UX fixes (AppNav persistence + BottomTabBar desktop hide). Residual device-only checkpoints documented above. - Phase 6 complete (2026-06-10): all 6 plans executed + 2 phase-level UX fixes (AppNav persistence + BottomTabBar desktop hide). Residual device-only checkpoints documented above.
- Backlog reviewed (2026-06-10, /gsd-review-backlog): removed 6 stale duplicates (999.2/3/6/7/8/9 — already promoted into Phase 6) from the Backlog section + deleted the 999.2 dir; kept 999.1/4/5/10/11/12/13; added 999.14 (Gitea CI, promoted from STATE pending todo); archived stale kickoff-new-project todo. - Backlog reviewed (2026-06-10, /gsd-review-backlog): removed 6 stale duplicates (999.2/3/6/7/8/9 — already promoted into Phase 6) from the Backlog section + deleted the 999.2 dir; kept 999.1/4/5/10/11/12/13; added 999.14 (Gitea CI, promoted from STATE pending todo); archived stale kickoff-new-project todo.
@@ -280,4 +284,4 @@ Resume file: .planning/phases/20-admin-member-editor-form-declutter/20-UI-SPEC.m
## Operator Next Steps ## Operator Next Steps
- Start the next milestone with /gsd-new-milestone - v1.2 roadmap created (Phases 2128). Plan the first phase with `/gsd-plan-phase 21`.