Files
familysync/.planning/phases/12-initial-setup-wizard/12-04-SUMMARY.md
T

11 KiB


phase: 12-initial-setup-wizard plan: 04 subsystem: pwa, ui, api-client tags: [react, vite, tanstack-query, tdd, setup-wizard, oidc, playwright]

Dependency graph

requires:

  • phase: 12-02 provides: /api/setup/* routes (7 handlers, pre-auth mount)
  • phase: 12-03 provides: first-login-claims (upsertUser D-08) provides:
  • apps/pwa/src/api/client.ts — 7 setup API functions + SetupAlreadyLockedError
  • apps/pwa/src/routes/SetupPage.tsx — standalone 4-step wizard + Terminal/Locked screens
  • apps/pwa/src/App.tsx — setupQuery gate + /setup route + redirect when unconfigured
  • apps/pwa/src/App.test.tsx — gate tests (both branches)
  • apps/pwa/src/routes/SetupPage.test.tsx — wizard unit tests
  • .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md — revised (done in prior session 0f3c378) affects:
  • first-run operator experience (SETUP-01/SETUP-02)

Tech tracking

tech-stack: added: [] # Zero new packages patterns: - TDD RED/GREEN cycle — SetupPage.test.tsx (RED gate eb84e6e) → SetupPage.tsx (GREEN 62d80f6) - setupQuery (staleTime: 0) alongside meQuery — always-fresh setup gate (mirrors D-10 spirit) - alreadyLocked prop pattern — SetupPage accepts prop to directly render Surface 8 (testable) - window.history.pushState({}, '', '/') in beforeEach — URL isolation between BrowserRouter tests - nested inside route element — outer * route contains inner app-shell routes

key-files: created: - apps/pwa/src/routes/SetupPage.tsx - apps/pwa/src/routes/SetupPage.test.tsx - apps/pwa/src/App.test.tsx modified: - apps/pwa/src/api/client.ts - apps/pwa/src/App.tsx - .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md (prior session 0f3c378)

key-decisions:

  • "ALREADYLOCKED-PROP: SetupPage accepts alreadyLocked?: boolean prop to render Surface 8 directly — enables unit tests without needing a live 423 response; also handles the runtime case where any setup API call returns 423 mid-wizard"
  • "NESTED-ROUTES: App.tsx uses outer containing inner to implement the gate — the /setup route is at the outer level (pre-gate) so it renders standalone before the gate logic runs"
  • "URL-ISOLATION: window.history.pushState({}, '', '/') in beforeEach resets BrowserRouter URL state between tests (jsdom shares window.location across tests in the same file)"

Metrics

duration: 32min completed: 2026-06-15

Phase 12 Plan 04: PWA Setup Wizard Summary

Setup wizard PWA side: 7 API client functions, standalone 4-step SetupPage, App.tsx gate + /setup route; TDD; 236 tests pass; playwright-cli partial smoke pass

Performance

  • Duration: 32 min
  • Started: 2026-06-15T18:20:37Z
  • Completed: 2026-06-15T18:55:00Z (at checkpoint Task 4)
  • Tasks completed: 3 of 4 (Task 4 is a human-verify checkpoint)
  • Files modified: 5

Accomplishments

Task 1: UI-SPEC Revision (pre-existing, 0f3c378)

The UI-SPEC was revised in a prior planning session (commit 0f3c378). Verified all acceptance criteria pass:

  • No /api/setup/generate references (Generate Secrets step dropped per D-05)
  • Input fields for oidc_issuer, oidc_client_id, vapid_public_key, app_external_url present
  • Design system sections retained (Design System, Spacing Scale, Accessibility Contract)
  • Step indicator re-numbered to 4 steps (Welcome / Instance / Calendar / Complete)

Task 2: Setup API Client + SetupPage Wizard (TDD RED/GREEN)

RED gate (eb84e6e): 17 failing tests covering all 7 API function exports and SetupPage rendering.

GREEN (62d80f6): Implemented:

  • fetchSetupStatus() — GETs /api/setup/status; no credentials/redirect:manual (pre-auth endpoint)
  • postSetupConfig(payload) — POSTs non-secret config (app_url, oidc_issuer, oidc_client_id, vapid_public_key)
  • validateSetupDb() — POSTs /api/setup/validate/db; typed error message on failure
  • validateSetupOidc() — POSTs /api/setup/validate/oidc; typed error message on failure
  • validateSetupVapid() — POSTs /api/setup/validate/vapid; typed error message on failure
  • postSetupCredential(payload) — POSTs fastmailEmail + appPassword to /api/setup/credential
  • postSetupComplete() — POSTs /api/setup/complete; throws SetupAlreadyLockedError on 423
  • SetupAlreadyLockedError — typed error class for 423 responses

SetupPage.tsx:

  • Standalone full-page wizard — no AppNav/BottomTabBar imports
  • role="main" on content column; aria-live="polite" on validation rows
  • 4 sub-components: StepIndicator, ValidationRow, ActionRow, step cards
  • Step 1 (Welcome): orientation text, "Before you start" note block, Continue button
  • Step 2 (Instance Configuration): 4 fields (App URL, OIDC issuer, client_id, VAPID public key); Save & Validate triggers sequential DB+OIDC validation; Continue appears only when both pass
  • Step 3 (Calendar Credential): email+password fields; CalDAV validation; Complete Setup button
  • Surface 7 (Terminal): ShieldCheck icon, "Setup complete" heading, Sign in link
  • Surface 8 (Already Locked): via alreadyLocked prop or any 423 response mid-wizard
  • All copy is plain-text JSX children — no HTML injection
  • Focus management: stepHeadingRef.current.focus() on step change (a11y)

Task 3: App.tsx Gate + /setup Route (1587bca)

  • Added setupQuery = useQuery({ queryKey: ['setupStatus'], queryFn: fetchSetupStatus, retry: false, staleTime: 0 })
  • Added <Route path="/setup" element={<SetupPage />} /> at the outer Routes level (pre-gate)
  • Redirect gate: setupLoading → <div aria-hidden> | setupComplete===false → <Navigate to="/setup"> | true → full app shell
  • /setup route renders standalone — AppNav/BottomTabBar only render inside the setupComplete===true branch

App.test.tsx:

  • setupComplete: false → SetupPage renders, AppNav absent ✓
  • setupComplete: true → CalendarShell renders, AppNav present ✓
  • Loading state → CalendarShell absent (no flash) ✓

Task 4: playwright-cli Partial Smoke (CHECKPOINT — awaiting human)

playwright-cli Chromium verified (automated):

  • /setup renders standalone wizard: role="main", h1 "FamilySync Setup", 4-step indicator ✓
  • No <nav> element on the wizard page ✓
  • Welcome step: heading, pre-start block, Continue button ✓
  • Clicking Continue advances to step 2 (Instance Configuration) ✓
  • Step 2: all 4 input fields with correct placeholders; Save & Validate button; Back button ✓
  • Step 1 shows completed checkmark in step indicator ✓

Screenshots saved:

  • .planning/phases/12-initial-setup-wizard/screenshot-setup-welcome.png
  • .planning/phases/12-initial-setup-wizard/screenshot-setup-step2-config.png

Cannot be automated (requires human):

  1. The Docker API container needs docker compose build && docker compose up -d to pick up Phase 12 Plan 02 setup routes — the running container is from an older build without setup.js
  2. Fresh/unconfigured DB (no setup_complete, no member_credentials) needed to verify the automatic redirect-to-/setup flow
  3. Live OIDC discovery validation (requires running Authelia reachable from the container)
  4. Live CalDAV credential validation (requires real Fastmail app password)

Task Commits

  1. Task 1: UI-SPEC revision0f3c378 (prior session — docs)
  2. Task 2 RED: failing testseb84e6e (test)
  3. Task 2 GREEN: client.ts + SetupPage62d80f6 (feat)
  4. Task 3: App.tsx gate + tests1587bca (feat)
  5. Task 4: CHECKPOINT — awaiting human verification

Files Created/Modified

  • apps/pwa/src/api/client.ts — 7 setup functions + SetupAlreadyLockedError appended
  • apps/pwa/src/routes/SetupPage.tsx — new (standalone wizard, 5 surfaces, 530 lines)
  • apps/pwa/src/routes/SetupPage.test.tsx — new (17 tests, RED gate + implementation tests)
  • apps/pwa/src/App.tsx — setupQuery + /setup route + redirect gate added
  • apps/pwa/src/App.test.tsx — new (6 tests covering both gate branches)
  • .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md — revised (prior session 0f3c378)

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] require('./SetupPage.js') pattern incompatible with Vitest ESM mode

  • Found during: Task 2 test execution
  • Issue: RED test scaffolding used require('./SetupPage.js') inside test functions to import after mocks — but in Vitest's ESM mode this resolves at runtime and cannot find the .tsx source file
  • Fix: Changed to static import { SetupPage } from './SetupPage.js' at the top of the test file (mocks are hoisted via vi.mock so static imports work correctly)
  • Files modified: apps/pwa/src/routes/SetupPage.test.tsx
  • Commit: 62d80f6 (Task 2 GREEN)

2. [Rule 1 - Bug] BrowserRouter URL state persists between tests in jsdom

  • Found during: Task 3 App.test.tsx test run
  • Issue: setupComplete:false test redirected to /setup, leaving window.location at /setup for the setupComplete:true test. The /setup route matched the standalone SetupPage instead of the CalendarShell.
  • Fix: Added window.history.pushState({}, '', '/') in beforeEach to reset URL to root before each test
  • Files modified: apps/pwa/src/App.test.tsx
  • Commit: 1587bca (Task 3)

Known Stubs

None — all wizard steps render from live state (no hardcoded empty values). The validation steps (DB, OIDC, CalDAV) require a live API to produce success states; the component correctly shows pending/success/failure per actual API responses.

Threat Surface Scan

No new threat surface beyond what is explicitly modeled in the plan's threat_model:

  • T-12-13 (wizard never handles secrets): mitigated — no VAPID_PRIVATE_KEY or SESSION_SECRET inputs
  • T-12-14 (XSS via operator input): mitigated — no dangerouslySetInnerHTML in SetupPage.tsx (grep returns 0)
  • T-12-15 (app password disclosure): mitigated — type="password", never stored client-side
  • T-12-SC (new packages): mitigated — zero new npm packages

TDD Gate Compliance

  • RED gate: eb84e6e test commit (17 failing tests) — PRESENT ✓
  • GREEN gate: 62d80f6 feat commit (all tests pass) — PRESENT ✓
  • REFACTOR: no refactoring commit needed (implementation was clean on first pass)

Self-Check: PASSED

Files exist:

  • apps/pwa/src/api/client.ts — FOUND
  • apps/pwa/src/routes/SetupPage.tsx — FOUND
  • apps/pwa/src/routes/SetupPage.test.tsx — FOUND
  • apps/pwa/src/App.tsx — FOUND
  • apps/pwa/src/App.test.tsx — FOUND

Commits exist:

  • eb84e6e — FOUND
  • 62d80f6 — FOUND
  • 1587bca — FOUND

Test suite: 236 passed | 0 failed TypeCheck: clean (0 errors) Build: green (dist/sw.js emitted) playwright-cli: partial smoke pass (steps 1-2 verified headlessly; steps 3-4 require operator rebuild + fresh DB)