--- phase: 03-wizard-ui plan: "03" type: execute wave: 3 depends_on: - "03-01" - "03-02" files_modified: - src/components/wizard/BackendSelectionStep.tsx autonomous: true requirements: - WIZD-01 - WIZD-04 must_haves: truths: - "BackendSelectionStep renders Azure Blob, Amazon S3, and S3-Compatible backend cards in that order" - "Clicking a backend card dispatches SET_BACKEND_TYPE + SET_REMOTE_PARAMS({}) + SET_STEP(1) — no Next button needed" - "Remote name field is rendered at the top of the step before the backend cards" - "Remote name field validates alphanumeric/dash/underscore only — inline error shown only after first Next attempt" - "Entering an invalid remote name and clicking a card does not advance (validation fires first)" artifacts: - path: "src/components/wizard/BackendSelectionStep.tsx" provides: "Step 0 — remote name + backend card grid" exports: ["BackendSelectionStep"] key_links: - from: "src/components/wizard/BackendSelectionStep.tsx" to: "src/store/context.tsx" via: "useWizard() for state and dispatch" pattern: "useWizard" - from: "src/components/wizard/BackendSelectionStep.tsx" to: "src/components/ui/BackendCard.tsx" via: "renders three BackendCard instances" pattern: "BackendCard" - from: "src/components/wizard/BackendSelectionStep.tsx" to: "src/store/types.ts" via: "dispatches SET_BACKEND_TYPE, SET_REMOTE_PARAMS, SET_STEP" pattern: "dispatch.*SET_BACKEND_TYPE|SET_REMOTE_PARAMS|SET_STEP" --- Implement BackendSelectionStep — the first wizard step where the user sets a remote name and selects a storage backend. Purpose: Satisfies WIZD-01 (popularity-sorted card grid) and WIZD-04 (remote name validation). Output: src/components/wizard/BackendSelectionStep.tsx, BackendSelectionStep tests GREEN. @C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/phases/03-wizard-ui/03-CONTEXT.md @.planning/phases/03-wizard-ui/03-RESEARCH.md @.planning/phases/03-wizard-ui/03-01-SUMMARY.md @.planning/phases/03-wizard-ui/03-02-SUMMARY.md From src/store/types.ts: ```typescript export type BackendType = 'azureblob' | 's3' | 's3-compatible'; export type WizardAction = | { type: 'SET_STEP'; payload: number } | { type: 'SET_BACKEND_TYPE'; payload: BackendType } | { type: 'SET_REMOTE_NAME'; payload: string } | { type: 'SET_REMOTE_PARAMS'; payload: Record } | { type: 'SET_DEPLOYMENT'; payload: Partial } | { type: 'RESET' }; // INITIAL_STATE.remote.name = '' // INITIAL_STATE.remote.backendType = null ``` From src/store/context.tsx: ```typescript export function useWizard(): { state: WizardState; dispatch: React.Dispatch } ``` From src/components/ui/BackendCard.tsx (created in Plan 02): ```typescript export function BackendCard(props: { name: string; description: string; selected?: boolean; onClick: () => void; }): JSX.Element ``` Remote name validation regex (from RESEARCH.md): ```typescript const remoteNameSchema = z.object({ name: z.string() .min(1, 'Remote name is required') .regex(/^[a-zA-Z0-9_-]+$/, 'Only letters, numbers, dashes, and underscores allowed'), }); ``` react-hook-form mode pattern (LOCKED — from RESEARCH.md): ```typescript const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(remoteNameSchema), mode: 'onSubmit', // no errors on initial fill reValidateMode: 'onChange', // live errors once submitted once defaultValues: { name: state.remote.name }, }); ``` Task 1: Implement BackendSelectionStep and make its tests GREEN src/components/wizard/BackendSelectionStep.tsx - WIZD-01: Renders Azure Blob, Amazon S3, S3-Compatible cards in that exact DOM order - WIZD-01: Azure Blob card appears before S3 card in the DOM (popularity-sorted) - WIZD-01: Clicking a card dispatches SET_BACKEND_TYPE with the correct BackendType value - WIZD-01: Clicking a card also dispatches SET_REMOTE_PARAMS({}) to clear stale params (CRITICAL — reducer does not auto-clear) - WIZD-01: Clicking a card dispatches SET_STEP(1) to advance after dispatching backend type - WIZD-04: Remote name input is rendered before the backend cards in DOM order - WIZD-04: No error message shown before the user has attempted to submit (mode: 'onSubmit') - WIZD-04: After submit attempt with empty name, "Remote name is required" error shows below the input - WIZD-04: After submit attempt with "my remote!", "Only letters, numbers, dashes, and underscores allowed" error shows - WIZD-04: "my-remote_01" passes validation (alphanumeric, dashes, underscores allowed) - WIZD-04: Clicking a card validates the name first — if invalid, error shows but navigation does NOT proceed Create `src/components/wizard/BackendSelectionStep.tsx`. The component uses react-hook-form for the remote name field with `mode: 'onSubmit'` and `reValidateMode: 'onChange'`. The backend cards trigger `handleSubmit` internally — clicking a card submits the form, and if validation passes, it dispatches the three actions (SET_REMOTE_NAME → SET_BACKEND_TYPE → SET_REMOTE_PARAMS → SET_STEP). Backend list (hardcoded display order — WIZD-01 popularity sort): 1. azureblob → "Azure Blob Storage" / "Microsoft Azure cloud storage" 2. s3 → "Amazon S3" / "AWS Simple Storage Service" 3. s3-compatible → "S3-Compatible" / "Wasabi, MinIO, Cloudflare R2, and others" Implementation approach: - `useForm` with zodResolver for name validation; `mode: 'onSubmit'`, `reValidateMode: 'onChange'` - `defaultValues: { name: state.remote.name }` to restore previously entered name on back-nav - Each BackendCard's `onClick` calls a handler that sets a `pendingBackend` ref, then calls `handleSubmit(onValidSubmit)()` - `onValidSubmit` receives validated values, dispatches: `SET_REMOTE_NAME` (with validated name), `SET_BACKEND_TYPE` (with pendingBackend), `SET_REMOTE_PARAMS({})` (clear stale params), `SET_STEP(1)` - CRITICAL dispatch order: SET_BACKEND_TYPE before SET_REMOTE_PARAMS({}) — SET_REMOTE_PARAMS clears old backend's params, SET_BACKEND_TYPE sets the new type. The reducer handles each action independently so order matters for clarity, not correctness. - ANTI-PATTERN WARNING: Do NOT dispatch RESET — that wipes deployment options. Use SET_REMOTE_PARAMS({}) only. Render structure: ```
(outermost wrapper)

Step 1: Select Backend

Remote name section (at top — WIZD-04):
``` Now update the test stub at `src/components/wizard/BackendSelectionStep.test.tsx` to make all tests GREEN. Tests must wrap the component in `WizardProvider` and use `@testing-library/react` render + screen + fireEvent. Import `WizardProvider` from `../../store/context`. Test patterns: - Render: `render()` - WIZD-01 card order: `const cards = screen.getAllByRole('button', { name: /Azure|Amazon|S3-Compatible/ })` — check order by textContent - WIZD-01 click dispatches: Mock or spy on dispatch is complex with context. Instead verify navigation by checking that after clicking a card with a valid name pre-set (set state.remote.name via initial state), the appropriate DOM change happens. Alternatively, use a custom WizardProvider wrapper with observable dispatch for testing. - WIZD-04 errors: `fireEvent.click(getByRole('button', { name: /Azure/i }))` with empty name → `await screen.findByRole('alert')` or `screen.getByText(/required/i)`
npx vitest run src/components/wizard/BackendSelectionStep.test.tsx --reporter=verbose 2>&1 All BackendSelectionStep tests pass GREEN. WIZD-01 and WIZD-04 test cases are all green. BackendSelectionStep.tsx exports the component. The component correctly dispatches the three required actions on card click with valid name.
```bash npx vitest run src/components/wizard/BackendSelectionStep.test.tsx --reporter=verbose 2>&1 ``` All WIZD-01 and WIZD-04 tests GREEN. Then run full suite: ```bash npx vitest run 2>&1 | tail -10 ``` Other test stubs remain RED (expected). BackendSelectionStep tests all GREEN. - BackendSelectionStep.tsx exists with named export - All WIZD-01 tests green: three cards render, Azure first, click dispatches correct actions - All WIZD-04 tests green: name field at top, no errors before submit, inline errors after failed submit, valid names pass - Clicking a card with invalid name shows validation error and does NOT navigate to step 1 - Component uses `mode: 'onSubmit', reValidateMode: 'onChange'` (locked UX decision) - Dispatches SET_REMOTE_PARAMS({}) on backend selection (clears stale params — critical for correctness) - Does NOT dispatch RESET (deployment options must be preserved) After completion, create `.planning/phases/03-wizard-ui/03-03-SUMMARY.md`