docs(05-03): complete act()-warning elimination plan
- SUMMARY.md: userEvent v14 migration + vi.useFakeTimers() for ReviewStep - STATE.md: advanced to completed 05-03, added patterns as decisions - ROADMAP.md: phase 5 now 4/4 plans complete (Complete status) - REQUIREMENTS.md: TECH-05 marked complete
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
---
|
||||
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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.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
|
||||
|
||||
<interfaces>
|
||||
<!-- Key types and contracts the executor needs. -->
|
||||
|
||||
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<string, string> }
|
||||
| { type: 'SET_DEPLOYMENT'; payload: Partial<WizardState['deployment']> }
|
||||
| { 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<WizardAction> }
|
||||
```
|
||||
|
||||
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 },
|
||||
});
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Implement BackendSelectionStep and make its tests GREEN</name>
|
||||
<files>src/components/wizard/BackendSelectionStep.tsx</files>
|
||||
<behavior>
|
||||
- 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
|
||||
</behavior>
|
||||
<action>
|
||||
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:
|
||||
```
|
||||
<div> (outermost wrapper)
|
||||
<h2>Step 1: Select Backend</h2>
|
||||
<form onSubmit={handleSubmit(onValidSubmit)}>
|
||||
Remote name section (at top — WIZD-04):
|
||||
<label> + <input id="remote-name" ...register('name') />
|
||||
{errors.name && <p role="alert">{errors.name.message}</p>}
|
||||
Backend cards section (below name — WIZD-01):
|
||||
<div role="list" or data-testid="backend-cards">
|
||||
<BackendCard name="Azure Blob Storage" onClick={...} />
|
||||
<BackendCard name="Amazon S3" onClick={...} />
|
||||
<BackendCard name="S3-Compatible" onClick={...} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
```
|
||||
|
||||
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(<WizardProvider><BackendSelectionStep /></WizardProvider>)`
|
||||
- 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)`
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npx vitest run src/components/wizard/BackendSelectionStep.test.tsx --reporter=verbose 2>&1</automated>
|
||||
</verify>
|
||||
<done>
|
||||
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.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
```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.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/03-wizard-ui/03-03-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user