diff --git a/.planning/phases/12-initial-setup-wizard/12-UI-SPEC.md b/.planning/phases/12-initial-setup-wizard/12-UI-SPEC.md index ac2d489..d7c08e5 100644 --- a/.planning/phases/12-initial-setup-wizard/12-UI-SPEC.md +++ b/.planning/phases/12-initial-setup-wizard/12-UI-SPEC.md @@ -5,12 +5,18 @@ status: draft shadcn_initialized: false preset: none created: 2026-06-14 +updated: 2026-06-15 --- # Phase 12 — UI Design Contract: Initial Setup Wizard > Visual and interaction contract for the first-run setup wizard. > Generated by gsd-ui-researcher. Verified by gsd-ui-checker. +> +> **Revision note (2026-06-15):** CONTEXT.md (D-04/D-05/D-06) supersedes the original +> validate-only model. Step 2 "Generate Secrets" is dropped (generation is pre-boot via repo +> helper script). Steps 3–4 are reworked to collect config via input fields, not just validate +> env. All other design tokens, surfaces, and a11y contracts are unchanged. --- @@ -60,10 +66,8 @@ Uses the existing 4px-based scale. No new tokens. Values from `tokens.css`: Exceptions: - Wizard card max-width: 540px (slightly wider than CredentialSheet 480px to accommodate - multi-field steps and generated-secret blocks). + multi-field steps). - Step indicator touch targets: 44px minimum (accessibility). -- Copy-to-clipboard button: 36px height is acceptable since it is paired with an adjacent - textarea (which itself is large enough), but the copy button must have `minWidth: 44px`. --- @@ -80,11 +84,10 @@ All values from `tokens.css`. No new sizes or weights. Usage in this phase: - Wizard page title ("FamilySync Setup"): Display (24px/600/1.2) -- Step heading (e.g. "Generate Secrets"): Heading (18px/600/1.25) +- Step heading (e.g. "OIDC & App URL"): Heading (18px/600/1.25) - Step description / helper text: Body (15px/400/1.5) - Field labels, step counter, status badges: Label (13px/400/1.4) — labels use weight 600 -- Generated secret value (monospace block): 13px/400/1.4 with `font-family: monospace` override -- Section labels (uppercase caps, e.g. "STEP 2 OF 5"): Label (13px/600) with +- Section labels (uppercase caps, e.g. "STEP 2 OF 4"): Label (13px/600) with `text-transform: uppercase; letter-spacing: 0.06em` (AdminPage `sectionLabelStyle` pattern) --- @@ -96,8 +99,8 @@ All values from `tokens.css`. No new hex values. | Role | Value | Variable | Usage | |------|-------|----------|-------| | Dominant (60%) | #ffffff | var(--color-surface) | Page background, card background | -| Secondary (30%) | #f7f7f8 | var(--color-surface-dim) | Step sidebar/tracker background, generated-secret block background, inactive step indicator | -| Accent (10%) | #4a90d9 | var(--color-member-0) | Primary CTA buttons, active step indicator fill, spinner, copy button, links | +| Secondary (30%) | #f7f7f8 | var(--color-surface-dim) | Step sidebar/tracker background, inactive step indicator | +| Accent (10%) | #4a90d9 | var(--color-member-0) | Primary CTA buttons, active step indicator fill, spinner, links | | Destructive | #dc2626 | var(--color-destructive) | Validation failure border + helper text (same CredentialSheet pattern) | Accent reserved for: @@ -105,7 +108,6 @@ Accent reserved for: - Active wizard step indicator (filled circle) - Inline spinner (`Loader2`) during async validation - Hyperlinks (e.g. "Get an app password") -- Copy-to-clipboard button icon - Focus ring (`var(--color-focus-ring): #4a90d9`) Additional semantic colors (not new — already in tokens.css): @@ -120,13 +122,11 @@ Additional semantic colors (not new — already in tokens.css): ## Surface Architecture The wizard is a **standalone full-page route** (`/setup`) mounted in a separate React root or -an App-level gate (see Interaction Contract). It renders none of the AppNav / BottomTabBar / +an App-level gate (see Routing section). It renders none of the AppNav / BottomTabBar / SetupBanner chrome. ### Surface 1 — Wizard Page Shell -The wizard page itself. - - Background: `var(--color-surface)` (#ffffff) - Layout: vertically centered column, `min-height: 100dvh` - Content column: `maxWidth: 540px`, `margin: 0 auto`, @@ -140,7 +140,7 @@ The wizard page itself. Linear step tracker shown above the active step card. -- Horizontal row of N step circles connected by lines +- Horizontal row of 4 step circles connected by lines - Completed step: filled circle `var(--color-member-0)` with white `Check` icon (16px) - Active step: filled circle `var(--color-member-0)` with white step number (13px/600) - Upcoming step: circle with `var(--color-border)` 2px border, `var(--color-text-muted)` step @@ -152,12 +152,11 @@ Linear step tracker shown above the active step card. - Circle size: 28px diameter; connector height: 1px; minimum row height: 44px touch target achieved by centering in a 44px tall row -Step labels (5 steps total): +Step labels (4 steps total): 1. Welcome -2. Secrets -3. Database -4. OIDC -5. Calendar +2. Instance +3. Calendar +4. Complete ### Surface 3 — Step Card @@ -174,27 +173,23 @@ The active step's input/content area. One card rendered at a time. `marginBottom: var(--space-6)` (24px) - Field group spacing: `var(--space-4)` (16px) between fields -### Surface 4 — Generated-Secret Block +### Surface 4 — Input Field -Used in Step 2 (Secrets) for values the operator must copy into env. +Standard text input used across steps 2–3 to collect config. -- Background: `var(--color-surface-dim)` (#f7f7f8) -- Border: `1px solid var(--color-border-subtle)` (#eceef2) -- Border-radius: 4px (`var(--space-1)`) -- Padding: `var(--space-3) var(--space-4)` (12px 16px) -- Secret value: monospace, 13px/400, `var(--color-text-primary)`, word-break: break-all - (VAPID keys are long strings) -- Label above block: Label (13px/600), `var(--color-text-primary)` -- Copy button: icon-only (`Copy` icon 16px, `var(--color-member-0)`), positioned top-right - inside the block, `minWidth: 44px`, `minHeight: 36px` (acceptable — paired with large block) -- After copy: icon swaps to `Check` (16px, `var(--color-member-0)`) for 2 seconds, then reverts -- Acknowledgement checkbox below each secret block: standard checkbox input, Label (13px/400), - "I have copied this value into my `.env` file." — the Continue button is disabled until all - checkboxes on the step are checked. +- Width: 100%, `box-sizing: border-box` +- Padding: `var(--space-3, 12px) var(--space-4, 16px)` (matches CredentialSheet pattern) +- Border: `1px solid var(--color-border)` default; `1px solid var(--color-destructive)` on + validation error +- Border-radius: `var(--space-1, 4px)` (4px) +- Font: 15px/400, `var(--color-text-primary)`, `var(--font-family-base)` +- Background: `var(--color-surface)` +- Label above: 13px/600, `var(--color-text-primary)`, `marginBottom: var(--space-1)` (4px) +- Helper text below: 13px/400, `var(--color-text-secondary)` ### Surface 5 — Validation State Row -Shown after the operator submits a validation step (DB / OIDC / CalDAV). +Shown after a validation request is triggered (DB connectivity / OIDC discovery / CalDAV PROPFIND). - Pending: `Loader2` icon (16px, `var(--color-member-0)`, `animation: spin 1s linear infinite`) + Body (15px/400) status text in `var(--color-text-secondary)` — inline row @@ -203,6 +198,7 @@ Shown after the operator submits a validation step (DB / OIDC / CalDAV). - Failure: `AlertCircle` icon (16px, `var(--color-destructive)`) + error text Body (15px/400) in `var(--color-destructive)` — same pattern as CredentialSheet `FAILURE_TEXT` - Layout: `display: flex; alignItems: center; gap: var(--space-2, 8px)` (CredentialSheet pattern) +- Container: `role="status"` with `aria-live="polite"` ### Surface 6 — Action Row @@ -218,11 +214,11 @@ Bottom of each step card. `borderRadius: var(--space-1)` (4px), `transition: background 0.15s ease` — disabled state: `background: var(--color-border)` (#e2e4e9), `cursor: default` (AdminPage / CredentialSheet pattern) -- "Continue" label on steps 1–4; "Complete Setup" label on step 5 +- "Continue" label on steps 1–3; "Complete Setup" label on step 4 ### Surface 7 — Terminal "Setup Complete" Screen -Replaces the wizard card after step 5 completes successfully. +Replaces the wizard card after step 4 completes successfully. - Icon: `ShieldCheck` (48px, `var(--color-member-0)`) centered - Heading: "Setup complete" — Display (24px/600), centered, `marginTop: var(--space-4)`, @@ -249,78 +245,91 @@ Shown when the operator navigates to `/setup` after `setup_complete = true` (423 ## Wizard Steps — Detailed Interaction Contract +> **REVISION (2026-06-15, CONTEXT.md D-04/D-05):** The original 5-step model included a +> "Generate Secrets" step (Step 2). This step is **dropped**. Secret generation (SESSION_SECRET, +> APP_PASSWORD_ENCRYPTION_KEY, VAPID public/private keys) is done pre-boot via the +> `npm run generate-secrets` repo helper script. The wizard never generates, displays, or +> requests acknowledgement of secrets. Steps are now 4 total. + ### Step 1: Welcome Purpose: orient the operator; no inputs; no validation. - Heading: "Welcome to FamilySync Setup" -- Description: "This wizard will guide you through configuring your self-hosted instance. - You'll need: your OIDC client credentials (Authelia), a Fastmail account with an app password, - and a copy of your `docker-compose.yml` to paste generated secrets into. This takes about - 5 minutes." +- Description: "This wizard will guide you through configuring your self-hosted instance. Before + continuing, run `npm run generate-secrets` from the repo to generate your instance secrets and + add them to your Docker environment. You'll also need: your OIDC client credentials (Authelia) + and a Fastmail account with an app password. This takes about 5 minutes." +- Informational note block (Surface 3 — inside the card, `background: var(--color-surface-dim)`, + `border-radius: 4px`, `padding: var(--space-3) var(--space-4)`, `marginBottom: var(--space-4)`): + - Label (13px/600, `var(--color-text-primary)`): "Before you start" + - Body: "Run `npm run generate-secrets` and add the output to your Docker environment block. + These secrets cannot be recovered if lost." - No input fields. - Continue button: always enabled. -### Step 2: Generate Secrets +### Step 2: Instance Configuration -Purpose: display generated session secret, encryption key, and VAPID keypair; operator copies -each into env. +Purpose: collect non-secret runtime config that the wizard writes to `app_config`. No secrets +are collected here. Validates DB connectivity and OIDC discovery. -- Heading: "Generated Secrets" -- Description: "These values are generated once and displayed now. Copy each into your - `docker-compose.yml` environment block before continuing. They will never be shown again and - are not stored in the database." -- Four generated-secret blocks (Surface 4), each with acknowledgement checkbox: - 1. `SESSION_SECRET` — label "Session secret", 64-char hex string - 2. `APP_PASSWORD_ENCRYPTION_KEY` — label "Encryption key", 64-char hex string - 3. `VAPID_PUBLIC_KEY` — label "VAPID public key" - 4. `VAPID_PRIVATE_KEY` — label "VAPID private key" -- Continue button disabled until all 4 checkboxes are checked. -- No async validation on this step. Secrets are generated client-side or fetched from - `POST /api/setup/generate` (backend choice — UI treats them as string values to display). +- Heading: "Instance Configuration" +- Description: "Enter your instance's connection details. These are written to the database — + not your environment file." -### Step 3: Database +**Fields (collected and written to `app_config`):** -Purpose: verify the DB connection configured in env is reachable. +1. **App URL** + - Label: "App URL" + - Type: `text`, placeholder: `https://familysync.example.com` + - Helper: "The public URL where FamilySync is reachable." + - `app_config` key: `app_url` -- Heading: "Database Connection" -- Description: "Verify that the app can reach the MariaDB database configured in your - environment. No changes are made — this is a read-only connectivity check." -- No operator input fields (DB creds come from env, not from this UI). -- "Test Connection" button (primary filled, full-width on this step — replace normal action row): - triggers `POST /api/setup/validate/db` -- Validation state row (Surface 5) shown below the description during/after the test: - - Pending: "Testing database connection…" - - Success: "Database connection verified." - - Failure: "Cannot reach the database. Check DB_HOST, DB_PORT, DB_USER, DB_PASSWORD in your - `docker-compose.yml` and try again." -- Continue button appears after success; disabled during pending; hidden on failure (operator - must retry first). +2. **OIDC Issuer** + - Label: "OIDC issuer URL" + - Type: `text`, placeholder: `https://auth.example.com` + - Helper: "Your Authelia instance URL. FamilySync will fetch `/.well-known/openid-configuration` + from this URL." + - `app_config` key: `oidc_issuer` + +3. **OIDC Client ID** + - Label: "OIDC client ID" + - Type: `text`, placeholder: `familysync` + - Helper: "The client ID registered in Authelia for this application." + - `app_config` key: `oidc_client_id` + +4. **VAPID Public Key** + - Label: "VAPID public key" + - Type: `text`, placeholder: `BH…` (URL-safe base64, 87 chars) + - Helper: "Paste the `VAPID_PUBLIC_KEY` value from `npm run generate-secrets`." + - `app_config` key: `vapid_public_key` + +**Validations (triggered by "Save & Validate" button):** + +Two sequential checks run after the operator taps the action button: + +1. **Database** — `POST /api/setup/validate/db` + - Validation state row (Surface 5): + - Pending: "Testing database connection…" + - Success: "Database connection verified." + - Failure: "Cannot reach the database. Check DB_HOST, DB_PORT, DB_USER, DB_PASSWORD in your + Docker environment and try again." + +2. **OIDC discovery** — `POST /api/setup/validate/oidc` (runs after DB success) + - Validation state row (Surface 5): + - Pending: "Checking OIDC discovery…" + - Success: "OIDC discovery resolved." + - Failure: "OIDC discovery failed. Check the issuer URL and that Authelia is reachable from + the server." + +- Action button label on this step: "Save & Validate" (primary filled, full-width on this + step — replace the normal right-aligned action row with a full-width button above the validation + state rows, then normal Continue/Back row appears below once both pass) +- Continue appears (enabled) only when both validation rows show success and config has been saved + (`POST /api/setup/config` call completes before validation begins). - Back is available. -### Step 4: OIDC & VAPID - -Purpose: verify OIDC discovery resolves and the VAPID keypair in env is structurally valid. - -- Heading: "OIDC & Push" -- Description: "Verify that the Authelia OIDC issuer is reachable and that the VAPID keypair - you copied in Step 2 is in place." -- Two validation rows, triggered sequentially by "Validate" button: - - OIDC: label "Authelia issuer discovery", `POST /api/setup/validate/oidc` - - Pending: "Checking OIDC discovery…" - - Success: "OIDC discovery resolved." - - Failure: "OIDC discovery failed. Check OIDC_ISSUER in your environment and that Authelia - is reachable." - - VAPID: label "VAPID keypair", `POST /api/setup/validate/vapid` - - Pending: "Checking VAPID keypair…" - - Success: "VAPID keypair is valid." - - Failure: "VAPID private key could not be verified. Ensure you copied both keys from Step 2 - into your environment and restarted the container." -- "Validate" button triggers both checks in sequence. -- Continue appears (and is enabled) only when both rows show success. -- Back is available. - -### Step 5: Calendar Credential +### Step 3: Calendar Credential Purpose: set the first member's Fastmail app password; validate against CalDAV PROPFIND. Reuses the CredentialSheet interaction pattern (same fields, same validation feedback, @@ -344,10 +353,16 @@ same copy). - Failure: "Invalid password — CalDAV validation failed. Check the scope is 'Calendars & Contacts (CalDAV)' and try again." (in `var(--color-destructive)`) - Continue button label on this step: "Complete Setup" -- On continue: `POST /api/setup/complete` — promotes operator to admin, sets - `app_config.setup_complete`, redirects to Surface 7 (Terminal Screen) +- On continue: `POST /api/setup/complete` — provisions the pre-OIDC local user + credential, + flips `app_config.setup_complete`, redirects to Surface 7 (Terminal Screen) - Back is available. +### Step 4 — no longer a step card; becomes the Terminal Screen + +After `POST /api/setup/complete` succeeds, the wizard card is replaced by Surface 7 (Terminal +"Setup Complete" screen). There is no separate "Step 4" card — the terminal screen IS the +completion state. + --- ## Routing & App-Level Gate @@ -374,8 +389,9 @@ contract requires: no AppNav, no BottomTabBar, no SetupBanner, no PermissionDeni |---------|------| | Page title | "FamilySync Setup" | | Page subtitle | "Let's get your instance ready." | -| Primary CTA (steps 1–4) | "Continue" | -| Primary CTA (step 5) | "Complete Setup" | +| Primary CTA (steps 1–2) | "Continue" | +| Step 2 action button | "Save & Validate" | +| Primary CTA (step 3) | "Complete Setup" | | Secondary action | "Back" | | Terminal heading | "Setup complete" | | Terminal body | "Your FamilySync instance is ready. Sign in to continue." | @@ -384,36 +400,40 @@ contract requires: no AppNav, no BottomTabBar, no SetupBanner, no PermissionDeni | Locked body | "This instance has already been configured. Sign in to continue." | | Locked link | "Sign in" | | Step 1 heading | "Welcome to FamilySync Setup" | -| Step 1 description | "This wizard will guide you through configuring your self-hosted instance. You'll need: your OIDC client credentials (Authelia), a Fastmail account with an app password, and a copy of your `docker-compose.yml` to paste generated secrets into. This takes about 5 minutes." | -| Step 2 heading | "Generated Secrets" | -| Step 2 description | "These values are generated once and displayed now. Copy each into your `docker-compose.yml` environment block before continuing. They will never be shown again and are not stored in the database." | -| Step 2 acknowledgement | "I have copied this value into my `.env` file." | -| Step 3 heading | "Database Connection" | -| Step 3 description | "Verify that the app can reach the MariaDB database configured in your environment. No changes are made — this is a read-only connectivity check." | -| Step 3 CTA | "Test Connection" | -| Step 3 pending | "Testing database connection…" | -| Step 3 success | "Database connection verified." | -| Step 3 failure | "Cannot reach the database. Check DB_HOST, DB_PORT, DB_USER, DB_PASSWORD in your `docker-compose.yml` and try again." | -| Step 4 heading | "OIDC & Push" | -| Step 4 description | "Verify that the Authelia OIDC issuer is reachable and that the VAPID keypair you copied in Step 2 is in place." | -| Step 4 CTA | "Validate" | -| Step 4 OIDC pending | "Checking OIDC discovery…" | -| Step 4 OIDC success | "OIDC discovery resolved." | -| Step 4 OIDC failure | "OIDC discovery failed. Check OIDC_ISSUER in your environment and that Authelia is reachable." | -| Step 4 VAPID pending | "Checking VAPID keypair…" | -| Step 4 VAPID success | "VAPID keypair is valid." | -| Step 4 VAPID failure | "VAPID private key could not be verified. Ensure you copied both keys from Step 2 into your environment and restarted the container." | -| Step 5 heading | "Fastmail Credential" | -| Step 5 description | "Add the Fastmail app password for the first household member. This credential is validated against Fastmail CalDAV before saving. The password is never stored in plain text." | -| Step 5 helper text | "Enter the Fastmail app password scoped to Calendars/CalDAV." | -| Step 5 helper link text | "Get an app password" | -| Step 5 helper link suffix | " — choose the 'Calendars & Contacts (CalDAV)' scope." | -| Step 5 pending | "Validating against CalDAV…" | -| Step 5 success | "Credential verified." | -| Step 5 failure | "Invalid password — CalDAV validation failed. Check the scope is 'Calendars & Contacts (CalDAV)' and try again." | -| Empty state (none for wizard — every step has explicit content) | N/A | +| Step 1 description | "This wizard will guide you through configuring your self-hosted instance. Before continuing, run `npm run generate-secrets` from the repo to generate your instance secrets and add them to your Docker environment. You'll also need: your OIDC client credentials (Authelia) and a Fastmail account with an app password. This takes about 5 minutes." | +| Step 1 pre-start label | "Before you start" | +| Step 1 pre-start body | "Run `npm run generate-secrets` and add the output to your Docker environment block. These secrets cannot be recovered if lost." | +| Step 2 heading | "Instance Configuration" | +| Step 2 description | "Enter your instance's connection details. These are written to the database — not your environment file." | +| Step 2 field: App URL label | "App URL" | +| Step 2 field: App URL placeholder | "https://familysync.example.com" | +| Step 2 field: App URL helper | "The public URL where FamilySync is reachable." | +| Step 2 field: OIDC issuer label | "OIDC issuer URL" | +| Step 2 field: OIDC issuer placeholder | "https://auth.example.com" | +| Step 2 field: OIDC issuer helper | "Your Authelia instance URL. FamilySync will fetch `/.well-known/openid-configuration` from this URL." | +| Step 2 field: OIDC client ID label | "OIDC client ID" | +| Step 2 field: OIDC client ID placeholder | "familysync" | +| Step 2 field: OIDC client ID helper | "The client ID registered in Authelia for this application." | +| Step 2 field: VAPID public key label | "VAPID public key" | +| Step 2 field: VAPID public key placeholder | "BH…" | +| Step 2 field: VAPID public key helper | "Paste the `VAPID_PUBLIC_KEY` value from `npm run generate-secrets`." | +| Step 2 DB pending | "Testing database connection…" | +| Step 2 DB success | "Database connection verified." | +| Step 2 DB failure | "Cannot reach the database. Check DB_HOST, DB_PORT, DB_USER, DB_PASSWORD in your Docker environment and try again." | +| Step 2 OIDC pending | "Checking OIDC discovery…" | +| Step 2 OIDC success | "OIDC discovery resolved." | +| Step 2 OIDC failure | "OIDC discovery failed. Check the issuer URL and that Authelia is reachable from the server." | +| Step 3 heading | "Fastmail Credential" | +| Step 3 description | "Add the Fastmail app password for the first household member. This credential is validated against Fastmail CalDAV before saving. The password is never stored in plain text." | +| Step 3 helper text | "Enter the Fastmail app password scoped to Calendars/CalDAV." | +| Step 3 helper link text | "Get an app password" | +| Step 3 helper link suffix | " — choose the 'Calendars & Contacts (CalDAV)' scope." | +| Step 3 pending | "Validating against CalDAV…" | +| Step 3 success | "Credential verified." | +| Step 3 failure | "Invalid password — CalDAV validation failed. Check the scope is 'Calendars & Contacts (CalDAV)' and try again." | +| Empty state | N/A — every step has explicit content | | Error state (network/unexpected) | "Something went wrong. Please try again." (generic fallback, shown in Surface 5 failure style) | -| Destructive actions | None — wizard has no destructive actions. The "Complete Setup" action is irreversible in effect but not destructive; no confirmation dialog required. | +| Destructive actions | None — wizard has no destructive actions. "Complete Setup" is irreversible in effect but not destructive; no confirmation dialog required. | --- @@ -426,7 +446,6 @@ contract requires: no AppNav, no BottomTabBar, no SetupBanner, no PermissionDeni - Heading hierarchy: `

` for page title, `

` for step heading - All inputs: explicit `