Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
11 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 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 11-polish-responsiveness | 02 | execute | 2 |
|
|
true |
|
|
Purpose: POLISH-03 creates visual continuity between wizard steps with a subtle fade/slide animation that respects accessibility preferences. POLISH-04 helps users find validation errors by auto-scrolling to the first errored field when form submission fails. Output: CSS animation keyframes in index.css, animated step wrapper in App.tsx, scrollIntoView handlers in BackendSelectionStep and RemoteConfigStep.
<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/ROADMAP.md @.planning/STATE.md @.planning/phases/11-polish-responsiveness/11-RESEARCH.md @.planning/phases/11-polish-responsiveness/11-01-SUMMARY.mdFrom src/index.css (after @theme block, line 53-69):
@theme {
--color-primary: var(--r2b-primary);
/* ... more color tokens ... */
--color-on-warning: var(--r2b-on-warning);
/* ADD: --animate-step-in: step-in 200ms ease-out both; */
}
From src/App.tsx (WizardShell step content wrapper, line 65):
<div className="mt-8">
{CurrentStep}
</div>
// CHANGE TO: <div key={state.currentStep} className="mt-8 animate-step-in">
From src/components/wizard/BackendSelectionStep.tsx:
// 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<typeof remoteNameSchema>;
// 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:
// 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.
-
src/index.css — Add two things:
a. Register the animation in the existing
@themeblock. Add this line inside@theme { ... }after the last--color-*token:--animate-step-in: step-in 200ms ease-out both;This registers
animate-step-inas a Tailwind utility class.b. Add the keyframes and reduced-motion guard AFTER the closing
}of the@themeblock (at the end of the file):@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; } } -
src/App.tsx — In the WizardShell component, change the step content wrapper div: From:
<div className="mt-8">To:<div key={state.currentStep} className="mt-8 animate-step-in">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.
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).
-
BackendSelectionStep.tsx — Add an
onInvalidSubmiterror handler: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)() -
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()
- Setup:
-
RemoteConfigStep.tsx — Add an
onInvalidSubmiterror handler: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:
FieldErrorsis already imported in RemoteConfigStep.tsx (asFieldError— addFieldErrorsto the import). Update the form's onSubmit:handleSubmit(onNext, onInvalidSubmit) -
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()
- Setup:
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<string, any> 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.
<success_criteria>
- Step content wrapper in App.tsx has
key={state.currentStep}andanimate-step-inclass - index.css defines
@keyframes step-inwith opacity 0->1 and translateY 8px->0 - index.css has
@media (prefers-reduced-motion: reduce)guard that disables animation @themeblock contains--animate-step-intoken- 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 </success_criteria>