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,299 @@
|
||||
---
|
||||
phase: 03-wizard-ui
|
||||
plan: "04"
|
||||
type: execute
|
||||
wave: 3
|
||||
depends_on:
|
||||
- "03-01"
|
||||
- "03-02"
|
||||
files_modified:
|
||||
- src/components/wizard/RemoteConfigStep.tsx
|
||||
autonomous: true
|
||||
requirements:
|
||||
- BACK-01
|
||||
- BACK-02
|
||||
- BACK-03
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "RemoteConfigStep renders the correct fields for azureblob backend using BACKEND_REGISTRY iteration"
|
||||
- "RemoteConfigStep renders the correct fields for s3 backend using BACKEND_REGISTRY iteration"
|
||||
- "RemoteConfigStep renders the correct fields for s3-compatible backend including an endpoint field"
|
||||
- "AzureAuthToggle appears for azureblob backend — SAS URL shown by default, toggle switches to Access Key"
|
||||
- "Toggling Azure auth method does not clear the hidden field value (both registered)"
|
||||
- "Errors only show after first Next attempt, then update live (mode: onSubmit, reValidateMode: onChange)"
|
||||
- "RemoteConfigStep component is keyed on backendType to force remount on backend change"
|
||||
artifacts:
|
||||
- path: "src/components/wizard/RemoteConfigStep.tsx"
|
||||
provides: "Step 1 — registry-driven backend configuration form"
|
||||
exports: ["RemoteConfigStep"]
|
||||
key_links:
|
||||
- from: "src/components/wizard/RemoteConfigStep.tsx"
|
||||
to: "src/schemas/registry.ts"
|
||||
via: "BACKEND_REGISTRY[backendType] drives field rendering loop"
|
||||
pattern: "BACKEND_REGISTRY"
|
||||
- from: "src/components/wizard/RemoteConfigStep.tsx"
|
||||
to: "src/schemas/index.ts"
|
||||
via: "BACKEND_SCHEMAS[backendType] provides Zod resolver"
|
||||
pattern: "BACKEND_SCHEMAS"
|
||||
- from: "src/components/wizard/RemoteConfigStep.tsx"
|
||||
to: "src/store/context.tsx"
|
||||
via: "dispatches SET_REMOTE_PARAMS on Next, SET_STEP(2)"
|
||||
pattern: "dispatch.*SET_REMOTE_PARAMS|SET_STEP"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Implement RemoteConfigStep — the second wizard step with a registry-driven form for configuring the selected backend.
|
||||
|
||||
Purpose: Satisfies BACK-01 (Azure Blob config with auth toggle), BACK-02 (S3 config), BACK-03 (S3-compatible with endpoint).
|
||||
Output: src/components/wizard/RemoteConfigStep.tsx, RemoteConfigStep 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/schemas/registry.ts:
|
||||
```typescript
|
||||
export type BackendType = 'azureblob' | 's3' | 's3-compatible';
|
||||
export const BACKEND_REGISTRY: Record<BackendType, FieldDef[]>
|
||||
// azureblob fields: account (text, required), key (password, optional), sas_url (password, optional)
|
||||
// s3 fields: provider (select/hidden), access_key_id (text), secret_access_key (password), region (text)
|
||||
// s3-compatible fields: provider (select/hidden), access_key_id, secret_access_key, endpoint (text, required), region (text, optional)
|
||||
```
|
||||
|
||||
From src/schemas/index.ts:
|
||||
```typescript
|
||||
export const BACKEND_SCHEMAS = {
|
||||
azureblob: ZodObject,
|
||||
s3: ZodObject,
|
||||
's3-compatible': ZodObject,
|
||||
} as const;
|
||||
// All schemas built from BACKEND_REGISTRY — key/sas_url are optional (z.string().optional())
|
||||
```
|
||||
|
||||
From src/store/types.ts:
|
||||
```typescript
|
||||
// On Next: dispatch SET_REMOTE_PARAMS with all form values
|
||||
// Then: dispatch SET_STEP(2)
|
||||
dispatch({ type: 'SET_REMOTE_PARAMS', payload: values });
|
||||
dispatch({ type: 'SET_STEP', payload: 2 });
|
||||
```
|
||||
|
||||
From src/components/ui/FieldRenderer.tsx (Plan 02):
|
||||
```typescript
|
||||
export function FieldRenderer(props: {
|
||||
field: FieldDef;
|
||||
register: UseFormRegister<any>;
|
||||
error?: FieldError;
|
||||
}): JSX.Element
|
||||
// Handles text, password, select, hidden (provider single-option)
|
||||
```
|
||||
|
||||
From src/components/wizard/AzureAuthToggle.tsx (Plan 02):
|
||||
```typescript
|
||||
export function AzureAuthToggle(props: {
|
||||
register: UseFormRegister<any>;
|
||||
errors: { key?: FieldError; sas_url?: FieldError };
|
||||
}): JSX.Element
|
||||
// Renders segmented SAS/Key toggle with both fields always registered
|
||||
```
|
||||
|
||||
react-hook-form registry-driven pattern (from RESEARCH.md):
|
||||
```tsx
|
||||
const { register, handleSubmit, formState: { errors } } = useForm({
|
||||
resolver: zodResolver(schema),
|
||||
mode: 'onSubmit',
|
||||
reValidateMode: 'onChange',
|
||||
defaultValues: state.remote.params,
|
||||
});
|
||||
const onNext = (values: Record<string, string>) => {
|
||||
dispatch({ type: 'SET_REMOTE_PARAMS', payload: values });
|
||||
dispatch({ type: 'SET_STEP', payload: 2 });
|
||||
};
|
||||
```
|
||||
|
||||
Remount on backend change (from RESEARCH.md Pitfall 1):
|
||||
```tsx
|
||||
// In App.tsx (or wherever RemoteConfigStep is rendered):
|
||||
<RemoteConfigStep key={state.remote.backendType} />
|
||||
// This forces full remount when backend changes — prevents stale defaultValues
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Implement RemoteConfigStep and make its tests GREEN</name>
|
||||
<files>src/components/wizard/RemoteConfigStep.tsx</files>
|
||||
<behavior>
|
||||
- BACK-01: When backendType is 'azureblob', renders the 'account' text input
|
||||
- BACK-01: When backendType is 'azureblob', renders AzureAuthToggle (not separate key/sas_url FieldRenderer calls)
|
||||
- BACK-01: Azure form submits with both sas_url and key values in params (even if one is empty string)
|
||||
- BACK-01: Toggling auth method in AzureAuthToggle does not remove the hidden field from form state
|
||||
- BACK-02: When backendType is 's3', renders access_key_id, secret_access_key, and region inputs
|
||||
- BACK-02: S3 provider field is hidden (auto-registered with value 'AWS') — no visible dropdown
|
||||
- BACK-03: When backendType is 's3-compatible', renders endpoint field in addition to access_key_id, secret_access_key
|
||||
- BACK-03: S3-compatible provider field is hidden (auto-registered with value 'Other')
|
||||
- All backends: Errors do not show before first Next attempt (mode: 'onSubmit')
|
||||
- All backends: Errors show after first failed Next attempt and update live (reValidateMode: 'onChange')
|
||||
- All backends: Clicking Next with valid data dispatches SET_REMOTE_PARAMS then SET_STEP(2)
|
||||
</behavior>
|
||||
<action>
|
||||
Create `src/components/wizard/RemoteConfigStep.tsx`.
|
||||
|
||||
The component reads `state.remote.backendType` and `state.remote.params` from `useWizard()`. It must NOT render if `backendType` is null (user somehow reached step 1 without selecting — guard with early return or redirect to step 0).
|
||||
|
||||
For the Azure backend, the field rendering is SPECIAL — do not loop `BACKEND_REGISTRY['azureblob']` naively:
|
||||
- Render `account` field via `FieldRenderer`
|
||||
- Render `AzureAuthToggle` for `key` / `sas_url` (handles both fields internally)
|
||||
- Do NOT pass key/sas_url through the generic FieldRenderer loop for azureblob
|
||||
|
||||
For S3 and S3-compatible, loop all `BACKEND_REGISTRY[backendType]` fields through `FieldRenderer`. The `provider` field with a single option is auto-hidden by `FieldRenderer` already.
|
||||
|
||||
Implementation structure:
|
||||
```tsx
|
||||
export function RemoteConfigStep() {
|
||||
const { state, dispatch } = useWizard();
|
||||
const backendType = state.remote.backendType;
|
||||
|
||||
// Guard — should never happen but prevents runtime errors
|
||||
if (!backendType) {
|
||||
dispatch({ type: 'SET_STEP', payload: 0 });
|
||||
return null;
|
||||
}
|
||||
|
||||
const schema = BACKEND_SCHEMAS[backendType];
|
||||
const { register, handleSubmit, formState: { errors } } = useForm({
|
||||
resolver: zodResolver(schema),
|
||||
mode: 'onSubmit',
|
||||
reValidateMode: 'onChange',
|
||||
defaultValues: state.remote.params,
|
||||
});
|
||||
|
||||
const onNext = (values: Record<string, string>) => {
|
||||
dispatch({ type: 'SET_REMOTE_PARAMS', payload: values });
|
||||
dispatch({ type: 'SET_STEP', payload: 2 });
|
||||
};
|
||||
|
||||
const backendLabel = {
|
||||
azureblob: 'Azure Blob Storage',
|
||||
s3: 'Amazon S3',
|
||||
's3-compatible': 'S3-Compatible Storage',
|
||||
}[backendType];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Step 2: Configure {backendLabel}</h2>
|
||||
<form onSubmit={handleSubmit(onNext)}>
|
||||
{backendType === 'azureblob' ? (
|
||||
<>
|
||||
{/* Account field via FieldRenderer */}
|
||||
<FieldRenderer
|
||||
field={BACKEND_REGISTRY.azureblob.find(f => f.key === 'account')!}
|
||||
register={register}
|
||||
error={errors.account}
|
||||
/>
|
||||
{/* Auth toggle handles key + sas_url — both always registered */}
|
||||
<AzureAuthToggle
|
||||
register={register}
|
||||
errors={{ key: errors.key, sas_url: errors.sas_url }}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
/* S3 and S3-compatible: full registry loop — FieldRenderer handles provider hiding */
|
||||
BACKEND_REGISTRY[backendType].map(field => (
|
||||
<FieldRenderer
|
||||
key={field.key}
|
||||
field={field}
|
||||
register={register}
|
||||
error={errors[field.key]}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
<div className="flex gap-3 mt-6">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => dispatch({ type: 'SET_STEP', payload: 0 })}
|
||||
className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Now update `src/components/wizard/RemoteConfigStep.test.tsx` to make all tests GREEN. Tests wrap component in `WizardProvider`. To set the backendType before rendering, create a helper that renders with a specific initial step/backend — dispatch actions in a test wrapper component, or initialize a custom context.
|
||||
|
||||
Practical test approach: Create a `TestWrapper` helper inside the test file that wraps with `WizardProvider` and dispatches the desired initial state before rendering `RemoteConfigStep`:
|
||||
```tsx
|
||||
function renderWithBackend(backendType: BackendType) {
|
||||
function Setup() {
|
||||
const { dispatch } = useWizard();
|
||||
useEffect(() => {
|
||||
dispatch({ type: 'SET_BACKEND_TYPE', payload: backendType });
|
||||
dispatch({ type: 'SET_STEP', payload: 1 });
|
||||
}, []);
|
||||
return <RemoteConfigStep key={backendType} />;
|
||||
}
|
||||
return render(<WizardProvider><Setup /></WizardProvider>);
|
||||
}
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npx vitest run src/components/wizard/RemoteConfigStep.test.tsx --reporter=verbose 2>&1</automated>
|
||||
</verify>
|
||||
<done>
|
||||
All RemoteConfigStep tests pass GREEN. BACK-01 (Azure form with toggle), BACK-02 (S3 fields), BACK-03 (S3-compatible with endpoint) all green. RemoteConfigStep.tsx exports the component correctly.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
```bash
|
||||
npx vitest run src/components/wizard/RemoteConfigStep.test.tsx --reporter=verbose 2>&1
|
||||
```
|
||||
All BACK-01, BACK-02, BACK-03 tests GREEN.
|
||||
|
||||
Full suite check:
|
||||
```bash
|
||||
npx vitest run 2>&1 | tail -10
|
||||
```
|
||||
RemoteConfigStep tests GREEN. BackendSelectionStep tests GREEN (from Plan 03). App and StepIndicator stubs still RED (expected — Plan 05).
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- RemoteConfigStep.tsx exists with named export
|
||||
- Azure Blob form: account field + AzureAuthToggle renders (both key and sas_url registered, only one visible)
|
||||
- S3 form: access_key_id, secret_access_key, region fields render; provider field is hidden
|
||||
- S3-compatible form: same as S3 plus endpoint field renders; provider field is hidden
|
||||
- Touch-then-live validation: no errors on initial render, errors after first failed submit
|
||||
- On valid submit: dispatches SET_REMOTE_PARAMS with all field values (including hidden auth field), then SET_STEP(2)
|
||||
- All BACK-01, BACK-02, BACK-03 tests GREEN
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/03-wizard-ui/03-04-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user