+```
+
+From src/components/wizard/BackendSelectionStep.tsx:
+```typescript
+// handleSubmit(onValidSubmit) called in two places:
+// 1. form onSubmit={handleSubmit(onValidSubmit)} — line 65
+// 2. void handleSubmit(onValidSubmit)() inside handleCardClick — line 55
+// ADD second arg: handleSubmit(onValidSubmit, onInvalidSubmit)
+// The form's onSubmit needs the error handler. handleCardClick does NOT need it
+// (card click validates remote name — if invalid, scrolls to remote-name field).
+
+type RemoteNameFormValues = z.infer
;
+// Error key will be "name" → document.getElementById("name") won't work
+// because TextFieldMD3 uses id="remote-name" (passed as id prop)
+// SOLUTION: use the form element to find the first [aria-invalid] or use
+// a fixed ID since there's only one field. Simplest: getElementById('remote-name')
+// since the only validatable field in this step is the remote name input.
+```
+
+From src/components/wizard/RemoteConfigStep.tsx:
+```typescript
+// handleSubmit(onNext) — line 63
+// ADD second arg: handleSubmit(onNext, onInvalidSubmit)
+// FieldRenderer passes field.key as id to TextFieldMD3 and select
+// So document.getElementById(fieldKey) will find the correct element.
+```
+
+
+
+
+
+
+ Task 1: Step transition animation with reduced-motion guard
+ src/index.css, src/App.tsx
+
+POLISH-03: Add a subtle fade/slide-up animation on wizard step transitions.
+
+1. **src/index.css** — Add two things:
+
+ a. Register the animation in the existing `@theme` block. Add this line inside `@theme { ... }` after the last `--color-*` token:
+ ```css
+ --animate-step-in: step-in 200ms ease-out both;
+ ```
+ This registers `animate-step-in` as a Tailwind utility class.
+
+ b. Add the keyframes and reduced-motion guard AFTER the closing `}` of the `@theme` block (at the end of the file):
+ ```css
+ @keyframes step-in {
+ from {
+ opacity: 0;
+ transform: translateY(8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ .animate-step-in {
+ animation: none !important;
+ }
+ }
+ ```
+
+2. **src/App.tsx** — In the WizardShell component, change the step content wrapper div:
+ From: ``
+ To: `
`
+
+ The `key={state.currentStep}` forces React to unmount/remount the div when the step changes, which triggers the CSS animation from its initial state. The animation also plays on first render — this is acceptable per research (imperceptible during page load).
+
+Do NOT add any JavaScript media query listener for reduced-motion. The CSS `@media (prefers-reduced-motion: reduce)` guard handles it declaratively, consistent with the existing pattern at line 45 of index.css.
+
+
+ npx vitest run src/App.test.tsx
+
+
index.css contains @keyframes step-in with 200ms ease-out, @theme contains --animate-step-in, @media prefers-reduced-motion guard exists. App.tsx step wrapper has key={state.currentStep} and animate-step-in class. All App tests pass.
+
+
+
+ Task 2: Auto-scroll to first error on validation failure
+ src/components/wizard/BackendSelectionStep.tsx, src/components/wizard/BackendSelectionStep.test.tsx, src/components/wizard/RemoteConfigStep.tsx, src/components/wizard/RemoteConfigStep.test.tsx
+
+ - Test: When BackendSelectionStep form submits with empty remote name, scrollIntoView is called on the remote-name input element
+ - Test: When RemoteConfigStep form submits with missing required fields, scrollIntoView is called on the first errored field's element
+
+
+POLISH-04: Auto-scroll to the first errored field when form validation fails.
+
+**Important prerequisite in both test files:** Add `Element.prototype.scrollIntoView = vi.fn();` in a `beforeEach` block (jsdom does not implement scrollIntoView — it will throw without this mock).
+
+1. **BackendSelectionStep.tsx** — Add an `onInvalidSubmit` error handler:
+ ```tsx
+ function onInvalidSubmit() {
+ // Only one validatable field in this step: remote-name
+ document.getElementById('remote-name')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ }
+ ```
+ Update the form's onSubmit: `handleSubmit(onValidSubmit, onInvalidSubmit)`
+ Also update the handleCardClick call: `void handleSubmit(onValidSubmit, onInvalidSubmit)()`
+
+2. **BackendSelectionStep.test.tsx** — Add test:
+ - Setup: `beforeEach(() => { Element.prototype.scrollIntoView = vi.fn(); });`
+ - Test name: "scrolls to remote-name field when submitted with empty name"
+ - Action: render component, clear the remote name input (if it has a default), click "Next" button (type=submit)
+ - Assert: `expect(Element.prototype.scrollIntoView).toHaveBeenCalled()`
+
+3. **RemoteConfigStep.tsx** — Add an `onInvalidSubmit` error handler:
+ ```tsx
+ import type { FieldErrors } from 'react-hook-form';
+
+ function onInvalidSubmit(errors: FieldErrors) {
+ const firstKey = Object.keys(errors)[0];
+ if (firstKey) {
+ document.getElementById(firstKey)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ }
+ }
+ ```
+ Note: `FieldErrors` is already imported in RemoteConfigStep.tsx (as `FieldError` — add `FieldErrors` to the import).
+ Update the form's onSubmit: `handleSubmit(onNext, onInvalidSubmit)`
+
+4. **RemoteConfigStep.test.tsx** — Add test:
+ - Setup: `beforeEach(() => { Element.prototype.scrollIntoView = vi.fn(); });`
+ - Test name: "scrolls to first errored field when submitted with missing required fields"
+ - Action: render component with a backend that has required fields (e.g., azureblob), submit the form without filling required fields
+ - Assert: `expect(Element.prototype.scrollIntoView).toHaveBeenCalled()`
+
+The `FieldErrors` type import: RemoteConfigStep already imports `FieldError` from react-hook-form. Change to `import type { FieldError, FieldErrors } from 'react-hook-form';` (or just use the generic `Record` type on the errors param if FieldErrors causes issues).
+
+
+ npx vitest run src/components/wizard/BackendSelectionStep.test.tsx src/components/wizard/RemoteConfigStep.test.tsx
+
+ BackendSelectionStep calls scrollIntoView on remote-name input when form validation fails. RemoteConfigStep calls scrollIntoView on the first errored field when form validation fails. Both behaviors verified by unit tests with mocked scrollIntoView. All tests pass.
+
+
+
+
+
+- `npx vitest run` — all tests green (full suite)
+- Grep for `@keyframes step-in` in index.css — present
+- Grep for `animate-step-in` in App.tsx — present
+- Grep for `prefers-reduced-motion` in index.css — present (2 occurrences: existing theme transition + new animation guard)
+- Grep for `scrollIntoView` in BackendSelectionStep.tsx and RemoteConfigStep.tsx — present in both
+- Grep for `scrollIntoView` in test files — mock setup present in both test files
+
+
+
+- Step content wrapper in App.tsx has `key={state.currentStep}` and `animate-step-in` class
+- index.css defines `@keyframes step-in` with opacity 0->1 and translateY 8px->0
+- index.css has `@media (prefers-reduced-motion: reduce)` guard that disables animation
+- `@theme` block contains `--animate-step-in` token
+- BackendSelectionStep scrolls to remote-name input on validation failure
+- RemoteConfigStep scrolls to first errored field on validation failure
+- Both scroll behaviors verified by passing unit tests
+- Full test suite passes with no regressions
+
+
+