Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
248 lines
18 KiB
Markdown
248 lines
18 KiB
Markdown
---
|
|
phase: 12-initial-setup-wizard
|
|
plan: 04
|
|
type: execute
|
|
wave: 3
|
|
depends_on: ["12-02"]
|
|
files_modified:
|
|
- .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md
|
|
- apps/pwa/src/api/client.ts
|
|
- apps/pwa/src/routes/SetupPage.tsx
|
|
- apps/pwa/src/App.tsx
|
|
- apps/pwa/src/App.test.tsx
|
|
autonomous: false
|
|
requirements: [SETUP-01, SETUP-02]
|
|
must_haves:
|
|
truths:
|
|
- "On a fresh instance (GET /api/setup/status → {setupComplete:false}), the app redirects to /setup and renders the wizard with no AppNav/BottomTabBar"
|
|
- "The revised wizard collects non-secret config (OIDC issuer/client_id, VAPID public key, app URL) as input fields, then validates DB/OIDC/VAPID/CalDAV before completing"
|
|
- "There is no in-wizard secret-generation step (D-05 — generation is the repo helper, pre-boot)"
|
|
- "Completing the wizard (POST /api/setup/complete) shows the terminal 'Setup complete' screen with a Sign in link to /"
|
|
- "Navigating to /setup after completion (423) renders the 'Already Locked' screen"
|
|
- "When setupComplete:true, normal app boot proceeds (no /setup redirect)"
|
|
artifacts:
|
|
- path: ".planning/phases/12-initial-setup-wizard/12-UI-SPEC.md"
|
|
provides: "Revised Wizard-Steps + Interaction-Contract (Step 2 dropped, Steps 3/4 collect config)"
|
|
contains: "config"
|
|
- path: "apps/pwa/src/routes/SetupPage.tsx"
|
|
provides: "The standalone multi-step wizard component"
|
|
min_lines: 80
|
|
- path: "apps/pwa/src/App.tsx"
|
|
provides: "setup-status gate + /setup route"
|
|
contains: "setup"
|
|
key_links:
|
|
- from: "apps/pwa/src/App.tsx"
|
|
to: "/api/setup/status"
|
|
via: "setupQuery on load → redirect to /setup when unconfigured"
|
|
pattern: "setup/status|setupStatus"
|
|
- from: "apps/pwa/src/routes/SetupPage.tsx"
|
|
to: "/api/setup/* (config, validate, credential, complete)"
|
|
via: "TanStack Query mutations"
|
|
pattern: "setup/(config|validate|credential|complete)"
|
|
---
|
|
|
|
<objective>
|
|
Deliver the PWA side of the wizard: revise `12-UI-SPEC.md` (drop the Generate-Secrets step per D-05;
|
|
make the OIDC/VAPID step collect non-secret config inputs per D-02), build `SetupPage.tsx` (the
|
|
standalone full-page wizard following the revised UI-SPEC and the AdminPage/CredentialSheet patterns),
|
|
add the App.tsx setup-status gate + `/setup` route, and wire the `apps/pwa/src/api/client.ts` setup
|
|
client functions. Verify the flow with playwright-cli (desktop Chromium) per the CLAUDE.md convention.
|
|
|
|
Purpose: This is the operator-facing surface that closes SETUP-01 (guided bootstrap instead of
|
|
hand-editing files) and surfaces SETUP-02's per-input validation. The API routes (Plan 02) are the
|
|
contract this consumes.
|
|
Output: A working /setup wizard, the App-level gate, and a revised UI-SPEC matching D-02/D-04/D-05.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
|
@$HOME/.claude/gsd-core/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/12-initial-setup-wizard/12-CONTEXT.md
|
|
@.planning/phases/12-initial-setup-wizard/12-RESEARCH.md
|
|
@.planning/phases/12-initial-setup-wizard/12-PATTERNS.md
|
|
@.planning/phases/12-initial-setup-wizard/12-UI-SPEC.md
|
|
@apps/pwa/src/routes/AdminPage.tsx
|
|
@apps/pwa/src/components/CredentialSheet.tsx
|
|
@apps/pwa/src/App.tsx
|
|
@apps/pwa/src/api/client.ts
|
|
</context>
|
|
|
|
## Artifacts this phase produces (Plan 04 portion)
|
|
|
|
- Revised `12-UI-SPEC.md`: Step 2 (Generate Secrets) dropped; the OIDC/VAPID step gains input fields for oidc_issuer/oidc_client_id/vapid_public_key (+ app URL); 4-step flow (Welcome / Config / Validate / Credential — or planner-chosen equivalent) consistent with D-02/D-04/D-05
|
|
- `apps/pwa/src/api/client.ts`: `fetchSetupStatus`, `postSetupConfig`, `validateSetupDb/Oidc/Vapid`, `postSetupCredential`, `postSetupComplete`
|
|
- `apps/pwa/src/routes/SetupPage.tsx`: standalone wizard (no AppNav/BottomTabBar), Surfaces 1-8 per the revised UI-SPEC, plain-text JSX (no dangerouslySetInnerHTML)
|
|
- `apps/pwa/src/App.tsx`: `setupQuery` on /api/setup/status (staleTime 0) + `/setup` route + redirect gate when `setupComplete:false`
|
|
|
|
<tasks>
|
|
|
|
<task type="execute">
|
|
<name>Task 1: Revise 12-UI-SPEC.md (drop Generate-Secrets; config-collect inputs per D-02/D-04/D-05)</name>
|
|
<files>.planning/phases/12-initial-setup-wizard/12-UI-SPEC.md</files>
|
|
<read_first>
|
|
- .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md (the file being revised — §Surface 2 step labels, §Surface 4 Generated-Secret block, §Wizard Steps, §Copywriting Contract)
|
|
- .planning/phases/12-initial-setup-wizard/12-RESEARCH.md §UI-SPEC Revision Requirements (the authoritative table of what changes vs stays)
|
|
- .planning/phases/12-initial-setup-wizard/12-CONTEXT.md D-02/D-04/D-05 + the ⚠ Supersedes notes
|
|
</read_first>
|
|
<action>
|
|
Revise ONLY the Wizard-Steps, Interaction-Contract, Step-Indicator labels, Surface-4, and
|
|
Copywriting sections per RESEARCH.md §UI-SPEC Revision Requirements. DROP Step 2 "Generate Secrets"
|
|
entirely (no Secret Blocks, no acknowledgement checkboxes, no POST /api/setup/generate — generation
|
|
is the pre-boot repo helper, D-05); remove the Surface-4 Generated-Secret-Block section (or mark it
|
|
removed). Re-number the step indicator to the revised set (planner's call per CONTEXT discretion,
|
|
e.g. Welcome / Config / Validate / Credential — 4 steps). Convert the OIDC/VAPID step to COLLECT
|
|
non-secret config via input fields (oidc_issuer, oidc_client_id, vapid_public_key, app_external_url)
|
|
that POST to /api/setup/config, THEN validate (D-02). Update Step-1 description copy to remove the
|
|
"copy of docker-compose.yml to paste generated secrets into" reference. Leave the design system,
|
|
tokens, spacing, typography, color, a11y contract, security display rules, the Credential step, and
|
|
the Terminal/Locked screens UNCHANGED — do NOT re-derive the design system.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- source: the Generated-Secrets step is gone (`grep -ic "Generate Secrets\|Generated Secrets" .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md` returns 0, or any remaining hit is explicitly marked "REMOVED")
|
|
- source: the OIDC/config step now references input fields for the config keys (`grep -Ec "oidc_issuer|oidc_client_id|vapid_public_key|app_external_url|/api/setup/config" .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md` >= 2)
|
|
- source: no in-wizard generate endpoint (`grep -c "/api/setup/generate" .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md` returns 0)
|
|
- source: design-system sections retained (`grep -c "Design System\|Spacing Scale\|Accessibility Contract" .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md` >= 3)
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>! grep -iq "/api/setup/generate" .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md && grep -Eq "oidc_issuer|/api/setup/config" .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md</automated>
|
|
</verify>
|
|
<done>UI-SPEC steps revised: no generate-secrets step, config-collect inputs for the OIDC/VAPID step, step indicator re-numbered; design system untouched.</done>
|
|
</task>
|
|
|
|
<task type="execute" tdd="true">
|
|
<name>Task 2: Setup API client + SetupPage wizard component</name>
|
|
<files>apps/pwa/src/api/client.ts, apps/pwa/src/routes/SetupPage.tsx</files>
|
|
<read_first>
|
|
- apps/pwa/src/api/client.ts (the file being extended — fetchMe l.74, saveCredential l.429 patterns)
|
|
- apps/pwa/src/routes/AdminPage.tsx (analog: page component, useQuery/useMutation, section-label/button styles, PATTERNS.md §SetupPage.tsx)
|
|
- apps/pwa/src/components/CredentialSheet.tsx (analog: credential field layout, validation-state row, helper link, plain-text JSX — Step Credential reuses this exactly)
|
|
- .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md (the REVISED contract from Task 1 — surfaces, copy, a11y)
|
|
- .planning/phases/12-initial-setup-wizard/12-PATTERNS.md §SetupPage.tsx (imports, mutation, step-state patterns)
|
|
</read_first>
|
|
<behavior>
|
|
- fetchSetupStatus() GETs /api/setup/status → { setupComplete: boolean }
|
|
- postSetupConfig(payload) POSTs the four non-secret config values to /api/setup/config
|
|
- validateSetupDb/Oidc/Vapid() POST the three validation routes; map non-200 to a typed failure
|
|
- postSetupCredential({fastmailEmail, appPassword}) POSTs /api/setup/credential
|
|
- postSetupComplete() POSTs /api/setup/complete
|
|
- SetupPage renders the revised steps (Welcome → Config → Validate → Credential), the step indicator (Surface 2), per-step validation-state rows (Surface 5), the terminal "Setup complete" screen (Surface 7) on success, and the "Already Locked" screen (Surface 8) when status/complete returns 423
|
|
- No AppNav/BottomTabBar; role="main"; step heading h2; aria-live status rows; all copy plain-text JSX (no dangerouslySetInnerHTML)
|
|
</behavior>
|
|
<action>
|
|
Add the setup client functions to apps/pwa/src/api/client.ts following the existing fetch/JSON
|
|
conventions (same error-shape handling as fetchMe/saveCredential). Build
|
|
apps/pwa/src/routes/SetupPage.tsx per the REVISED UI-SPEC (Task 1) and PATTERNS.md §SetupPage.tsx:
|
|
local `useState` step cursor (no URL params, D-06 stateless); a TanStack `useMutation` per
|
|
POST step advancing the cursor onSuccess and surfacing a Surface-5 failure row onError; reuse the
|
|
CredentialSheet field/validation idiom verbatim for the Credential step; render Surface 7 on
|
|
/complete success and Surface 8 when an API call returns 423. Use the existing tokens.css custom
|
|
properties and lucide-react icons named in the UI-SPEC. All copy must be plain-text JSX children —
|
|
NO dangerouslySetInnerHTML (UI-SPEC security contract). Render standalone — no AppNav/BottomTabBar.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- source: client.ts exports the setup functions (`grep -Ec "fetchSetupStatus|postSetupConfig|postSetupComplete|postSetupCredential" apps/pwa/src/api/client.ts` >= 4)
|
|
- source: SetupPage references all setup routes (`grep -Ec "setup/config|setup/validate|setup/credential|setup/complete|setup/status" apps/pwa/src/routes/SetupPage.tsx` >= 4 — directly or via the client imports)
|
|
- source: no dangerouslySetInnerHTML (`grep -c "dangerouslySetInnerHTML" apps/pwa/src/routes/SetupPage.tsx` returns 0)
|
|
- source: standalone — SetupPage does not import AppNav/BottomTabBar (`grep -Ec "AppNav|BottomTabBar" apps/pwa/src/routes/SetupPage.tsx` returns 0)
|
|
- source: a11y — role="main" + aria-live present (`grep -Ec "role=\"main\"|aria-live" apps/pwa/src/routes/SetupPage.tsx` >= 1)
|
|
- test: `pnpm --filter @familysync/pwa typecheck` and `pnpm --filter @familysync/pwa build` green
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>cd apps/pwa && pnpm typecheck && pnpm build</automated>
|
|
</verify>
|
|
<done>Setup client functions added; SetupPage renders the revised 4-step wizard standalone with terminal/locked screens, no dangerouslySetInnerHTML; pwa typecheck + build green.</done>
|
|
</task>
|
|
|
|
<task type="execute">
|
|
<name>Task 3: App.tsx setup-status gate + /setup route + redirect</name>
|
|
<files>apps/pwa/src/App.tsx, apps/pwa/src/App.test.tsx</files>
|
|
<read_first>
|
|
- apps/pwa/src/App.tsx (the file being modified — meQuery l.65-70, Routes block l.133-153, isAdmin loading-gate l.144-150)
|
|
- apps/pwa/src/App.test.tsx (existing App routing tests to extend, if present; else mirror the meQuery test setup)
|
|
- .planning/phases/12-initial-setup-wizard/12-PATTERNS.md §App.tsx (setupQuery + gate + Navigate pattern)
|
|
- .planning/phases/12-initial-setup-wizard/12-UI-SPEC.md §Routing & App-Level Gate
|
|
</read_first>
|
|
<action>
|
|
In apps/pwa/src/App.tsx add `import { SetupPage } from './routes/SetupPage.js';` and a
|
|
`setupQuery = useQuery({ queryKey: ['setupStatus'], queryFn: fetchSetupStatus, retry: false,
|
|
staleTime: 0 })` alongside meQuery (staleTime 0 — the gate must not be stale, mirrors D-10 spirit).
|
|
Add `<Route path="/setup" element={<SetupPage />} />` to the Routes block. Add the redirect gate:
|
|
while setupQuery is loading render nothing (prevent flash, mirror the isAdmin loading-gate l.144-150);
|
|
when `setupQuery.data?.setupComplete === false`, redirect all non-/setup routes to /setup
|
|
(`<Navigate to="/setup" replace />`); when true, normal app boot proceeds. The /setup route renders
|
|
standalone — ensure the gate prevents AppNav/BottomTabBar from rendering over the wizard when
|
|
unconfigured (per UI-SPEC §Routing). Extend App.test.tsx: setupComplete:false → SetupPage/redirect
|
|
rendered; setupComplete:true → normal calendar route.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- source: setupQuery present (`grep -Ec "setupStatus|fetchSetupStatus" apps/pwa/src/App.tsx` >= 1)
|
|
- source: /setup route added (`grep -c "/setup" apps/pwa/src/App.tsx` >= 1)
|
|
- source: SetupPage imported (`grep -c "SetupPage" apps/pwa/src/App.tsx` >= 1)
|
|
- source: redirect gate keyed on setupComplete (`grep -Ec "setupComplete === false|setupComplete\\?" apps/pwa/src/App.tsx` >= 1)
|
|
- test: `pnpm --filter @familysync/pwa test -- App` green (both setupComplete branches)
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>cd apps/pwa && pnpm test -- App && pnpm typecheck</automated>
|
|
</verify>
|
|
<done>App.tsx queries /api/setup/status, exposes the /setup route, and redirects to /setup when unconfigured (no flash, no nav over wizard); App.test.tsx covers both branches.</done>
|
|
</task>
|
|
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
|
<name>Task 4: Verify the /setup wizard flow end-to-end (playwright-cli desktop)</name>
|
|
<action>Drive the /setup flow with playwright-cli (desktop Chromium) against a fresh/unconfigured DB per the verification steps below; escalate to the human only for steps playwright-cli cannot perform (e.g. a live Authelia/Fastmail round-trip).</action>
|
|
<what-built>The /setup wizard flow end-to-end in the PWA: redirect-to-/setup when unconfigured, the revised 4-step flow (Welcome → Config → Validate → Credential), validation-state rows, and the terminal "Setup complete" screen. Per CLAUDE.md the executor MUST first drive this with playwright-cli (desktop Chromium) — only fall back to a human if a step genuinely cannot be driven headlessly.</what-built>
|
|
<how-to-verify>
|
|
1. Bring up the dev stack against a FRESH/unconfigured DB (no setup_complete, no member_credentials) — see MEMORY familysync-dev-stack-setup; the API + PWA dev servers + MariaDB.
|
|
2. Using playwright-cli (`/usr/local/bin/playwright-cli`), navigate to the app root and confirm it redirects to /setup and renders the wizard with NO AppNav/BottomTabBar.
|
|
3. Drive the wizard: Config step accepts the OIDC issuer/client_id + VAPID public key + app URL inputs and POSTs /api/setup/config; Validate step shows pending→success rows for DB/OIDC/VAPID (mock or live as available); Credential step accepts a Fastmail email + app password (use a known-good or mocked credential) and shows "Credential verified."; Complete shows the "Setup complete" terminal screen with a Sign in link to /.
|
|
4. Re-navigate to /setup after completion and confirm the "Already Locked" screen renders (API 423).
|
|
5. Capture screenshots of the wizard, a validation-success row, and the terminal screen into the phase dir for the SUMMARY.
|
|
Only escalate to the human for steps playwright-cli cannot perform (e.g. a live Authelia/Fastmail round-trip if no mock is wired) — note any such steps explicitly.
|
|
</how-to-verify>
|
|
<resume-signal>Type "approved" or describe the issues observed</resume-signal>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## Trust Boundaries
|
|
|
|
| Boundary | Description |
|
|
|----------|-------------|
|
|
| operator browser → /api/setup/* | the wizard is the unauthenticated client of the pre-auth API; it submits non-secret config + the Fastmail app password |
|
|
| SetupPage render → DOM | operator-supplied copy/config values rendered; XSS risk if not plain-text |
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
|
|-----------|----------|-----------|-------------|-----------------|
|
|
| T-12-13 | Information Disclosure | wizard never displays/handles secrets | mitigate | D-05 — no generate-secrets step; the wizard never receives SESSION_SECRET/encryption key/VAPID private key; only the non-secret VAPID public key is an input |
|
|
| T-12-14 | Tampering (XSS) | SetupPage rendering operator input | mitigate | No dangerouslySetInnerHTML (acceptance-checked); all copy + config values rendered as plain-text JSX children (UI-SPEC security contract) |
|
|
| T-12-15 | Information Disclosure | app password in the Credential step | mitigate | type="password" input (UI-SPEC); reuses CredentialSheet idiom; server-side noEchoHook (Plan 02) ensures the value is never echoed back |
|
|
| T-12-SC | Tampering | npm/pip/cargo installs | accept | Zero new packages — lucide-react/react-query/react-router already installed (RESEARCH §Standard Stack) |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
- `cd apps/pwa && pnpm typecheck && pnpm build` green
|
|
- `pnpm --filter @familysync/pwa test -- App` green (both setupComplete branches)
|
|
- UI-SPEC revised: no generate-secrets step, config-collect inputs present
|
|
- playwright-cli desktop smoke: redirect→wizard→config→validate→credential→complete + locked screen
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- SETUP-01: fresh instance redirects to /setup; guided multi-step wizard renders standalone
|
|
- SETUP-02: each input validates (DB/OIDC/VAPID/CalDAV) before the step completes
|
|
- D-05 honored: no in-wizard secret generation
|
|
- Terminal + Already-Locked screens behave per UI-SPEC; playwright-cli smoke passes
|
|
</success_criteria>
|
|
|
|
<output>
|
|
Create `.planning/phases/12-initial-setup-wizard/12-04-SUMMARY.md` when done
|
|
</output>
|