--- phase: 09-md3-components plan: 01 type: execute wave: 1 depends_on: [] files_modified: - src/components/ui/TextFieldMD3.tsx - src/components/ui/TextFieldMD3.test.tsx - src/styles/md3-buttons.ts - src/components/ui/FieldRenderer.tsx - src/components/ui/FieldRenderer.test.tsx autonomous: true requirements: [COMP-01, COMP-02, DEBT-01] must_haves: truths: - "TextFieldMD3 renders an outlined input with a floating label that floats on focus" - "TextFieldMD3 floating label floats when the field has a value (not just on focus)" - "TextFieldMD3 preserves htmlFor/id pairing so getByLabelText queries work" - "MD3 button class constants exist for filled, outlined, and text variants" - "FieldRenderer text-branch tooltip button uses aria-label instead of sr-only span" artifacts: - path: "src/components/ui/TextFieldMD3.tsx" provides: "MD3 outlined text field with CSS floating label" exports: ["TextFieldMD3"] - path: "src/components/ui/TextFieldMD3.test.tsx" provides: "Unit tests for floating label behavior" min_lines: 30 - path: "src/styles/md3-buttons.ts" provides: "MD3 button class constants" exports: ["MD3_BTN_FILLED", "MD3_BTN_OUTLINED", "MD3_BTN_TEXT"] - path: "src/components/ui/FieldRenderer.test.tsx" provides: "Tests for DEBT-01 aria-label consistency" min_lines: 20 key_links: - from: "src/components/ui/TextFieldMD3.tsx" to: "react-hook-form" via: "registration prop spread onto input" pattern: "\\.\\.\\.(registration|register)" --- Create MD3 UI primitives: TextFieldMD3 component with CSS-only floating label, MD3 button class constants, and fix FieldRenderer DEBT-01 aria-label inconsistency. Purpose: Establish the reusable building blocks that Plan 03 will wire into all wizard steps. TextFieldMD3 is the most complex new component and needs its own test coverage before integration. Output: TextFieldMD3.tsx + tests, md3-buttons.ts constants, FieldRenderer aria-label fix + tests. @C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/09-md3-components/09-RESEARCH.md @src/index.css @src/components/ui/FieldRenderer.tsx @src/components/ui/PasswordField.tsx From src/components/ui/FieldRenderer.tsx: ```typescript interface FieldRendererProps { field: FieldDef; register: UseFormRegister; error?: FieldError; } ``` From src/components/ui/PasswordField.tsx: ```typescript interface PasswordFieldProps { id: string; label: string; error?: FieldError; registration: UseFormRegisterReturn; placeholder?: string; helpText?: string; tooltipText?: string; } ``` From src/schemas/registry.ts (FieldDef shape): ```typescript interface FieldDef { key: string; label: string; required?: boolean; inputType?: 'text' | 'password' | 'select'; options?: { value: string; label: string }[]; placeholder?: string; helpText?: string; tooltipText?: string; } ``` Task 1: Create TextFieldMD3 component with floating label + MD3 button constants src/components/ui/TextFieldMD3.tsx, src/components/ui/TextFieldMD3.test.tsx, src/styles/md3-buttons.ts - Test 1: TextFieldMD3 renders an input with the given id and a label with matching htmlFor - Test 2: screen.getByLabelText(label) finds the input (htmlFor/id pairing works) - Test 3: Input receives registration props (can be queried after typing) - Test 4: Error message renders with role="alert" when error prop is passed - Test 5: Help text renders when helpText prop is passed and no error - Test 6: Required asterisk renders when required=true - Test 7: Suffix slot renders (for PasswordField show/hide button) - Test 8: Input has placeholder=" " for CSS floating label trick Create `src/components/ui/TextFieldMD3.tsx` implementing the CSS-only floating label pattern from the research doc (Pattern 1). Key details: - Interface: `{ id, label, error?, registration, type?, helpText?, required?, suffix? }` where registration is `UseFormRegisterReturn` - Input gets `placeholder=" "` for `:placeholder-shown` CSS detection - Label is absolutely positioned, uses Tailwind `peer` utilities to float on focus and when input has value - Use `peer-[:not(:placeholder-shown)]:` arbitrary variant for has-value state (Tailwind v4 supports this) - All colors use semantic tokens: `border-outline`, `focus:border-primary`, `text-on-surface-container/60`, `text-error` - Error state: `border-error` + error message with `role="alert"` - Suffix slot: absolutely positioned right side (for password show/hide toggle) - Spread `{...registration}` onto the input element Create `src/styles/md3-buttons.ts` exporting three constants: - `MD3_BTN_FILLED`: `px-6 py-2.5 rounded-full bg-primary text-on-primary text-sm font-medium hover:opacity-90 active:opacity-80 transition-opacity disabled:opacity-40 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50` - `MD3_BTN_OUTLINED`: `px-6 py-2.5 rounded-full border border-outline text-on-surface text-sm font-medium hover:bg-primary/8 active:bg-primary/12 transition-colors disabled:opacity-40 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50` - `MD3_BTN_TEXT`: `px-4 py-2.5 rounded-full text-primary text-sm font-medium hover:bg-primary/8 active:bg-primary/12 transition-colors disabled:opacity-40 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50` Write tests FIRST (RED), then implement (GREEN). npx vitest run src/components/ui/TextFieldMD3.test.tsx --reporter=dot TextFieldMD3 component renders floating label with proper htmlFor/id pairing, getByLabelText works, error/helpText/suffix/required all render correctly. MD3 button constants exported. All tests pass. Task 2: Fix DEBT-01 aria-label inconsistency in FieldRenderer + add tests src/components/ui/FieldRenderer.tsx, src/components/ui/FieldRenderer.test.tsx - Test 1: Text-branch tooltip button has aria-label="More info about {field.label}" (not sr-only span) - Test 2: Select-branch tooltip button has aria-label="More info about {field.label}" - Test 3: Both branches' tooltip buttons are findable via getByRole('button', { name: /more info about/i }) Create `src/components/ui/FieldRenderer.test.tsx` with tests for DEBT-01 aria-label consistency. Tests render FieldRenderer with a mock field that has tooltipText, and verify the tooltip button uses `aria-label` attribute (not sr-only span) for both text-branch and select-branch. Then fix FieldRenderer.tsx text-branch (lines 91-99): Replace the `...` pattern with a direct `aria-label={...}` on the button and plain `i` icon content, matching the select-branch pattern (lines 49-54). The fix is exactly: ```tsx // Before (text branch, ~line 91): // After: ``` Also apply the same fix to PasswordField.tsx (lines 27-31) which has the identical sr-only pattern. Run full test suite after to confirm no regressions. npx vitest run src/components/ui/FieldRenderer.test.tsx --reporter=dot && npx vitest run --reporter=dot Both text-branch and select-branch tooltip buttons use aria-label attribute consistently. PasswordField tooltip also fixed. All 166+ existing tests still pass. DEBT-01 resolved. - `npx vitest run --reporter=dot` — all existing tests pass (no regressions) - `npx vitest run src/components/ui/TextFieldMD3.test.tsx` — new component tests pass - `npx vitest run src/components/ui/FieldRenderer.test.tsx` — DEBT-01 tests pass - `src/styles/md3-buttons.ts` exports MD3_BTN_FILLED, MD3_BTN_OUTLINED, MD3_BTN_TEXT - TextFieldMD3 component exists with floating label, proper htmlFor/id, and all tests passing - MD3 button constants exported from src/styles/md3-buttons.ts - FieldRenderer and PasswordField tooltip buttons use consistent aria-label (DEBT-01 complete) - Zero test regressions across the full suite After completion, create `.planning/phases/09-md3-components/09-01-SUMMARY.md`