docs(09-md3-components): create phase plan

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-01 09:14:15 +02:00
co-authored by Claude Opus 4.6
parent 80a26e395d
commit a6c2ec7292
4 changed files with 640 additions and 3 deletions
+7 -3
View File
@@ -66,7 +66,11 @@ Plans:
3. Backend selection cards and output blocks display MD3 elevation with tonal surface tint and consistent shape tokens
4. Step indicator shows numbered circles connected by lines, with checkmarks on completed steps, highlight on current, and muted future steps
5. FieldRenderer produces identical `aria-label` attributes for both text-branch and select-branch inputs
**Plans**: TBD
**Plans:** 3 plans
Plans:
- [ ] 09-01-PLAN.md — TextFieldMD3 component, MD3 button constants, DEBT-01 aria-label fix
- [ ] 09-02-PLAN.md — StepIndicator rebuild with MD3 circles and connectors
- [ ] 09-03-PLAN.md — Integration: wire TextFieldMD3 + button styles + elevation into all wizard steps
### Phase 10: Content & Clarity
**Goal**: A first-time visitor understands what Ready2Blob does and what each wizard step expects without external documentation
@@ -101,7 +105,7 @@ Plans:
| 5. Tech Debt | v1.1 | 4/4 | Complete | 2026-03-30 |
| 6. New Backends | v1.1 | 4/4 | Complete | 2026-03-31 |
| 7. Validation & UX Polish | v1.1 | 3/3 | Complete | 2026-03-31 |
| 8. Theme Foundation | 2/2 | Complete | 2026-04-01 | - |
| 9. MD3 Components | v1.2 | 0/? | Not started | - |
| 8. Theme Foundation | v1.2 | 2/2 | Complete | 2026-04-01 |
| 9. MD3 Components | v1.2 | 0/3 | Not started | - |
| 10. Content & Clarity | v1.2 | 0/? | Not started | - |
| 11. Polish & Responsiveness | v1.2 | 0/? | Not started | - |
@@ -0,0 +1,204 @@
---
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)"
---
<objective>
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.
</objective>
<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>
<context>
@.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
<interfaces>
<!-- FieldRenderer and PasswordField both use react-hook-form registration -->
From src/components/ui/FieldRenderer.tsx:
```typescript
interface FieldRendererProps {
field: FieldDef;
register: UseFormRegister<any>;
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;
}
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Create TextFieldMD3 component with floating label + MD3 button constants</name>
<files>src/components/ui/TextFieldMD3.tsx, src/components/ui/TextFieldMD3.test.tsx, src/styles/md3-buttons.ts</files>
<behavior>
- 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
</behavior>
<action>
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).
</action>
<verify>
<automated>npx vitest run src/components/ui/TextFieldMD3.test.tsx --reporter=dot</automated>
</verify>
<done>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.</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Fix DEBT-01 aria-label inconsistency in FieldRenderer + add tests</name>
<files>src/components/ui/FieldRenderer.tsx, src/components/ui/FieldRenderer.test.tsx</files>
<behavior>
- 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 })
</behavior>
<action>
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 `<span className="sr-only">...</span><span aria-hidden="true">...</span>` 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):
<button type="button" onClick={...} className="...">
<span className="sr-only">More info about {field.label}</span>
<span aria-hidden="true">ⓘ</span>
</button>
// After:
<button
type="button"
onClick={...}
aria-label={`More info about ${field.label}`}
className="..."
>
</button>
```
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.
</action>
<verify>
<automated>npx vitest run src/components/ui/FieldRenderer.test.tsx --reporter=dot && npx vitest run --reporter=dot</automated>
</verify>
<done>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.</done>
</task>
</tasks>
<verification>
- `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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/09-md3-components/09-01-SUMMARY.md`
</output>
@@ -0,0 +1,157 @@
---
phase: 09-md3-components
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
- src/components/wizard/StepIndicator.tsx
- src/components/wizard/StepIndicator.test.tsx
autonomous: true
requirements: [COMP-04]
must_haves:
truths:
- "Step indicator shows numbered circles for each wizard step"
- "Completed steps display a checkmark and are clickable buttons"
- "Current step is visually highlighted with primary color"
- "Future steps appear muted"
- "Connector lines link steps — filled for completed, muted for future"
- "Clicking step 0 dispatches SET_REMOTE_PARAMS({}) then SET_STEP(0)"
artifacts:
- path: "src/components/wizard/StepIndicator.tsx"
provides: "Rebuilt MD3 step indicator with circles and connectors"
exports: ["StepIndicator"]
min_lines: 40
key_links:
- from: "src/components/wizard/StepIndicator.tsx"
to: "src/store/context.tsx"
via: "useWizard() hook for state.currentStep and dispatch"
pattern: "useWizard"
---
<objective>
Rebuild StepIndicator from text breadcrumbs to MD3 visual step indicator with numbered circles, connector lines, checkmarks on completed steps, and proper semantic tokens.
Purpose: Replace the current text-based breadcrumb (using inline styles and hardcoded colors) with an MD3-compliant visual stepper that is dark-mode aware and accessible.
Output: Rebuilt StepIndicator.tsx + updated tests.
</objective>
<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>
<context>
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/09-md3-components/09-RESEARCH.md
@src/components/wizard/StepIndicator.tsx
@src/components/wizard/StepIndicator.test.tsx
<interfaces>
From src/store/context.tsx:
```typescript
function useWizard(): { state: WizardState; dispatch: Dispatch<WizardAction> }
```
From src/store/types.ts:
```typescript
type WizardAction =
| { type: 'SET_STEP'; payload: number }
| { type: 'SET_REMOTE_PARAMS'; payload: Record<string, unknown> }
| ...
```
Current StepIndicator test selectors (MUST remain compatible):
- `screen.getByText(/Backend/)` — finds step label
- `screen.getAllByRole('button')` — finds completed step buttons
- `buttons.find(b => b.textContent?.includes('Backend'))` — locates specific step button
- `screen.findByText(/Deployment/)` — finds step label async
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Rebuild StepIndicator with MD3 circles and connectors</name>
<files>src/components/wizard/StepIndicator.tsx, src/components/wizard/StepIndicator.test.tsx</files>
<action>
Rebuild `src/components/wizard/StepIndicator.tsx` from the text breadcrumb to a visual MD3 step indicator. Keep the existing `handleStepClick` logic EXACTLY as-is (SET_REMOTE_PARAMS({}) before SET_STEP when going to step 0). Remove ALL inline `style={{}}` props.
Structure (from research Pattern 4):
- Wrap in `<nav aria-label="Wizard steps"><ol className="flex items-center w-full">...</ol></nav>`
- Each step is an `<li>` containing a circle + label + optional connector
- Completed steps: `<button>` with checkmark icon, `bg-primary text-on-primary`, `aria-label="Go to step N: Label"`
- Active step: `<span>` with number, `border-2 border-primary bg-primary/10 text-primary font-bold`
- Future steps: `<span>` with number, `border-2 border-outline text-on-surface-container/40`
- All circles: `w-8 h-8 rounded-full flex items-center justify-center text-sm`
- Labels below circles: `text-xs mt-1 text-center`
- Connector lines between steps: `flex-1 h-0.5 mx-2`, `bg-primary` if completed else `bg-outline`
- Connector is `aria-hidden="true"`
CRITICAL for test compatibility: Completed step buttons MUST include the label text in their content so `b.textContent?.includes('Backend')` still works. Structure the button as:
```tsx
<button ...>
<span aria-hidden="true">&#10003;</span>
<span className="sr-only">{label}</span>
</button>
```
AND render the label as a visible `<span>` adjacent to the button (inside the same flex container). The test checks `b.textContent?.includes('Backend')` on the button element itself, so the label text must be INSIDE the button. Use:
```tsx
<button ... aria-label={`Go to step ${i + 1}: ${label}`}>
&#10003; {label}
</button>
```
This way `textContent` includes "Backend" and `aria-label` provides accessible name. The visual label below the circle can be separate.
Actually, looking at the test more carefully: the label text appears as a visible `<span>` below the circle in the new design. But the button `textContent` needs to include the label for the test. Solution: render the label text INSIDE the button element so textContent includes it, and use CSS to visually position it below the circle icon.
Simplest approach that preserves tests: make the completed step button contain BOTH the checkmark and the label text:
```tsx
<button type="button" onClick={() => handleStepClick(i)}
className="flex flex-col items-center gap-1 shrink-0 group focus-visible:outline-none">
<span className="w-8 h-8 rounded-full bg-primary text-on-primary flex items-center justify-center text-sm font-medium group-focus-visible:ring-2 group-focus-visible:ring-primary/50">
&#10003;
</span>
<span className="text-xs text-primary">{label}</span>
</button>
```
This way `button.textContent` is `"✓Backend"` which includes "Backend".
For active and future steps (non-clickable), use a `<div>` wrapper with the circle `<span>` and label `<span>` inside.
Update `StepIndicator.test.tsx` only if needed. The existing 5 tests should pass with this structure since:
- `screen.getByText(/Backend/)` will match the label span
- `screen.getAllByRole('button')` finds completed step buttons
- `buttons.find(b => b.textContent?.includes('Backend'))` works because label is inside button
- Dispatch behavior is unchanged
Run the full test suite after rebuild to catch any regressions.
</action>
<verify>
<automated>npx vitest run src/components/wizard/StepIndicator.test.tsx --reporter=dot && npx vitest run --reporter=dot</automated>
</verify>
<done>StepIndicator renders numbered circles connected by lines, completed steps show checkmarks and are clickable, current step is highlighted, future steps are muted. All inline styles removed. All 5 existing WIZD-03 tests pass. No regressions in full suite.</done>
</task>
</tasks>
<verification>
- `npx vitest run src/components/wizard/StepIndicator.test.tsx` — all 5 WIZD-03 tests pass
- `npx vitest run --reporter=dot` — full suite green
- No inline `style={{}}` props remain in StepIndicator.tsx
- No hardcoded color values (`#999`, etc.) remain
</verification>
<success_criteria>
- StepIndicator uses MD3 circles with numbered/checkmark states
- Connector lines between steps show progress visually
- All semantic tokens used (no hardcoded colors)
- All existing tests pass without modification (or with minimal test selector updates if button textContent changed)
</success_criteria>
<output>
After completion, create `.planning/phases/09-md3-components/09-02-SUMMARY.md`
</output>
@@ -0,0 +1,272 @@
---
phase: 09-md3-components
plan: 03
type: execute
wave: 2
depends_on: ["09-01"]
files_modified:
- src/components/ui/FieldRenderer.tsx
- src/components/ui/PasswordField.tsx
- src/components/ui/BackendCard.tsx
- src/components/wizard/OutputBlock.tsx
- src/components/wizard/BackendSelectionStep.tsx
- src/components/wizard/RemoteConfigStep.tsx
- src/components/wizard/DeploymentStep.tsx
- src/components/wizard/ReviewStep.tsx
- src/index.css
autonomous: false
requirements: [COMP-01, COMP-02, COMP-03]
must_haves:
truths:
- "All text inputs in the wizard render with floating labels that animate on focus and when the field has content"
- "Next and Download All buttons use MD3 filled style (rounded-full, bg-primary)"
- "Back, Copy, and Download buttons use MD3 outlined style (rounded-full, border)"
- "BackendCard uses rounded-xl shape and shadow elevation"
- "OutputBlock pre blocks use rounded-xl and subtle shadow"
- "All existing tests pass with no regressions"
artifacts:
- path: "src/components/ui/FieldRenderer.tsx"
provides: "Text-branch using TextFieldMD3 component"
contains: "TextFieldMD3"
- path: "src/components/ui/PasswordField.tsx"
provides: "Password field using TextFieldMD3 layout"
contains: "TextFieldMD3"
- path: "src/components/ui/BackendCard.tsx"
provides: "MD3 elevation and shape"
contains: "rounded-xl"
- path: "src/components/wizard/BackendSelectionStep.tsx"
provides: "MD3 filled button for Next"
contains: "MD3_BTN_FILLED"
key_links:
- from: "src/components/ui/FieldRenderer.tsx"
to: "src/components/ui/TextFieldMD3.tsx"
via: "import and render TextFieldMD3"
pattern: "import.*TextFieldMD3"
- from: "src/components/wizard/BackendSelectionStep.tsx"
to: "src/styles/md3-buttons.ts"
via: "import button constants"
pattern: "import.*MD3_BTN"
- from: "src/components/wizard/ReviewStep.tsx"
to: "src/styles/md3-buttons.ts"
via: "import button constants"
pattern: "import.*MD3_BTN"
---
<objective>
Wire TextFieldMD3 into FieldRenderer and PasswordField, apply MD3 button styles across all wizard steps, and add MD3 elevation to BackendCard and OutputBlock.
Purpose: This is the integration plan that connects the primitives from Plan 01 into the actual wizard UI, completing COMP-01 (floating labels everywhere), COMP-02 (button hierarchy everywhere), and COMP-03 (elevation on cards and code blocks).
Output: All wizard components updated with MD3 styling.
</objective>
<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>
<context>
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/09-md3-components/09-RESEARCH.md
@.planning/phases/09-md3-components/09-01-SUMMARY.md
@src/components/ui/FieldRenderer.tsx
@src/components/ui/PasswordField.tsx
@src/components/ui/BackendCard.tsx
@src/components/wizard/OutputBlock.tsx
@src/components/wizard/BackendSelectionStep.tsx
@src/components/wizard/RemoteConfigStep.tsx
@src/components/wizard/DeploymentStep.tsx
@src/components/wizard/ReviewStep.tsx
<interfaces>
<!-- TextFieldMD3 and button constants created in Plan 01 -->
From src/components/ui/TextFieldMD3.tsx (created in 09-01):
```typescript
interface TextFieldMD3Props {
id: string;
label: string;
error?: FieldError;
registration: UseFormRegisterReturn;
type?: 'text' | 'password';
helpText?: string;
required?: boolean;
suffix?: React.ReactNode;
}
export function TextFieldMD3(props: TextFieldMD3Props): JSX.Element;
```
From src/styles/md3-buttons.ts (created in 09-01):
```typescript
export const MD3_BTN_FILLED: string; // filled primary action
export const MD3_BTN_OUTLINED: string; // outlined secondary action
export const MD3_BTN_TEXT: string; // text tertiary action
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Integrate TextFieldMD3 into FieldRenderer and PasswordField</name>
<files>src/components/ui/FieldRenderer.tsx, src/components/ui/PasswordField.tsx</files>
<action>
**FieldRenderer.tsx — text branch (default, lines 82-119):**
Replace the entire text-branch return block with a `<TextFieldMD3>` render. Import TextFieldMD3 at top. The text branch currently renders label + input + helpText + error manually. Replace with:
```tsx
return (
<div className="flex flex-col gap-1">
{field.tooltipText && /* keep tooltip button + tooltip display exactly as-is */}
<TextFieldMD3
id={field.key}
label={field.label}
error={error}
registration={register(field.key)}
helpText={field.helpText}
required={field.required}
/>
</div>
);
```
CRITICAL: The tooltip button (lines 88-99) must remain OUTSIDE TextFieldMD3, rendered above it. TextFieldMD3 handles the label, input, helpText, and error display. The tooltip is a separate concern that stays in FieldRenderer.
Actually, re-examining the layout: the current text branch has tooltip button inline with label. Since TextFieldMD3 includes its own label (floating), the tooltip must be rendered separately. Place the tooltip button + tooltip content ABOVE the TextFieldMD3 component in the flex column. This preserves existing tooltip behavior.
Keep the select branch (lines 40-79) UNCHANGED — select fields do NOT get floating labels.
**PasswordField.tsx:**
Replace the manual label + input layout with TextFieldMD3, passing the show/hide toggle as `suffix`:
```tsx
import { TextFieldMD3 } from './TextFieldMD3';
export function PasswordField({ id, label, error, registration, helpText, tooltipText }: PasswordFieldProps) {
const [show, setShow] = useState(false);
const [showTooltip, setShowTooltip] = useState(false);
const toggleButton = (
<button type="button" onClick={() => setShow(v => !v)}
aria-label={show ? 'Hide' : 'Show'}
className="text-on-surface-container/50 hover:text-on-surface-container text-sm">
{show ? 'Hide' : 'Show'}
</button>
);
return (
<div className="flex flex-col gap-1">
{tooltipText && (
<button type="button" onClick={() => setShowTooltip(v => !v)}
aria-label={`More info about ${label}`}
className="text-primary hover:text-primary text-xs leading-none self-start">
</button>
)}
{tooltipText && showTooltip && (
<p className="text-xs text-primary bg-primary/10 border border-primary/30 rounded px-2 py-1.5">
{tooltipText}
</p>
)}
<TextFieldMD3
id={id}
label={label}
error={error}
registration={registration}
type={show ? 'text' : 'password'}
helpText={helpText}
suffix={toggleButton}
/>
</div>
);
}
```
Remove the `placeholder` prop from PasswordFieldProps interface since TextFieldMD3 uses `placeholder=" "` internally.
Run full test suite after — ALL `getByLabelText` queries must still work because TextFieldMD3 preserves htmlFor/id pairing.
</action>
<verify>
<automated>npx vitest run --reporter=dot</automated>
</verify>
<done>FieldRenderer text-branch renders TextFieldMD3 with floating label. PasswordField uses TextFieldMD3 with suffix for show/hide toggle. All existing tests pass (getByLabelText queries work). No regressions.</done>
</task>
<task type="auto">
<name>Task 2: Apply MD3 button styles and elevation across all wizard steps</name>
<files>src/components/wizard/BackendSelectionStep.tsx, src/components/wizard/RemoteConfigStep.tsx, src/components/wizard/DeploymentStep.tsx, src/components/wizard/ReviewStep.tsx, src/components/wizard/OutputBlock.tsx, src/components/ui/BackendCard.tsx, src/index.css</files>
<action>
**Button styling (COMP-02):** Import `{ MD3_BTN_FILLED, MD3_BTN_OUTLINED }` from `../../styles/md3-buttons` in each wizard step file. Apply className replacements:
BackendSelectionStep.tsx: "Next" button gets `className={MD3_BTN_FILLED}`. No Back button here.
RemoteConfigStep.tsx: "Back" button gets `className={MD3_BTN_OUTLINED}`, "Next" (submit) button gets `className={MD3_BTN_FILLED}`.
DeploymentStep.tsx: "Back" button gets `className={MD3_BTN_OUTLINED}`, "Next" button gets `className={MD3_BTN_FILLED}`.
ReviewStep.tsx: "Back" button gets `className={MD3_BTN_OUTLINED}`, "Download All (ZIP)" button gets `className={MD3_BTN_FILLED}`.
OutputBlock.tsx: "Copy" button gets `className={MD3_BTN_OUTLINED + ' text-xs !px-3 !py-1'}` (keep smaller size). "Download" button gets same. Import from `../../styles/md3-buttons`. Note: OutputBlock buttons are small utility buttons — keep `text-xs` and reduce padding with overrides. Alternatively, create a size-reduced variant inline: `className={\`\${MD3_BTN_OUTLINED} !text-xs !px-3 !py-1.5\`}`.
CRITICAL: Do NOT change button text content — only className. Tests use `getByRole('button', { name: /next/i })` etc.
**BackendCard elevation (COMP-03):**
Update BackendCard.tsx className:
- Change `rounded-lg` to `rounded-xl` (MD3 medium shape, 12px)
- Add `shadow hover:shadow-md` to unselected state
- Selected state: add `shadow-md`
- Add `transition-all` (already present — keep)
**OutputBlock elevation (COMP-03):**
Update OutputBlock.tsx `<pre>` className:
- Change `rounded` to `rounded-xl`
- Add `shadow-sm`
**Optional: Add elevation shadow token to index.css:**
If standard Tailwind `shadow` is insufficient, add to `@theme` block:
```css
--shadow-elevation-1: 0 1px 4px 0 rgb(0 0 0 / 0.37);
```
Only add this if the default `shadow` utility doesn't provide enough visual lift. Use your judgment.
Run full test suite after all changes.
</action>
<verify>
<automated>npx vitest run --reporter=dot</automated>
</verify>
<done>All Next/Download buttons use MD3 filled (rounded-full, bg-primary). All Back/Copy buttons use MD3 outlined (rounded-full, border). BackendCard has rounded-xl and shadow. OutputBlock pre has rounded-xl and shadow-sm. All tests pass.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 3: Visual verification of complete MD3 component set</name>
<what-built>Complete MD3 component migration: floating label text fields, MD3 button hierarchy (filled/outlined), card elevation, and rebuilt step indicator</what-built>
<how-to-verify>
1. Run `npm run dev` and open http://localhost:5173
2. **Step Indicator:** Verify numbered circles with connector lines at top. Step 1 should be highlighted, future steps muted.
3. **Backend Selection:** Click a backend card — verify rounded corners (rounded-xl) and shadow elevation. Verify "Next" button is pill-shaped (rounded-full) with primary fill color.
4. **Remote Config:** Verify text inputs have floating labels that animate up on focus and stay floated when field has value. Verify "Back" button is pill-shaped outlined, "Next" is pill-shaped filled.
5. **Password fields:** Verify floating label works with show/hide toggle button visible on right side.
6. **Deployment Step:** Verify Back/Next button styles match MD3 hierarchy.
7. **Review Step:** Verify "Back" is outlined, "Download All" is filled. Verify OutputBlock code areas have rounded corners and subtle shadow. Verify Copy/Download buttons are outlined style.
8. **Navigate back** to step 1 — verify step indicator shows checkmark on completed step, and the step is clickable.
9. **Toggle dark mode** — verify all new components look correct in dark theme (no hardcoded colors, shadows work).
</how-to-verify>
<resume-signal>Type "approved" or describe issues</resume-signal>
</task>
</tasks>
<verification>
- `npx vitest run` — full suite passes (166+ tests, zero regressions)
- All text inputs render floating labels
- Buttons follow MD3 filled/outlined/text hierarchy
- Cards and code blocks have MD3 elevation
- Dark mode renders correctly
</verification>
<success_criteria>
- COMP-01: All text inputs (FieldRenderer text-branch + PasswordField) render as MD3 outlined fields with floating labels
- COMP-02: Buttons across all wizard steps follow filled/outlined/text hierarchy
- COMP-03: BackendCard has rounded-xl + shadow, OutputBlock pre has rounded-xl + shadow-sm
- Zero test regressions
- User visually approves the complete MD3 component set
</success_criteria>
<output>
After completion, create `.planning/phases/09-md3-components/09-03-SUMMARY.md`
</output>