From ab02cdb1ba4bf5da75d8006aaca6a6278a2b887a Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Wed, 10 Jun 2026 21:29:30 -0400 Subject: [PATCH] docs: create milestone v1.1 roadmap (6 phases) --- .planning/REQUIREMENTS.md | 42 ++++++----- .planning/ROADMAP.md | 153 +++++++++++++++++++++++++++++++++++++- .planning/STATE.md | 31 +++++--- 3 files changed, 193 insertions(+), 33 deletions(-) diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index d82c321..6f53053 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -39,13 +39,13 @@ Each requirement maps to exactly one roadmap phase (see Traceability). ### CI — Gitea continuous integration -- [ ] **CI-01**: Every pull request targeting `main` runs full regression — lint, typecheck (both apps), unit tests, and API integration tests against a MariaDB service container — and the result gates the merge. +- [ ] **CI-01**: Every pull request targeting `main` runs full regression — lint, typecheck (both apps), unit tests, API integration tests against a MariaDB service container, **and the Phase 7 mobile Playwright harness as a UI-regression step (CI brings up the API + PWA dev servers + MariaDB service container with `DEV_AUTH_BYPASS` in the runner and runs the harness specs headlessly against the authed PWA)** — and the result gates the merge. - [ ] **CI-02**: On merge to `main`, the API Docker image is built and published to the Gitea container registry. ### Test — Mobile-emulated authed browser harness - [ ] **TEST-01**: The assistant can drive the PWA in a **mobile-emulated viewport** (device profile + mobile UA + touch) for automated UI/layout verification. -- [ ] **TEST-02**: Automated runs reach the **authenticated** PWA via the existing `DEV_AUTH_BYPASS` on the host-side dev stack (no manual login, no Authelia/OIDC mocking). Targets the dev build; real prod-service-worker mobile testing is out of scope (see below). +- [ ] **TEST-02**: Automated runs reach the **authenticated** PWA via the existing `DEV_AUTH_BYPASS` on the host-side dev stack (no manual login, no Authelia/OIDC mocking). Targets the dev build; real prod-service-worker mobile testing is out of scope (see below). The harness specs are also consumed by Phase 8 (Gitea CI) as the PR UI-regression step. ## Future Requirements (deferred, not in v1.1) @@ -67,24 +67,26 @@ Each requirement maps to exactly one roadmap phase (see Traceability). ## Traceability -Filled by the roadmapper — maps each REQ-ID to its phase. +Maps each REQ-ID to its phase. v1.1 phases continue v1.0 numbering (v1.0 ended at Phase 6) → v1.1 starts at Phase 7. **Coverage: 17/17 v1.1 requirements mapped, no orphans, no duplicates.** | REQ-ID | Phase | Status | |--------|-------|--------| -| CAL-13 | TBD | Pending | -| CAL-14 | TBD | Pending | -| CAL-15 | TBD | Pending | -| NOTIF-04 | TBD | Pending | -| NOTIF-05 | TBD | Pending | -| NOTIF-06 | TBD | Pending | -| ADMIN-01 | TBD | Pending | -| ADMIN-02 | TBD | Pending | -| ADMIN-03 | TBD | Pending | -| SETUP-01 | TBD | Pending | -| SETUP-02 | TBD | Pending | -| SETUP-03 | TBD | Pending | -| SETUP-04 | TBD | Pending | -| CI-01 | TBD | Pending | -| CI-02 | TBD | Pending | -| TEST-01 | TBD | Pending | -| TEST-02 | TBD | Pending | +| TEST-01 | Phase 7 (Mobile Test Harness) | Pending | +| TEST-02 | Phase 7 (Mobile Test Harness) | Pending | +| CI-01 | Phase 8 (Gitea CI) | Pending | +| CI-02 | Phase 8 (Gitea CI) | Pending | +| CAL-15 | Phase 9 (Faster Write-Back) | Pending | +| ADMIN-01 | Phase 10 (Admin Role & Settings) | Pending | +| ADMIN-02 | Phase 10 (Admin Role & Settings) | Pending | +| ADMIN-03 | Phase 10 (Admin Role & Settings) | Pending | +| CAL-13 | Phase 11 (Per-Event Reminders) | Pending | +| CAL-14 | Phase 11 (Per-Event Reminders) | Pending | +| NOTIF-04 | Phase 11 (Per-Event Reminders) | Pending | +| NOTIF-05 | Phase 11 (Per-Event Reminders) | Pending | +| NOTIF-06 | Phase 11 (Per-Event Reminders) | Pending | +| SETUP-01 | Phase 12 (Initial Setup Wizard) | Pending | +| SETUP-02 | Phase 12 (Initial Setup Wizard) | Pending | +| SETUP-03 | Phase 12 (Initial Setup Wizard) | Pending | +| SETUP-04 | Phase 12 (Initial Setup Wizard) | Pending | + +**DB foundation note:** The v1.1 schema migration (`users.is_admin`, `calendar_events.reminder_lead_minutes`, `app_config` table) is not a standalone requirement — it is carried by **Phase 10 (Admin Role & Settings)** (which owns is_admin + app_config) and consumed by **Phase 11 (Per-Event Reminders)** (reminder_lead_minutes) and **Phase 12 (Initial Setup Wizard)** (app_config.setup_complete). Folded per ARCHITECTURE.md ordering rather than created as a migration-only phase. This makes Phase 10 the head of the admin chain (10 → 11, 10 → 12). diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index e1de9c4..4cc3ef4 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -3,6 +3,7 @@ ## 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–12 (planning) — mobile test harness, Gitea CI (runs the harness), faster write-back, in-app admin, per-event reminders, guided setup ## Phases @@ -20,9 +21,131 @@ Full phase detail archived in [`milestones/v1.0-ROADMAP.md`](milestones/v1.0-ROA -### 🚧 v1.x (Planned) +### 🚧 v1.1 Operability & Polish (Phases 7–12) -No phases planned yet. Promote items from the Backlog with `/gsd-review-backlog`, or start the next cycle with `/gsd-new-milestone`. +Make FamilySync configurable, administrable, and maintainable for real multi-member use — without hand-editing env files or the database. The new critical path runs **mobile test harness → Gitea CI** (CI consumes the harness specs for UI regression), and the **admin role → reminders / setup wizard** chain (a single `/api/admin` + `/api/setup` route surface carrying the v1.1 DB migration). Faster write-back is a fully independent track. + +- [ ] **Phase 7: Mobile Test Harness** - Mobile-emulated, authenticated PWA browser harness so the assistant (and CI) can catch mobile-only defects +- [ ] **Phase 8: Gitea CI** - Full regression on PR to main (lint/typecheck/unit/API-integration vs a MariaDB service container **+ the Phase 7 mobile harness as a UI-regression step against a CI-hosted dev stack**) + Docker image publish on merge +- [ ] **Phase 9: Faster Write-Back** - Event-driven outbox drain so edits land in ~1-2s instead of ~15s, preserving every outbox durability guarantee +- [ ] **Phase 10: Admin Role & Settings** - DB foundation (is_admin / reminder_lead / app_config) + role-gated admin UI to rotate app passwords and designate the shared calendar +- [ ] **Phase 11: Per-Event Reminders** - Reminder selector on the event form (incl. "None") serialized as VALARM, with a variable-lead scheduler that honors each event's choice +- [ ] **Phase 12: Initial Setup Wizard** - First-run validated bootstrap of env/VAPID/DB/OIDC + first app password, reusing the admin route surface + +## Phase Details + +> v1.0 phase detail (Phases 1–6) is archived in [`milestones/v1.0-ROADMAP.md`](milestones/v1.0-ROADMAP.md). + +### Phase 7: Mobile Test Harness +**Goal**: The assistant can drive the PWA in a mobile-emulated, authenticated browser context against the host-side dev stack, so mobile-only layout and flow defects can be caught automatically instead of only by the operator on real devices. This harness is also the artifact Phase 8 (CI) runs for UI regression. +**Mode:** standard +**Depends on**: Nothing (fully independent; goes first. One new dev dependency `@playwright/test` in `apps/pwa`; no backend changes). +**Requirements**: TEST-01, TEST-02 +**Success Criteria** (what must be TRUE): + 1. An automated run can load the PWA in a mobile-emulated viewport (device profile + mobile UA + touch) and assert on responsive layout / tap targets. + 2. The automated run reaches the authenticated PWA via the existing `DEV_AUTH_BYPASS` on the host-side dev stack — no manual login and no Authelia/OIDC mocking. + 3. The harness runs repeatably day-over-day without re-capturing any session state (no stale storage-state failures). + 4. The harness specs are structured so they can run headlessly in CI (Phase 8) against a dev stack the runner brings up — no dependence on a developer's already-running host stack. +**Pitfalls this phase owns** (from PITFALLS.md): + - **No stale storage-state** (Pitfall 14): use `DEV_AUTH_BYPASS=true` for the automated harness rather than a checked-in storage-state.json with an expiring session cookie; decide the auth strategy before the first test. + - **Service worker block** (Pitfall 15): set `serviceWorkers: 'block'` (or explicitly unregister) in the context so a previous run's SW does not intercept requests / return stale cached responses; verify no SW-sourced responses in the trace. + - Hard constraints: targets the dev build via `DEV_AUTH_BYPASS` (DEV_AUTH_BYPASS user 1 has no CalDAV credential/calendars — verify layout/flows, not live event-create); real prod-service-worker / iOS-Safari-standalone mobile testing stays a human/device gate (out of scope). +**Plans**: TBD +**UI hint**: yes + +### Phase 8: Gitea CI +**Goal**: Every PR to `main` runs a full regression that gates the merge — lint, typecheck, unit, API-integration against a MariaDB service container, **and the Phase 7 mobile Playwright harness as a UI-regression step against a CI-hosted dev stack** — and a merge to `main` builds and publishes the API Docker image, all on the existing self-hosted Gitea Actions runner. +**Mode:** standard +**Depends on**: Phase 7 (the PR regression runs the Phase 7 mobile harness specs as its UI-regression step; without the harness there is nothing to run). No other code dependencies. Start with a runner-probe step. +**Requirements**: CI-01, CI-02 +**Success Criteria** (what must be TRUE): + 1. Opening or updating a PR targeting `main` triggers a workflow that runs lint, typecheck (both apps), unit tests, and API integration tests against a MariaDB service container — and a failing run blocks the merge. + 2. The API integration tests connect to the service-container MariaDB (DB_HOST=127.0.0.1, service creds) and pass reliably on a cold first run, not only on re-run. + 3. The same PR workflow brings up the dev stack inside the runner — the API dev server, the PWA dev server, and the MariaDB service container, with `DEV_AUTH_BYPASS=true` — and runs the Phase 7 mobile Playwright harness specs headlessly against that authed PWA; a harness failure blocks the merge. + 4. The harness step waits for both the API and PWA dev servers to be ready (readiness probe / poll) before launching Playwright, so it does not flake on startup races. + 5. On merge to `main`, the API Docker image is built and pushed to the Gitea container registry under a sensible tag. + 6. Registry credentials never appear in plaintext in the CI logs. +**Pitfalls this phase owns** (from PITFALLS.md): + - **Runner-probe first** (Pitfall 12): the first workflow only probes `node --version` / `pnpm --version` / Docker access on the `self-hosted` runner before any test or build steps are designed; pin Node 22 explicitly, do not assume `actions/setup-node` works as on GitHub. + - **MariaDB readiness wait** (Pitfall 11): add an explicit readiness loop (e.g. `healthcheck.sh --connect --innodb_initialized`, NOT `mysqladmin ping` which is removed in MariaDB 11) before any `drizzle-kit migrate` / integration test step; healthy ≠ accepting connections. + - **Dev-stack readiness races (NEW for the harness step):** running the PWA and API dev servers *inside* CI adds startup/readiness races on top of the MariaDB-11 readiness race. The harness step must wait for **both** the API and PWA dev servers to be accepting connections (poll their URLs / health endpoints) before Playwright launches — do not race the browser against a not-yet-listening server. Run with `DEV_AUTH_BYPASS=true` so the harness reaches the authed PWA exactly as in Phase 7. + - **--password-stdin** (Pitfall 13): `docker login` via `--password-stdin` with the token piped from a registered Gitea secret (PAT with `write:package`); never `-p $TOKEN` on the command line. + - Hard constraints: API integration tests need a real MariaDB and live in `apps/api/tests/` (never `src/`); cache the pnpm store; Drizzle generate+migrate to set up the CI DB schema; the harness step reuses the Phase 7 specs unchanged (CI owns only the stack bring-up + readiness wait, not the spec content). +**Plans**: TBD +**UI hint**: yes + +### Phase 9: Faster Write-Back +**Goal**: A created, edited, or deleted event reaches Fastmail within ~1-2 seconds (event-driven outbox drain) instead of waiting up to ~15s for the next interval tick — with every existing durability guarantee intact. +**Mode:** standard +**Depends on**: Nothing (fully independent; the only new artifact is a zero-dependency in-process EventEmitter, `lib/outboxTrigger.ts`). Can run in parallel with any other v1.1 track. +**Requirements**: CAL-15 +**Success Criteria** (what must be TRUE): + 1. After creating/editing/deleting an event, the change lands in Fastmail in ~1-2s in the common case (drain is signalled on enqueue, not waited-for on the interval) — observable as the change appearing in the Fastmail native app well before the old ~15s window. + 2. The route handler still returns an optimistic 202 immediately and never makes a CalDAV call inline — the event-driven signal is fire-and-forget. + 3. Edit-as-move still writes the new event before deleting the old one (create-before-delete ordering preserved); no event is ever lost when a move drains under rapid enqueues. + 4. No duplicate CalDAV PUTs occur for the same outbox row when the signal and the 15s fallback interval overlap (exactly-once per uid preserved). + 5. The 15s `setInterval` fallback still runs and recovers any rows missed by the signal path (startup catch-up, transient errors). +**Pitfalls this phase owns** (from PITFALLS.md): + - **No double-drain** (Pitfall 5): the trigger must set a `drainRequested` flag funnelled through the single setInterval-controlled path / the existing `isDraining` guard — never call `runOutboxDrain()` directly from the signal in a way that bypasses the guard or escapes the error-caught wrapper. + - **Create-before-delete under concurrent enqueues** (Pitfall 6): enqueue CREATE before DELETE; do not fire the signal between the two inserts of a move (publish after both inserts / after the transaction commits). + - Hard constraints: `setInterval` only (no node-cron); single-process by design — **no Redis** for the drain (Redis stays for list SSE); all outbox guarantees (fresh-etag-before-PUT, 412 conflict flow, per-uid exactly-once) unchanged. +**Plans**: TBD + +### Phase 10: Admin Role & Settings +**Goal**: An admin can manage household configuration that previously required manual DB writes — rotating a member's Fastmail app password and designating the shared family calendar — from a role-gated in-app Settings section, on top of the v1.1 DB foundation this phase introduces. +**Mode:** standard +**Depends on**: Nothing required upstream; this phase **carries the v1.1 DB migration** (users.is_admin, calendar_events.reminder_lead_minutes, app_config table) that Phases 11 and 12 build on. It is the head of the admin chain (10 → 11, 10 → 12). +**Requirements**: ADMIN-01, ADMIN-02, ADMIN-03 +**Success Criteria** (what must be TRUE): + 1. An admin sees an Admin section in Settings and can list household members with their credential status; a non-admin member never sees it and cannot invoke any `/api/admin/*` route (gets 403). + 2. An admin can enter or rotate a member's Fastmail app password; it is validated against CalDAV (PROPFIND) before saving and stored encrypted — and the password is never displayed, echoed in a response, or logged. + 3. An admin can pick which synced calendar is the shared family calendar from a list, and the `calendars.is_shared` flag updates accordingly (replacing the manual `UPDATE calendars SET is_shared=1` step). + 4. The role check is role-agnostic and member-count-agnostic: it gates on `users.is_admin`, so more admins can be added later without reworking the guard. + 5. The DB migration (is_admin, reminder_lead_minutes, app_config) is applied via generate+migrate and is in place for downstream phases (reminder_lead_minutes for Phase 11, app_config.setup_complete for Phase 12). +**Pitfalls this phase owns** (from PITFALLS.md): + - **Admin role check inside the sub-router** (Pitfall 9): apply `requireAdmin` with `.use('*', ...)` inside `adminRouter`, not only at the parent mount; integration test must assert 403 for a non-admin authenticated user. + - **App password never logged/echoed** (Pitfall 7): custom zod-validator `hook` returns a generic 400 (no Zod `received`/`value` field); no `console.log` of request bodies in `routes/admin*`. + - Hard constraints: Drizzle **generate+migrate, never push** (false destructive diff on populated MariaDB); reuse `broker/crypto.ts` `encryptPassword` (no changes to crypto); `/api/admin/credentials` and `/api/admin/calendars/:id/shared` are the single shared surface — do NOT duplicate them into `/api/setup/*` in Phase 12. +**Plans**: TBD +**UI hint**: yes + +### Phase 11: Per-Event Reminders +**Goal**: A user can choose a reminder lead time per event (None / 5m / 10m / 15m / 30m / 1h / 2h / 1d / 2d, default None), serialized as a VALARM on the event, and the push scheduler fires at that exact lead — firing nothing when there is no alarm and never stripping reminders set in other clients. +**Mode:** standard +**Depends on**: Phase 10 (the `calendar_events.reminder_lead_minutes` column from the v1.1 migration is the scheduler's ground truth). Independent of Phases 7/8/9/12. +**Requirements**: CAL-13, CAL-14, NOTIF-04, NOTIF-05, NOTIF-06 +**Success Criteria** (what must be TRUE): + 1. When creating or editing a timed event, the user can pick a reminder lead from the preset list (None default); the choice round-trips to Fastmail as a VALARM and is visible/honored on re-open. + 2. Editing an event that already has a reminder set in another client (Fastmail / Apple Calendar) preserves that VALARM — it is never silently dropped on round-trip. + 3. A reminder push fires at the event's chosen lead time (e.g. T-30 for a 30-minute lead), not a hardcoded 15-minute lead. + 4. An event with no reminder set produces no reminder push (no default 15-minute fire). + 5. An all-day event's reminder fires at a sensible local time (9 AM on the alert day), not midnight; the reminder selector is disabled/hidden for all-day events in the UI; and reminder delivery stays exactly-once across catch-up scans and rescheduled events. +**Pitfalls this phase owns** (from PITFALLS.md): + - **Preserve-on-edit** (Pitfall 1): the update path extracts and preserves existing VALARM sub-components from `rawVevent` (mirroring the WR-01 RRULE-preserve pattern) — never rebuild-from-scratch and silently strip; `outboxPayloadSchema` distinguishes "no change" from explicit "no reminder". + - **No TRIGGER VALUE=TEXT** (Pitfall 2): build the trigger with `ICAL.Duration.fromSeconds(-n*60)`, not a bare string; unit-test that the ICS emits a DURATION trigger with no `VALUE=TEXT`. + - **All-day 9AM semantics** (Pitfall 3): guard `buildVeventString` (`if (!allDay && reminderMinutes > 0)`), disable the selector when allDay, keep the scheduler's all-day handling at 9 AM local. + - **uid:dtstartMs dedup** (Pitfall 4): change the scheduler dedup key from bare `uid` to compound `uid:dtstartMs` and widen the scan to a variable per-event window so long leads fire and rescheduled events re-fire; keep `eventFieldsSchema` and `outboxPayloadSchema` in sync (IN-03). + - Hard constraints: `setInterval` only; scheduler reads `reminder_lead_minutes` from the DB (ground truth), not the outbox payload; drop the `isShared`-only reminder restriction (a user who set an alarm wants it regardless of calendar). +**Plans**: TBD +**UI hint**: yes + +### Phase 12: Initial Setup Wizard +**Goal**: On first run (no admin/credentials configured), the operator is guided through a validated, step-by-step wizard to bootstrap the app — env presence, generated secrets to copy, DB/OIDC/VAPID/app-password validation — instead of hand-editing `.env` / `docker-compose.yml`; once complete, the setup endpoints lock. +**Mode:** standard +**Depends on**: Phase 10 (reuses the admin role + `/api/admin/credentials` and `/api/admin/calendars/:id/shared` routes; the wizard is the second frontend consumer of that surface, and `app_config` from the Phase 10 migration holds `setup_complete`). Goes last. Independent of Phases 7/8/9/11. +**Requirements**: SETUP-01, SETUP-02, SETUP-03, SETUP-04 +**Success Criteria** (what must be TRUE): + 1. On a fresh install with nothing configured, the operator reaches a setup wizard (via `GET /api/setup/status` mounted before the OIDC guard) and walks through bootstrap steps instead of editing files by hand. + 2. Each input is validated before the step can complete: DB connects, VAPID private key decodes to exactly 32 bytes and pairs with the public key, OIDC discovery resolves, and the Fastmail app password reaches CalDAV (PROPFIND). + 3. Generated secrets (session secret, encryption key, VAPID keypair) are displayed for the operator to copy into env; they are never written to the DB or returned in a way that persists, and `APP_PASSWORD_ENCRYPTION_KEY`/`VAPID_PRIVATE_KEY` never enter the DB at all. + 4. After completion, the wizard-completing user is promoted to admin (`is_admin`), `app_config.setup_complete` is set, and any further call to a setup endpoint returns 423 Locked. + 5. The 423 guard is enforced on every invocation (checked against member-credentials + VAPID env present), not only at startup. +**Pitfalls this phase owns** (from PITFALLS.md): + - **Guard on every invocation** (Pitfall 8): the "already set up" guard returns 423 from all setup routes once configured — implement and test the guard before the happy path; a second POST after completion must return 423, not 200. + - **Secrets stay in env, never in DB** (Pitfalls 8 & 10): the wizard validates secrets by performing a test operation (test encrypt/decrypt, structural VAPID check), never by accepting/storing the key value; no DB column for `vapid_private_key` or `app_password_encryption_key`; never log/echo the app password. + - Hard constraints: `GET /api/setup/status` mounts **before** the OIDC guard (like `/health`); do NOT create `/api/setup/credentials` — reuse the Phase 10 admin routes; Drizzle generate+migrate (any `app_config` seeding via migration). +**Plans**: TBD +**UI hint**: yes ## Progress @@ -34,6 +157,12 @@ No phases planned yet. Promote items from the Backlog with `/gsd-review-backlog` | 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 | 0/? | Not started | - | +| 8. Gitea CI | v1.1 | 0/? | Not started | - | +| 9. Faster Write-Back | v1.1 | 0/? | Not started | - | +| 10. Admin Role & Settings | v1.1 | 0/? | Not started | - | +| 11. Per-Event Reminders | v1.1 | 0/? | Not started | - | +| 12. Initial Setup Wizard | v1.1 | 0/? | Not started | - | ## Backlog @@ -62,6 +191,8 @@ Plans: **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) @@ -81,6 +212,9 @@ Plans: - Security: never log/echo the app password; member-scoped; T-03-19 style scoping. **Severity:** high for true multi-member use — without it the second member has no personal calendar. Tags: phase-03, onboarding, auth, caldav, per-member-credential, D-09. + +> **Note:** v1.1 covers the *admin-managed* counterpart (ADMIN-01, Phase 10) — an admin can set any member's app password. *Self-service* member onboarding (member adds their own) stays deferred here. + **Requirements:** TBD **Plans:** 0 plans @@ -96,6 +230,9 @@ Plans: - **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=` — 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 @@ -114,6 +251,9 @@ Plans: 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 @@ -133,6 +273,9 @@ Plans: **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 @@ -152,6 +295,9 @@ Plans: **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 @@ -173,6 +319,9 @@ Plans: **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 diff --git a/.planning/STATE.md b/.planning/STATE.md index ead90cd..a0c7a5f 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,10 +3,10 @@ gsd_state_version: 1.0 milestone: v1.1 milestone_name: Operability & Polish status: planning -last_updated: "2026-06-10T22:50:59.197Z" +last_updated: "2026-06-10T23:30:00.000Z" last_activity: 2026-06-10 progress: - total_phases: 0 + total_phases: 6 completed_phases: 0 total_plans: 0 completed_plans: 0 @@ -20,14 +20,14 @@ progress: See: .planning/PROJECT.md (updated 2026-06-10) **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:** v1.0 MVP shipped + archived — planning next milestone (v1.x). Run `/gsd-new-milestone` or promote backlog via `/gsd-review-backlog`. +**Current focus:** v1.1 Operability & Polish — roadmap reordered (Phases 7–12). Next: plan Phase 7 with `/gsd-plan-phase 7` (Mobile Test Harness — independent, goes first; Phase 8 CI consumes its specs) or Phase 9 (Faster Write-Back — independent, lowest risk) or Phase 10 (Admin Role & Settings — carries the DB migration that Phases 11 & 12 need). ## Current Position -Phase: Not started (defining requirements) +Phase: Not started (roadmap reordered — Phases 7–12) Plan: — -Status: Defining requirements -Last activity: 2026-06-10 — Milestone v1.1 started +Status: Roadmap complete, awaiting phase planning +Last activity: 2026-06-10 — v1.1 roadmap reordered (6 phases, 17/17 requirements mapped) ## Performance Metrics @@ -129,13 +129,15 @@ Recent decisions affecting current work: - 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. - 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. +- **v1.1 roadmap created (2026-06-10):** 6 phases (7–12), continuing v1.0 numbering. 17/17 requirements mapped, no orphans. Promotions: 999.13→Faster Write-Back (CAL-15), 999.10→Admin Role & Settings (ADMIN-01/02/03), 999.11→Setup Wizard (SETUP-01/02/03/04), 999.4→Per-Event Reminders (CAL-13/14 + NOTIF-04/05/06), 999.14→Gitea CI (CI-01/02), 999.12→Mobile Test Harness (TEST-01/02). The v1.1 DB migration (users.is_admin, calendar_events.reminder_lead_minutes, app_config) is folded into the Admin phase (per ARCHITECTURE.md ordering), not a standalone migration phase; the reminders + wizard phases consume it. Backlog 999.5 (self-service onboarding) and 999.1 (provider abstraction) stay deferred — ADMIN-01 covers the admin-managed credential gap in the interim. +- **v1.1 roadmap reordered (2026-06-10):** same 6 phases / 17 mappings, new order/numbering. Phase 7 = Mobile Test Harness (was 12), Phase 8 = Gitea CI (was 11), Phase 9 = Faster Write-Back (was 7), Phase 10 = Admin Role & Settings (was 8, carries the DB migration), Phase 11 = Per-Event Reminders (was 10), Phase 12 = Initial Setup Wizard (was 9). **Phase 8 (CI) scope extended:** the PR regression now also runs the Phase 7 mobile harness as a UI-regression step, bringing up the dev stack (API + PWA dev servers + MariaDB service container + DEV_AUTH_BYPASS) in the runner — so **Phase 8 now depends on Phase 7**. New critical path: **7 → 8** (CI consumes the harness); **9** independent; **10 → 11** and **10 → 12** (admin migration precedes reminders + wizard). Parallelizable once prerequisites met: 9 anytime; 11 and 12 in parallel after 10; 7 then 8. ### Pending Todos - ~~**Fix `docs/deployment.md` local-dev command**~~ DONE 2026-06-10 (quick 260610-czd) — added a "Running locally (host-side, no Docker)" subsection with the correct two-terminal command (`set -a; source .env; set +a && DEV_AUTH_BYPASS=true DB_HOST=localhost pnpm --filter @familysync/api dev` + `pnpm --filter @familysync/pwa dev`). `--env-file` deliberately NOT baked into the dev script (root `.env` sets `DB_HOST=mariadb`; auto-load would break host-side dev). - ~~**REQUIREMENTS.md traceability gap**~~ DONE 2026-06-10 (gsd-fast) — added the 6 deferred REQ-IDs to the Traceability table: CAL-09…CAL-12 (v1.x, Deferred), DISP-01/DISP-02 (v2, Deferred). v1 coverage stays 20/20; deferred IDs tracked separately. - **DST spring-forward spot-check (Phase 2)** — recurring/DST is implemented and code-verified (VTIMEZONE before expansion + local display TZ), and operator approved general times; navigating to March 2026 to eyeball the spring-forward transition is a recommended future spot-check. -- ~~**Gitea CI — regression on PR to main + Docker build/publish**~~ PROMOTED TO BACKLOG 999.14 (2026-06-10, /gsd-review-backlog) — self-hosted Gitea runner exists but no CI yet. Full regression (lint/typecheck/unit + API integration vs a MariaDB service container + PWA build) gating PRs to `main`, plus build/publish the Docker image to the Gitea registry. Detail retained in pending todo `2026-06-10-gitea-ci-regression-and-docker-publish.md` (backing the backlog entry). +- ~~**Gitea CI — regression on PR to main + Docker build/publish**~~ PROMOTED TO BACKLOG 999.14 → now **v1.1 Phase 8** (Gitea CI, CI-01/CI-02). Detail retained in pending todo `2026-06-10-gitea-ci-regression-and-docker-publish.md`. Scope extended in the reorder: CI also runs the Phase 7 mobile harness as a UI-regression step. ### Blockers/Concerns @@ -145,6 +147,8 @@ Recent decisions affecting current work: - Phase 2/3 dev: build behind a documented dev-auth bypass until Gate 2 deploy (D-14). - Phase 5: iOS push subscriptions silently revoked after 3 silent pushes. Subscription health-check and event.waitUntil() are mandatory from day one. - Phase 06 residual device-only items (not drivable in desktop Chromium): (1) PushPermissionPrompt spinner visible only in an installed iOS/standalone PWA — code-confirmed uses global @keyframes spin; spot-check at go-live. (2) iOS-Safari standalone cold-load and Authelia redirect — per 06-VALIDATION.md Manual-Only table; not yet verified. (3) Dev-bypass user (id 1) has no CalDAV credential/calendars; live event-create via the form requires user 2 or a dev-seed fix before go-live testing. +- **v1.1 Phase 7 (Mobile Test Harness):** DEV_AUTH_BYPASS user 1 has no CalDAV credential/calendars — the harness verifies layout/flows, not live event-create. Confirm SW-block + bypass strategy before the first test (PITFALLS 14/15). These specs become Phase 8's CI UI-regression step, so structure them to run headlessly against a runner-hosted dev stack. +- **v1.1 Phase 8 (Gitea CI):** Unraid Gitea runner Docker-socket/Node/pnpm state unknown — runner-probe is the first CI task (PITFALLS 12); MariaDB 11 readiness uses `healthcheck.sh --connect`, not `mysqladmin ping` (removed in MariaDB 11). NEW: the harness step brings up the API + PWA dev servers in the runner — added startup/readiness races on top of the MariaDB race; the step must wait for **both** dev servers to be ready before launching Playwright. Depends on Phase 7. ### Quick Tasks Completed @@ -170,15 +174,20 @@ Recent decisions affecting current work: | Calendar | Apple Calendar native subscribe URL docs | v1.x | Roadmap | | Calendar | Secondary timezone display toggle | v1.x | Roadmap | | Display | Wall-display / kiosk dashboard | v2 | PROJECT.md | +| Reminders | Multiple reminders per event (2× VALARM) | v1.2 | v1.1 REQUIREMENTS.md | +| Onboarding | Self-service member app-password setup (999.5) — v1.1 covers admin-managed (ADMIN-01) | Backlog | v1.1 roadmap | +| Calendar | Provider abstraction (999.1) — Fastmail as one of several backends | Backlog | v1.1 roadmap | +| Setup | Wizard re-run / reconfigure flow after first setup | Backlog | v1.1 REQUIREMENTS.md | | Notifications | **Android event-change push delivery (Phase 5 UAT Test 4)** — confirm member B's Android device receives a non-silent "A updated an event" push after member A edits a shared event. Blocking bugs already fixed + deployed (quick 260610-jlp how-to-enable link, 260610-ka9 silent-notification options); server-side FCM delivery proven (FCM 201). Remaining: on-device confirmation + operator raises the Edge/Android notification-channel importance. See 05-UAT.md Test 4. | Phase 6 verification | 2026-06-10 | -| ~~Calendar~~ | ~~Mark shared-family calendar `is_shared=1`~~ **RESOLVED 2026-06-10** — operator created the "FamilySync" calendar on the primary Fastmail account; poller synced it as calendars.id=10 (user 2); ran `UPDATE calendars SET is_shared=1 WHERE id=10`. Shared color lane now populated; Phase 5 reminders now fire on its events. Poller upsert does not touch is_shared, so the flag persists. | ~~Phase 2 (deferred, D-16)~~ DONE | 2026-06-05 → 2026-06-10 | +| ~~Calendar~~ | ~~Mark shared-family calendar `is_shared=1`~~ **RESOLVED 2026-06-10** — operator created the "FamilySync" calendar on the primary Fastmail account; poller synced it as calendars.id=10 (user 2); ran `UPDATE calendars SET is_shared=1 WHERE id=10`. Shared color lane now populated; Phase 5 reminders now fire on its events. Poller upsert does not touch is_shared, so the flag persists. (ADMIN-02 in v1.1 Phase 10 replaces this manual step with a UI toggle.) | ~~Phase 2 (deferred, D-16)~~ DONE | 2026-06-05 → 2026-06-10 | ## Session Continuity -Last session: 2026-06-10T15:20:02.349Z -Stopped at: Completed 06-03: hasRrule server-side exposure +Last session: 2026-06-10T23:30:00.000Z +Stopped at: v1.1 roadmap reordered (Phases 7–12; 17/17 requirements mapped) Resume file: None ## Operator Next Steps -- Start the next milestone with /gsd-new-milestone +- Review the reordered v1.1 roadmap in `.planning/ROADMAP.md` (Phases 7–12). +- Plan the first phase with `/gsd-plan-phase 7` (Mobile Test Harness — independent, goes first; Phase 8 CI runs its specs). Alternatives that can start in parallel: `/gsd-plan-phase 9` (Faster Write-Back — independent, lowest risk) or `/gsd-plan-phase 10` (Admin Role & Settings — carries the DB migration that Phases 11 & 12 depend on).