Files
Ready2Blob/.planning/phases/11-polish-responsiveness/11-02-PLAN.md
T
2026-04-01 13:14:51 +02:00

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
11-01
src/index.css
src/App.tsx
src/components/wizard/BackendSelectionStep.tsx
src/components/wizard/RemoteConfigStep.tsx
true
POLISH-03
POLISH-04
truths artifacts key_links
Changing wizard steps plays a subtle fade/slide-up animation on the new step content
Users with prefers-reduced-motion enabled see no animation on step change
When form validation fails, the view auto-scrolls to the first errored field
scrollIntoView uses smooth behavior and centers the errored field
path provides contains
src/index.css step-in keyframe animation and reduced-motion guard @keyframes step-in
path provides contains
src/App.tsx Step content wrapper with key and animation class animate-step-in
path provides contains
src/components/wizard/BackendSelectionStep.tsx Auto-scroll to first error on validation failure scrollIntoView
path provides contains
src/components/wizard/RemoteConfigStep.tsx Auto-scroll to first error on validation failure scrollIntoView
from to via pattern
src/index.css src/App.tsx animate-step-in Tailwind utility from @theme --animate-step-in animate-step-in
from to via pattern
src/components/wizard/BackendSelectionStep.tsx DOM element document.getElementById(firstErrorKey)?.scrollIntoView scrollIntoView
Add step transition animations and auto-scroll-to-error behavior to the wizard.

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.md

From 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.
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:

    --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):

    @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: <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.

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:

    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:

    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<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.

- `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

<success_criteria>

  • 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 </success_criteria>
After completion, create `.planning/phases/11-polish-responsiveness/11-02-SUMMARY.md`