docs(08): create phase plan - 2 plans, 2 waves

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 18:07:29 +02:00
co-authored by Claude Opus 4.6
parent 0ef71cdcab
commit bee3bad93a
3 changed files with 492 additions and 2 deletions
+5 -2
View File
@@ -51,7 +51,10 @@ Full phase details: [.planning/milestones/v1.1-ROADMAP.md](milestones/v1.1-ROADM
2. User can toggle between System, Light, and Dark themes via a visible control, and the entire UI responds immediately 2. User can toggle between System, Light, and Dark themes via a visible control, and the entire UI responds immediately
3. Theme preference persists in localStorage and applies on reload without any flash of wrong theme 3. Theme preference persists in localStorage and applies on reload without any flash of wrong theme
4. Tailwind v4 `@theme` directive maps MD3 token names to utility classes (e.g., `bg-surface`, `text-on-primary`) 4. Tailwind v4 `@theme` directive maps MD3 token names to utility classes (e.g., `bg-surface`, `text-on-primary`)
**Plans**: TBD **Plans:** 2 plans
Plans:
- [ ] 08-01-PLAN.md — Token system, flash prevention, ThemeToggle component
- [ ] 08-02-PLAN.md — Color class migration across all components
### Phase 9: MD3 Components ### Phase 9: MD3 Components
**Goal**: All interactive elements use MD3-styled primitives with consistent visual language across the entire wizard **Goal**: All interactive elements use MD3-styled primitives with consistent visual language across the entire wizard
@@ -98,7 +101,7 @@ Full phase details: [.planning/milestones/v1.1-ROADMAP.md](milestones/v1.1-ROADM
| 5. Tech Debt | v1.1 | 4/4 | Complete | 2026-03-30 | | 5. Tech Debt | v1.1 | 4/4 | Complete | 2026-03-30 |
| 6. New Backends | v1.1 | 4/4 | Complete | 2026-03-31 | | 6. New Backends | v1.1 | 4/4 | Complete | 2026-03-31 |
| 7. Validation & UX Polish | v1.1 | 3/3 | Complete | 2026-03-31 | | 7. Validation & UX Polish | v1.1 | 3/3 | Complete | 2026-03-31 |
| 8. Theme Foundation | v1.2 | 0/? | Not started | - | | 8. Theme Foundation | v1.2 | 0/2 | Planning | - |
| 9. MD3 Components | v1.2 | 0/? | Not started | - | | 9. MD3 Components | v1.2 | 0/? | Not started | - |
| 10. Content & Clarity | v1.2 | 0/? | Not started | - | | 10. Content & Clarity | v1.2 | 0/? | Not started | - |
| 11. Polish & Responsiveness | v1.2 | 0/? | Not started | - | | 11. Polish & Responsiveness | v1.2 | 0/? | Not started | - |
@@ -0,0 +1,225 @@
---
phase: 08-theme-foundation
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/index.css
- index.html
- src/components/ui/ThemeToggle.tsx
- src/components/ui/ThemeToggle.test.tsx
- src/App.tsx
autonomous: true
requirements:
- THEME-01
- THEME-02
must_haves:
truths:
- "Tailwind utility classes bg-surface, text-on-surface, bg-primary, text-on-primary, border-outline etc. are available and resolve to correct hex values"
- "Adding .dark class to html element switches all token values to dark palette"
- "ThemeToggle renders three options: Light, Dark, System"
- "Clicking a theme option applies the correct class to document.documentElement and persists to localStorage"
- "On hard reload with r2b-theme=dark in localStorage, the .dark class is present before React mounts (no flash)"
artifacts:
- path: "src/index.css"
provides: "MD3 color token definitions (light + dark) and @theme mapping"
contains: "@theme"
- path: "index.html"
provides: "Inline blocking script for flash prevention"
contains: "r2b-theme"
- path: "src/components/ui/ThemeToggle.tsx"
provides: "Segmented theme toggle component"
exports: ["ThemeToggle"]
- path: "src/components/ui/ThemeToggle.test.tsx"
provides: "Unit tests for ThemeToggle behavior"
contains: "describe"
- path: "src/App.tsx"
provides: "ThemeToggle wired into header, bg-surface applied to shell"
contains: "ThemeToggle"
key_links:
- from: "src/index.css"
to: "src/components/ui/ThemeToggle.tsx"
via: ".dark class on html toggles CSS variable cascade"
pattern: "classList\\.toggle.*dark"
- from: "index.html"
to: "localStorage"
via: "inline script reads r2b-theme before paint"
pattern: "localStorage\\.getItem.*r2b-theme"
- from: "src/App.tsx"
to: "src/components/ui/ThemeToggle.tsx"
via: "import and render in header"
pattern: "import.*ThemeToggle"
---
<objective>
Build the MD3 color token system, dark mode infrastructure, and ThemeToggle component.
Purpose: Establish the CSS custom property foundation that all components will reference instead of hardcoded Tailwind colors. This is the infrastructure layer that Plan 02 (migration) and Phases 9-11 depend on.
Output: Working token system with dark mode toggle, flash prevention, and ThemeToggle component with unit 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/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/08-theme-foundation/08-CONTEXT.md
@.planning/phases/08-theme-foundation/08-RESEARCH.md
<interfaces>
<!-- Current src/index.css is just: @import "tailwindcss"; -->
<!-- Current index.html has no inline scripts -->
From src/App.tsx (current structure):
```tsx
import { useWizard } from './store/context';
import { WizardProvider } from './store/context';
import { StepIndicator } from './components/wizard/StepIndicator';
// ... step imports
function WizardShell() {
const { state } = useWizard();
// ...
return (
<div className="min-h-screen bg-gray-50 flex flex-col items-center py-12 px-4">
<div className="w-full max-w-2xl">
<h1 className="text-3xl font-bold text-gray-900 mb-8 text-center">Ready2Blob</h1>
<StepIndicator />
<div className="mt-8">{CurrentStep}</div>
</div>
</div>
);
}
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Define MD3 color tokens and flash prevention script</name>
<files>src/index.css, index.html</files>
<action>
**src/index.css** — Replace the single `@import "tailwindcss"` line with the complete token system. Structure (in this exact order):
1. `@import "tailwindcss";`
2. `@custom-variant dark (&:where(.dark, .dark *));` — immediately after import, before any @layer or @theme
3. `@layer base` block with `:root` (light values) and `.dark` (dark values) for all tokens:
- `--r2b-primary`: light #4338CA, dark #A5B4FC
- `--r2b-on-primary`: light #FFFFFF, dark #1E1B4B
- `--r2b-surface`: light #F9FAFB, dark #111827
- `--r2b-on-surface`: light #111827, dark #F9FAFB
- `--r2b-surface-variant`: light #1F2937, dark #0F172A (code blocks)
- `--r2b-on-surface-variant`: light #F3F4F6, dark #E5E7EB
- `--r2b-surface-container`: light #FFFFFF, dark #1F2937 (cards/forms)
- `--r2b-on-surface-container`: light #374151, dark #D1D5DB
- `--r2b-outline`: light #D1D5DB, dark #4B5563
- `--r2b-error`: light #EF4444, dark #FCA5A5
- `--r2b-on-error`: light #FFFFFF, dark #7F1D1D
- Also add warning/success tokens per research recommendation:
- `--r2b-success`: light #15803D (green-700), dark #86EFAC (green-300)
- `--r2b-on-success`: light #F0FDF4 (green-50), dark #14532D (green-900)
- `--r2b-warning`: light #D97706 (amber-600), dark #FCD34D (amber-300)
- `--r2b-on-warning`: light #FFFBEB (amber-50), dark #78350F (amber-900)
Also add `body` transition in `@layer base`: `transition: background-color 200ms ease, color 200ms ease, border-color 200ms ease` with `@media (prefers-reduced-motion: reduce)` wrapper that sets `transition: none`.
4. `@theme` block mapping each `--color-*` to `var(--r2b-*)` for all 15 tokens (the 11 core + warning, on-warning, success, on-success).
**index.html** — Add inline blocking script inside `<head>`, BEFORE any stylesheet or script tags. The script:
1. Reads `localStorage.getItem('r2b-theme')`
2. If `'dark'` -> effective = dark. If `'light'` -> effective = light. Otherwise -> check `window.matchMedia('(prefers-color-scheme: dark)').matches`
3. If effective is dark, add `.dark` to `document.documentElement.classList`
4. Wrap in try/catch for private browsing mode safety
Use vanilla JS (no arrow functions) for maximum browser compat in the inline script. Use the exact pattern from 08-RESEARCH.md Pattern 2.
</action>
<verify>
<automated>cd C:/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vite build 2>&1 | tail -5</automated>
</verify>
<done>src/index.css contains @custom-variant, @layer base with :root and .dark blocks (15 token pairs), @theme mapping all tokens. index.html has inline blocking script reading r2b-theme from localStorage. Vite build succeeds with no errors.</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Create ThemeToggle component with tests and wire into App.tsx</name>
<files>src/components/ui/ThemeToggle.tsx, src/components/ui/ThemeToggle.test.tsx, src/App.tsx</files>
<behavior>
- ThemeToggle renders three buttons: Light, Dark, System
- Each button has role="button" and aria-pressed reflecting current selection
- The component group has aria-label="Theme"
- Default selection is "system" when localStorage is empty
- Clicking "Dark" adds .dark class to document.documentElement
- Clicking "Light" removes .dark class from document.documentElement
- Clicking any option writes the value to localStorage key "r2b-theme"
- Active button uses bg-primary text-on-primary classes
- Inactive buttons use bg-surface-container text-on-surface-container classes
</behavior>
<action>
**ThemeToggle.test.tsx** (RED first):
Write tests using Vitest + jsdom (add `// @vitest-environment jsdom` pragma). Test:
1. Renders three buttons with text containing "Light", "Dark", "System"
2. Default state is "System" (aria-pressed="true" on System button) when localStorage is empty
3. Clicking "Dark" -> `document.documentElement.classList.contains('dark')` is true
4. Clicking "Light" -> `document.documentElement.classList.contains('dark')` is false
5. Clicking "Dark" -> `localStorage.getItem('r2b-theme')` === `'dark'`
6. Active button has aria-pressed="true", others have aria-pressed="false"
Run tests — they must FAIL (RED).
**ThemeToggle.tsx** (GREEN):
Create the component following 08-RESEARCH.md Pattern 3:
- Type `ThemeValue = 'light' | 'dark' | 'system'`
- `STORAGE_KEY = 'r2b-theme'`
- `getStored()` reads from localStorage, returns ThemeValue (default 'system')
- `applyTheme(value)` toggles `.dark` on `document.documentElement` and writes to localStorage
- `useState<ThemeValue>(getStored)` for lazy init
- Render as a segmented control: `<div role="group" aria-label="Theme">` with three buttons
- Each button: `type="button"`, `aria-pressed={theme === v}`, `onClick={() => select(v)}`
- Active state: `bg-primary text-on-primary font-medium`
- Inactive state: `bg-surface-container text-on-surface-container hover:bg-surface`
- Icons: Sun for Light, Moon for Dark, Monitor for System (use Unicode or simple SVG inline icons)
- Outer div: `flex rounded-md border border-outline overflow-hidden text-sm`
Run tests — they must PASS (GREEN).
**App.tsx** updates:
1. Add `import { ThemeToggle } from './components/ui/ThemeToggle';`
2. Change the header from centered h1 to a flex row: `<div className="flex items-center justify-between mb-8">`
3. h1 keeps `text-3xl font-bold` but change `text-gray-900` to `text-on-surface`, remove `text-center`
4. Add `<ThemeToggle />` as second child in the flex row
5. Change outer div `bg-gray-50` to `bg-surface`
6. Change h1 `text-gray-900` to `text-on-surface`
Run full test suite to confirm no regressions.
</action>
<verify>
<automated>cd C:/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npm test 2>&1 | tail -20</automated>
</verify>
<done>ThemeToggle.test.tsx has 6+ passing tests. ThemeToggle renders segmented control with Light/Dark/System. App.tsx header shows title left and ThemeToggle right. All existing tests still pass. App outer div uses bg-surface, h1 uses text-on-surface.</done>
</task>
</tasks>
<verification>
1. `npx vite build` completes without errors
2. `npm test` — all tests pass (existing + new ThemeToggle tests)
3. `grep -c "@theme" src/index.css` returns 1 (theme block exists)
4. `grep -c "r2b-theme" index.html` returns at least 1 (inline script present)
5. `grep "ThemeToggle" src/App.tsx` confirms component is wired in
</verification>
<success_criteria>
- Token system produces working Tailwind utility classes: bg-surface, text-on-surface, bg-primary, etc.
- Dark mode toggle works: clicking Dark applies .dark class, Light removes it, System checks OS preference
- Flash prevention: inline script in index.html reads localStorage before CSS paints
- ThemeToggle has passing unit tests covering all three modes and DOM class toggling
- No regression in existing test suite
</success_criteria>
<output>
After completion, create `.planning/phases/08-theme-foundation/08-01-SUMMARY.md`
</output>
@@ -0,0 +1,262 @@
---
phase: 08-theme-foundation
plan: 02
type: execute
wave: 2
depends_on:
- "08-01"
files_modified:
- src/components/ui/BackendCard.tsx
- src/components/ui/FieldRenderer.tsx
- src/components/ui/PasswordField.tsx
- src/components/wizard/AzureAuthToggle.tsx
- src/components/wizard/SftpAuthToggle.tsx
- src/components/wizard/DeploymentStep.tsx
- src/components/wizard/OutputBlock.tsx
- src/components/wizard/RemoteConfigStep.tsx
- src/components/wizard/ReviewStep.tsx
autonomous: false
requirements:
- THEME-01
- THEME-02
must_haves:
truths:
- "No hardcoded Tailwind color classes (gray-*, blue-*, red-*, green-*, yellow-*) remain in any of the 10 migrated files (App.tsx already done in Plan 01)"
- "All components render correctly using semantic token classes (bg-surface, text-on-surface, bg-primary, etc.)"
- "Components look correct in both light and dark mode without any dark: prefixes"
- "All existing tests pass without modification (tests use semantic queries, not class selectors)"
artifacts:
- path: "src/components/ui/BackendCard.tsx"
provides: "Migrated backend selection card"
min_lines: 15
- path: "src/components/ui/FieldRenderer.tsx"
provides: "Migrated form field renderer"
min_lines: 50
- path: "src/components/ui/PasswordField.tsx"
provides: "Migrated password input"
min_lines: 30
- path: "src/components/wizard/AzureAuthToggle.tsx"
provides: "Migrated Azure auth method toggle"
min_lines: 20
- path: "src/components/wizard/SftpAuthToggle.tsx"
provides: "Migrated SFTP auth method toggle"
min_lines: 25
- path: "src/components/wizard/DeploymentStep.tsx"
provides: "Migrated deployment step"
min_lines: 50
- path: "src/components/wizard/OutputBlock.tsx"
provides: "Migrated code output block"
min_lines: 20
- path: "src/components/wizard/RemoteConfigStep.tsx"
provides: "Migrated remote config step"
min_lines: 50
- path: "src/components/wizard/ReviewStep.tsx"
provides: "Migrated review step with success/warning tokens"
min_lines: 60
key_links:
- from: "src/index.css"
to: "all 9 migrated component files"
via: "CSS variable cascade through Tailwind utility classes"
pattern: "bg-surface|text-on-surface|border-outline|bg-primary|text-on-primary"
---
<objective>
Migrate all 63 hardcoded Tailwind color classes across 9 component files to semantic MD3 token classes.
Purpose: Complete THEME-01 by eliminating all hardcoded color references. After this plan, the entire UI responds to theme changes via the CSS variable cascade established in Plan 01.
Output: 9 migrated component files with zero hardcoded color classes, all tests passing.
</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/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/08-theme-foundation/08-CONTEXT.md
@.planning/phases/08-theme-foundation/08-RESEARCH.md
@.planning/phases/08-theme-foundation/08-01-SUMMARY.md
<interfaces>
<!-- Token utility classes available after Plan 01 (from @theme in src/index.css): -->
<!-- bg-surface, text-on-surface, bg-surface-variant, text-on-surface-variant -->
<!-- bg-surface-container, text-on-surface-container -->
<!-- bg-primary, text-on-primary, text-primary, border-primary -->
<!-- border-outline, bg-error, text-error, text-on-error -->
<!-- bg-success, text-success, text-on-success -->
<!-- bg-warning, text-warning, text-on-warning -->
<!-- Opacity modifiers work: text-on-surface-container/70, border-primary/30, etc. -->
</interfaces>
<!-- Migration mapping reference (from 08-RESEARCH.md Pattern 4): -->
<!--
bg-gray-50 -> bg-surface
bg-white -> bg-surface-container
bg-gray-900 -> bg-surface-variant (code blocks)
text-gray-900 -> text-on-surface
text-gray-700 -> text-on-surface-container
text-gray-500 -> text-on-surface-container/70
text-gray-400 -> text-on-surface-container/50
text-gray-100 -> text-on-surface-variant
border-gray-300 -> border-outline
border-gray-200 -> border-outline
hover:bg-gray-50 -> hover:bg-surface
hover:border-blue-400 -> hover:border-primary
bg-blue-600 -> bg-primary
bg-blue-50 -> bg-primary/10
text-white (on blue bg) -> text-on-primary
text-blue-600/500/700 -> text-primary
hover:bg-blue-700 -> hover:bg-primary
border-blue-600 -> border-primary
border-blue-200 -> border-primary/30
text-blue-700 bg-blue-50 border-blue-200 -> text-primary bg-primary/10 border-primary/30
border-red-500 -> border-error
focus:ring-red-300 -> focus:ring-error/50
text-red-600 -> text-error
text-red-500 -> text-error
focus:ring-blue-300 -> focus:ring-primary/50
text-green-700 bg-green-50 -> text-success bg-on-success (or bg-success/10)
bg-yellow-50 border-yellow-300 text-yellow-800/900 -> bg-on-warning border-warning text-warning
-->
</context>
<tasks>
<task type="auto">
<name>Task 1: Migrate UI primitives (BackendCard, FieldRenderer, PasswordField)</name>
<files>src/components/ui/BackendCard.tsx, src/components/ui/FieldRenderer.tsx, src/components/ui/PasswordField.tsx</files>
<action>
Migrate all hardcoded color classes to semantic tokens. This is mechanical find-and-replace following the mapping. Do NOT change any DOM structure, only className strings.
**BackendCard.tsx** (5 occurrences):
- `border-blue-600 bg-blue-50` (selected) -> `border-primary bg-primary/10`
- `border-gray-200 bg-white hover:border-blue-400 hover:bg-gray-50` (unselected) -> `border-outline bg-surface-container hover:border-primary hover:bg-surface`
- `text-gray-900` -> `text-on-surface`
- `text-gray-500` -> `text-on-surface-container/70`
**FieldRenderer.tsx** (17 occurrences):
- `text-gray-700` (labels) -> `text-on-surface-container`
- `text-red-500` (required asterisk) -> `text-error`
- `text-blue-500 hover:text-blue-700` (toggle link) -> `text-primary hover:text-primary`
- `text-blue-700 bg-blue-50 border border-blue-200` (tooltip/info) -> `text-primary bg-primary/10 border border-primary/30`
- `border-red-500 focus:ring-red-300` (error state) -> `border-error focus:ring-error/50`
- `border-gray-300 focus:ring-blue-300` (normal state) -> `border-outline focus:ring-primary/50`
- `text-gray-500` (helpText) -> `text-on-surface-container/70`
- `text-red-600` (error message) -> `text-error`
- Apply the same mapping to both the text-input branch and the select branch (they have identical color patterns)
**PasswordField.tsx** (8 occurrences):
- `text-gray-700` -> `text-on-surface-container`
- `text-blue-500 hover:text-blue-700` -> `text-primary hover:text-primary`
- `text-blue-700 bg-blue-50 border border-blue-200` -> `text-primary bg-primary/10 border border-primary/30`
- `border-red-500 focus:ring-red-300` -> `border-error focus:ring-error/50`
- `border-gray-300 focus:ring-blue-300` -> `border-outline focus:ring-primary/50`
- `text-gray-400 hover:text-gray-700` (eye icon) -> `text-on-surface-container/50 hover:text-on-surface-container`
- `text-gray-500` (helpText) -> `text-on-surface-container/70`
- `text-red-600` (error message) -> `text-error`
Run `npm test` after each file to catch any regressions immediately.
</action>
<verify>
<automated>cd C:/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npm test 2>&1 | tail -20</automated>
</verify>
<done>BackendCard.tsx has 0 hardcoded color classes. FieldRenderer.tsx has 0 hardcoded color classes. PasswordField.tsx has 0 hardcoded color classes. All tests pass.</done>
</task>
<task type="auto">
<name>Task 2: Migrate wizard components (toggles, steps, output block)</name>
<files>src/components/wizard/AzureAuthToggle.tsx, src/components/wizard/SftpAuthToggle.tsx, src/components/wizard/DeploymentStep.tsx, src/components/wizard/OutputBlock.tsx, src/components/wizard/RemoteConfigStep.tsx, src/components/wizard/ReviewStep.tsx</files>
<action>
Continue the mechanical migration for all wizard components. Do NOT change DOM structure, only className strings.
**AzureAuthToggle.tsx** (7 occurrences):
- `border-gray-300` -> `border-outline`
- `bg-blue-600 text-white` (active tab) -> `bg-primary text-on-primary`
- `bg-white text-gray-700 hover:bg-gray-50` (inactive tab) -> `bg-surface-container text-on-surface-container hover:bg-surface`
**SftpAuthToggle.tsx** (10 occurrences):
- `text-gray-700` (label) -> `text-on-surface-container`
- `text-blue-500 hover:text-blue-700` (toggle link) -> `text-primary hover:text-primary`
- `text-blue-700 bg-blue-50 border border-blue-200` (info) -> `text-primary bg-primary/10 border border-primary/30`
- `border-gray-300` -> `border-outline`
- `bg-blue-600 text-white` (active) -> `bg-primary text-on-primary`
- `bg-white text-gray-700 hover:bg-gray-50` (inactive) -> `bg-surface-container text-on-surface-container hover:bg-surface`
**DeploymentStep.tsx** (2 occurrences):
- `border border-gray-300 ... hover:bg-gray-50` (Back button) -> `border border-outline ... hover:bg-surface`
- `bg-blue-600 text-white ... hover:bg-blue-700` (Next button) -> `bg-primary text-on-primary ... hover:bg-primary`
**OutputBlock.tsx** (4 occurrences):
- `text-gray-700` (label) -> `text-on-surface-container`
- `border-gray-300` (action buttons, 2 occurrences) -> `border-outline`
- `bg-gray-900 text-gray-100` (code pre) -> `bg-surface-variant text-on-surface-variant`
**RemoteConfigStep.tsx** (2 occurrences):
- `border border-gray-300 ... hover:bg-gray-50` (Back button) -> `border border-outline ... hover:bg-surface`
- `bg-blue-600 text-white ... hover:bg-blue-700` (Next button) -> `bg-primary text-on-primary ... hover:bg-primary`
**ReviewStep.tsx** (6 occurrences):
- `text-green-700 bg-green-50` (client-side notice) -> `text-success bg-on-success`
- `bg-yellow-50 border border-yellow-300` (security warning box) -> `bg-on-warning border border-warning`
- `text-yellow-800` (warning title) -> `text-warning`
- `text-yellow-900` (warning label) -> `text-warning`
- `border border-gray-300 ... hover:bg-gray-50` (Back button) -> `border border-outline ... hover:bg-surface`
- `bg-blue-600 text-white ... hover:bg-blue-700` (Download button) -> `bg-primary text-on-primary ... hover:bg-primary`
After all files migrated, run verification grep to confirm zero hardcoded color classes remain.
</action>
<verify>
<automated>cd C:/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npm test 2>&1 | tail -20 && echo "---GREP CHECK---" && grep -rn "text-gray-\|bg-gray-\|border-gray-\|bg-blue-\|text-blue-\|border-blue-\|text-white\|bg-white\|text-red-\|border-red-\|text-green-\|bg-green-\|bg-yellow-\|text-yellow-\|border-yellow-\|hover:bg-gray-\|hover:bg-blue-\|hover:border-blue-\|focus:ring-red-\|focus:ring-blue-" src/components/ src/App.tsx 2>/dev/null; echo "Exit: $?"</automated>
</verify>
<done>All 9 component files migrated. Grep for hardcoded color classes in src/components/ and src/App.tsx returns 0 matches. All existing tests pass without modification.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 3: Visual verification of theme system across all wizard steps</name>
<files>none</files>
<action>
Present the completed theme system for user visual verification. Start the dev server if not already running.
</action>
<what-built>Complete MD3 token system with dark mode toggle and all 63 hardcoded color classes migrated to semantic tokens across 10 files.</what-built>
<how-to-verify>
1. Run `npm run dev` and open http://localhost:5173
2. Verify the app renders correctly in light mode (indigo primary accents, gray surfaces)
3. Click "Dark" in the theme toggle (top-right, next to "Ready2Blob" title) -- entire UI should switch to dark palette instantly
4. Click "Light" -- UI returns to light palette
5. Click "System" -- follows your OS preference
6. With "Dark" selected, hard-reload the page (Ctrl+Shift+R) -- should load directly in dark mode with NO flash of light theme
7. Navigate through all 4 wizard steps checking:
- Backend cards have correct border/background colors in both themes
- Form inputs have visible borders and labels in both themes
- Code output blocks (ReviewStep) are readable in both themes
- Warning/success notices in ReviewStep are visible in both themes
- All buttons (Back/Next/Download) are visible and styled in both themes
</how-to-verify>
<verify>User confirms visual correctness</verify>
<done>User approves visual appearance of both light and dark themes across all wizard steps.</done>
<resume-signal>Type "approved" or describe any visual issues</resume-signal>
</task>
</tasks>
<verification>
1. `npm test` -- all tests pass (existing + ThemeToggle tests from Plan 01)
2. `grep -rn "text-gray-\|bg-gray-\|border-gray-\|bg-blue-\|text-blue-\|border-blue-\|text-white\|bg-white\|text-red-\|border-red-\|text-green-\|bg-green-\|bg-yellow-\|text-yellow-\|border-yellow-" src/components/ src/App.tsx` returns 0 matches
3. `npx vite build` completes without errors
</verification>
<success_criteria>
- Zero hardcoded Tailwind color classes remain in src/components/ and src/App.tsx
- All 9 component files use only semantic token classes (bg-surface, text-on-surface, bg-primary, etc.)
- No `dark:` utility prefixes used anywhere -- the CSS variable cascade handles both themes
- All existing tests pass without modification
- Visual verification confirms both light and dark mode render correctly across all wizard steps
</success_criteria>
<output>
After completion, create `.planning/phases/08-theme-foundation/08-02-SUMMARY.md`
</output>