style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
This commit is contained in:
+80
-80
@@ -44,27 +44,27 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
||||
**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.
|
||||
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).
|
||||
- **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**: 4 plans (3 waves)Plans:
|
||||
**Wave 1**
|
||||
|
||||
- [x] 07-01-PLAN.md — Harness foundation: @playwright/test + WebKit/Chromium browsers, playwright.config.ts (iPhone/WebKit + Pixel/Chromium matrix, serviceWorkers block, env baseURL, vite webServer), vitest exclude, scripts (Wave 1)
|
||||
|
||||
**Wave 2** *(blocked on Wave 1 completion)*
|
||||
**Wave 2** _(blocked on Wave 1 completion)_
|
||||
|
||||
- [x] 07-02-PLAN.md — global-setup.ts: /health readiness poll + deterministic mysql2 reset-and-seed (calendar id 10 INSERT IGNORE guard, list + items) + e2e README/guardrails (Wave 2)
|
||||
|
||||
**Wave 3** *(blocked on Wave 2 completion)*
|
||||
**Wave 3** _(blocked on Wave 2 completion)_
|
||||
|
||||
- [x] 07-03-PLAN.md — layout.spec.ts: tap targets >=44px, no overflow, in-viewport, accessible names (UI-SPEC Rules 1-4) + harness self-validation injected-defect proofs (Wave 3)
|
||||
- [x] 07-04-PLAN.md — calendar.spec.ts + lists.spec.ts: populated/empty/error states (Rules 4/5) + DEV_AUTH_BYPASS auth-reached + no-SW-controller precondition (Wave 3)
|
||||
@@ -79,35 +79,35 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
||||
**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.
|
||||
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).
|
||||
- **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**: 4 plans (4 waves)Plans:
|
||||
**Wave 1**
|
||||
|
||||
- [x] 08-01-PLAN.md — Runner probe + operator runner/PAT registration (W0; answers the Docker-vs-host fork)
|
||||
|
||||
**Wave 2** *(blocked on Wave 1 completion)*
|
||||
**Wave 2** _(blocked on Wave 1 completion)_
|
||||
|
||||
- [x] 08-02-PLAN.md — ci.yml: fast-checks (lint/typecheck/PWA unit) + API job (MariaDB service + migrate + DB-backed tests)
|
||||
|
||||
**Wave 3** *(blocked on Wave 2 completion)*
|
||||
**Wave 3** _(blocked on Wave 2 completion)_
|
||||
|
||||
- [x] 08-03-PLAN.md — ci.yml: harness job (dev-stack bring-up + readiness waits + Phase 7 Playwright specs, both profiles)
|
||||
|
||||
**Wave 4** *(blocked on Wave 3 completion)*
|
||||
**Wave 4** _(blocked on Wave 3 completion)_
|
||||
|
||||
- [x] 08-04-PLAN.md — ci.yml: publish job (build production image + push :latest + :v1.1-<sha> via --password-stdin)
|
||||
|
||||
@@ -121,17 +121,17 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
||||
**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).
|
||||
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.
|
||||
- **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
|
||||
|
||||
@@ -143,17 +143,17 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
||||
**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).
|
||||
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.
|
||||
- **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
|
||||
@@ -166,19 +166,19 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
||||
**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.
|
||||
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).
|
||||
- **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
|
||||
@@ -191,17 +191,17 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
||||
**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.
|
||||
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).
|
||||
- **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
|
||||
@@ -214,9 +214,9 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
||||
**Requirements**: none (promoted from backlog 999.16; no formal REQ-IDs)
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. **SC-1:** `pnpm lint` exits non-zero on an introduced violation (today it exits 0); `pnpm format:check` exits non-zero on an unformatted file.
|
||||
2. **SC-2:** The CI `fast-checks` lint step blocks a PR to main on lint violations, and a new `format:check` step blocks on format violations.
|
||||
3. **SC-3:** The first real run's existing violations are resolved so the baseline gate ends green — `pnpm lint` AND `pnpm format:check` both exit 0 across both apps.
|
||||
1. **SC-1:** `pnpm lint` exits non-zero on an introduced violation (today it exits 0); `pnpm format:check` exits non-zero on an unformatted file.
|
||||
2. **SC-2:** The CI `fast-checks` lint step blocks a PR to main on lint violations, and a new `format:check` step blocks on format violations.
|
||||
3. **SC-3:** The first real run's existing violations are resolved so the baseline gate ends green — `pnpm lint` AND `pnpm format:check` both exit 0 across both apps.
|
||||
|
||||
**Decisions** (LOCKED, from 13-CONTEXT.md): D-13-01 `recommendedTypeChecked` via `projectService:true`; D-13-02 React/hooks plugins scoped to `apps/pwa` only; D-13-03 no strict presets; D-13-04 `--max-warnings 0`; D-13-05 fix all violations now; D-13-06 fixes address, never mask (justified suppressions only); D-13-07 Prettier standalone + `eslint-config-prettier`; D-13-08 isolated reformat commit; D-13-09 lint all TS/TSX incl. tests/e2e/config; D-13-10 `disableTypeChecked` override for non-project files.
|
||||
**Plans**: 3 plans
|
||||
@@ -224,31 +224,31 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
||||
|
||||
- [x] 13-01-PLAN.md — Install ESLint/Prettier deps + flat config + package scripts + prove the gate fails (SC-1)
|
||||
|
||||
**Wave 2** *(blocked on Wave 1 completion)*
|
||||
**Wave 2** _(blocked on Wave 1 completion)_
|
||||
|
||||
- [x] 13-02-PLAN.md — Fix all first-run lint violations across both apps, green `pnpm lint` (D-13-05/06)
|
||||
|
||||
**Wave 3** *(blocked on Wave 2 completion)*
|
||||
**Wave 3** _(blocked on Wave 2 completion)_
|
||||
|
||||
- [ ] 13-03-PLAN.md — Prettier reformat (isolated commit) + CI format:check step + green baseline (SC-2, SC-3)
|
||||
|
||||
## 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 | 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 | - |
|
||||
| 13. Real Lint Gate (ESLint) | v1.1 | 2/3 | In 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 | 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 | - |
|
||||
| 13. Real Lint Gate (ESLint) | v1.1 | 2/3 | In Progress | |
|
||||
|
||||
## Backlog
|
||||
|
||||
@@ -264,7 +264,7 @@ Plans:
|
||||
|
||||
### 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.
|
||||
**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).
|
||||
|
||||
@@ -298,7 +298,7 @@ Plans:
|
||||
|
||||
**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.
|
||||
> **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
|
||||
@@ -356,7 +356,7 @@ Plans:
|
||||
- **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.
|
||||
**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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user