# Requirements: FamilySync — v1.1 "Operability & Polish"

**Defined:** 2026-06-10
**Milestone:** v1.1 (continues from v1.0 MVP, shipped 2026-06-10)
**Core Value:** The household can see and co-edit one color-coded family calendar (shared + each member's personal) and shared lists from a single low-friction PWA — cross-ecosystem, no app store. v1.1 makes that app **configurable, administrable, and maintainable** without hand-editing env files or the database.

REQ-IDs continue v1.0 numbering (CAL ≤12, NOTIF ≤3 already used). New categories: ADMIN, SETUP, CI, TEST.

## v1.1 Requirements

Each requirement maps to exactly one roadmap phase (see Traceability).

### Calendar — Per-event reminders & write-back latency

- [ ] **CAL-13**: User can choose a reminder lead time when creating or editing an event from a preset list (None / 5m / 10m / 15m / 30m / 1h / 2h / 1d / 2d), with **"None" as the default**; the choice is serialized as a VALARM on the event written back to Fastmail.
- [ ] **CAL-14**: Editing an event **preserves any existing reminder/VALARM** set in another client (Fastmail or native) — reminders are never silently stripped on round-trip.
- [ ] **CAL-15**: A created, edited, or deleted event reaches Fastmail within ~2 seconds (event-driven outbox drain) instead of up to ~15s, while preserving the optimistic-202 accept and all outbox durability guarantees (create-before-delete ordering, drain concurrency guard, fresh-etag-before-PUT, per-uid exactly-once).

### Notifications — Variable-lead reminder scheduling

- [ ] **NOTIF-04**: An event reminder push fires at the event's **chosen lead time**, not a hardcoded 15-minute lead.
- [ ] **NOTIF-05**: An event with **no reminder set produces no reminder push** (no default 15-min fire).
- [ ] **NOTIF-06**: An all-day event's reminder fires at a sensible local time (9 AM on the alert day), not at midnight, and reminder delivery remains exactly-once across catch-up scans and rescheduled events.

### Administration — Settings section (role-gated)

> Role-agnostic design: ship operator-only (`is_admin`), but the role check is member-count-agnostic so more admins can be added later without rework.

- [ ] **ADMIN-01**: An admin can view household members and update (rotate / re-enter) a member's Fastmail app password from the UI; the credential is validated against CalDAV before saving and stored encrypted (existing `APP_PASSWORD_ENCRYPTION_KEY` path) — the password is never displayed, logged, or echoed.
- [ ] **ADMIN-02**: An admin can designate which synced calendar is the shared family calendar (set `calendars.is_shared`) from the UI, replacing the manual DB write.
- [ ] **ADMIN-03**: Admin Settings routes and UI are gated by a role check; a non-admin member cannot reach or invoke them.

### Setup — First-run configuration wizard

- [ ] **SETUP-01**: On first run (no admin/credentials configured), the operator is guided through a setup wizard to define bootstrap configuration (app/external URL, OIDC client, session secret, encryption key, VAPID keypair, MariaDB connection, first member's Fastmail app password) instead of hand-editing `.env` / `docker-compose.yml`.
- [ ] **SETUP-02**: The wizard **validates each input before completing** — DB connectivity test, VAPID private key decodes to 32 bytes and pairs with the public key, OIDC discovery resolves, and the Fastmail app password reaches CalDAV (PROPFIND).
- [ ] **SETUP-03**: The wizard generates secrets (session secret, encryption key, VAPID keypair) for the operator to copy into env; secrets are **never written to the database or returned in a response body**.
- [ ] **SETUP-04**: Once setup is complete, the setup endpoints are no longer accessible (guard checked on every invocation, not only at startup).

### CI — Gitea continuous integration

- [ ] **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

- [x] **TEST-01**: The assistant can drive the PWA in a **mobile-emulated viewport** (device profile + mobile UA + touch) for automated UI/layout verification.
- [x] **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)

- **Multiple reminders per event** (2× VALARM) — stretch; deferred to v1.2.
- **Self-service provider onboarding** (backlog 999.5) — a member adds their *own* Fastmail app password on first login. v1.1 covers this admin-managed (ADMIN-01); self-service deferred.
- **Calendar provider abstraction** (backlog 999.1) — provider interface so Fastmail is one of several backends.
- **Android PWA install walkthrough** verified on a real device (carried from v1.0).
- **Wizard re-run / reconfigure** flow after first setup.

## Out of Scope (explicit exclusions)

- **Notification-preferences UI, reminder snooze** — over-build for a 2-member household; the per-event selector (CAL-13) is sufficient.
- **Admin audit log, health dashboard, user-management/CRUD** — scope creep for a tiny self-hosted app.
- **Real-device iOS push / standalone CI** — remains a human/device gate, as in v1.0; the mobile harness covers responsive layout + authed flows only, not iOS-Safari-standalone behavior.
- **Mobile testing against the prod service-worker build past real Authelia** — `DEV_AUTH_BYPASS` only reaches the dev build (no real SW). A reusable Authelia storage-state to drive the prod-SW PWA is deferred; not worth the complexity for v1.1's layout/flow goal.
- **Redis pub/sub for the outbox drain** — the drain is single-process by design; an in-process EventEmitter is correct. (Redis stays for list SSE.)
- **node-cron** — silently skips ticks in the long-lived process; schedulers stay on `setInterval`.
- **drizzle-kit push** — emits a false destructive diff on populated MariaDB; migrations use generate+migrate.

## Traceability

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 |
|--------|-------|--------|
| TEST-01 | Phase 7 (Mobile Test Harness) | Complete |
| TEST-02 | Phase 7 (Mobile Test Harness) | Complete |
| CI-01 | Phase 8 (Gitea CI) | In progress |
| 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).
