Files
kawaandClaude Opus 4.6 2ea0a1faaf fix(08): revise plans based on checker feedback
- 08-01 Task 2: change type="auto" tdd="true" to type="tdd" (schema consistency)
- 08-02 Task 2: fix inverted on-role tokens in ReviewStep migration
  bg-on-success -> bg-success/10, bg-on-warning -> bg-warning/10
- 08-02 mapping comment: same fix for consistency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:11:02 +02:00

10 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
08-theme-foundation 01 execute 1
src/index.css
index.html
src/components/ui/ThemeToggle.tsx
src/components/ui/ThemeToggle.test.tsx
src/App.tsx
true
THEME-01
THEME-02
truths artifacts key_links
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)
path provides contains
src/index.css MD3 color token definitions (light + dark) and @theme mapping @theme
path provides contains
index.html Inline blocking script for flash prevention r2b-theme
path provides exports
src/components/ui/ThemeToggle.tsx Segmented theme toggle component
ThemeToggle
path provides contains
src/components/ui/ThemeToggle.test.tsx Unit tests for ThemeToggle behavior describe
path provides contains
src/App.tsx ThemeToggle wired into header, bg-surface applied to shell ThemeToggle
from to via pattern
src/index.css src/components/ui/ThemeToggle.tsx .dark class on html toggles CSS variable cascade classList.toggle.*dark
from to via pattern
index.html localStorage inline script reads r2b-theme before paint localStorage.getItem.*r2b-theme
from to via pattern
src/App.tsx src/components/ui/ThemeToggle.tsx import and render in header import.*ThemeToggle
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.

<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/08-theme-foundation/08-CONTEXT.md @.planning/phases/08-theme-foundation/08-RESEARCH.md

From src/App.tsx (current structure):

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>
  );
}
Task 1: Define MD3 color tokens and flash prevention script src/index.css, index.html **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. cd C:/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vite build 2>&1 | tail -5 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.

Task 2: Create ThemeToggle component with tests and wire into App.tsx src/components/ui/ThemeToggle.tsx, src/components/ui/ThemeToggle.test.tsx, src/App.tsx - 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 **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. cd C:/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npm test 2>&1 | tail -20 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.

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

<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>
After completion, create `.planning/phases/08-theme-foundation/08-01-SUMMARY.md`