- 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
12 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 03-wizard-ui | 04 | execute | 3 |
|
|
true |
|
|
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.
<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>
@.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.mdFrom src/schemas/registry.ts:
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:
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:
// 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):
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):
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):
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):
// In App.tsx (or wherever RemoteConfigStep is rendered):
<RemoteConfigStep key={state.remote.backendType} />
// This forces full remount when backend changes — prevents stale defaultValues
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>);
}
```
Full suite check:
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).
<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>