docs(08-01): complete theme foundation plan 01
- SUMMARY with MD3 token system, ThemeToggle, FOUC prevention - STATE updated: decisions, session, 97% progress - ROADMAP phase 8 marked In Progress (1/2 plans) - REQUIREMENTS THEME-01, THEME-02 marked complete
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
---
|
||||
phase: 08-theme-foundation
|
||||
plan: 01
|
||||
subsystem: ui
|
||||
tags: [tailwindcss, css-custom-properties, dark-mode, theme, react, vitest]
|
||||
|
||||
# Dependency graph
|
||||
requires: []
|
||||
provides:
|
||||
- MD3 color token system (15 tokens: primary, surface, error, success, warning families)
|
||||
- Dark mode infrastructure via @custom-variant dark + .dark class on <html>
|
||||
- Flash prevention inline script in index.html
|
||||
- ThemeToggle segmented control component (Light/Dark/System)
|
||||
- bg-surface, text-on-surface, bg-primary, etc. Tailwind utility classes
|
||||
affects: [09-component-migration, 10-content-refinement, 11-polish]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- Two-layer token architecture (@layer base :root/.dark raw values + @theme var() references)
|
||||
- DOM class toggle for theme (no React Context, no re-render cascade)
|
||||
- Inline blocking script in <head> for FOUC prevention
|
||||
- vi.stubGlobal for Node v25 localStorage/matchMedia in jsdom tests
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- src/components/ui/ThemeToggle.tsx
|
||||
- src/components/ui/ThemeToggle.test.tsx
|
||||
modified:
|
||||
- src/index.css
|
||||
- index.html
|
||||
- src/App.tsx
|
||||
|
||||
key-decisions:
|
||||
- "Two-layer CSS token pattern: raw values in @layer base :root/.dark, @theme references via var() — required for .dark cascade to work"
|
||||
- "vi.stubGlobal for localStorage and matchMedia in tests — Node v25 experimental WebStorage breaks standard Storage API in jsdom"
|
||||
- "15 tokens (11 core + success/on-success/warning/on-warning) to cover ReviewStep semantic colors without hardcoded dark: prefixes"
|
||||
- "Inline FOUC script placed before stylesheets in <head> for maximum paint-blocking guarantee"
|
||||
|
||||
patterns-established:
|
||||
- "Token pattern: Always use @theme { --color-X: var(--r2b-X); } + @layer base :root/.dark for dynamic theming"
|
||||
- "Test pattern: vi.stubGlobal('localStorage', mock) + vi.stubGlobal('matchMedia', mock) for Node v25 jsdom compatibility"
|
||||
- "DOM theme toggle: applyTheme() directly mutates document.documentElement.classList — no useEffect, no Context"
|
||||
|
||||
requirements-completed: [THEME-01, THEME-02]
|
||||
|
||||
# Metrics
|
||||
duration: 17min
|
||||
completed: 2026-04-01
|
||||
---
|
||||
|
||||
# Phase 8, Plan 01: Theme Foundation Summary
|
||||
|
||||
**MD3 color token system with 15 CSS custom properties, @custom-variant dark toggle, FOUC-free flash prevention script, and ThemeToggle segmented control component with 7 passing unit tests**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 17 min
|
||||
- **Started:** 2026-04-01T02:32:49Z
|
||||
- **Completed:** 2026-04-01T02:50:04Z
|
||||
- **Tasks:** 2 (+ TDD RED commit)
|
||||
- **Files modified:** 5
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Full MD3 color token system in `src/index.css`: 15 token pairs (light/dark), wired to Tailwind utility classes via `@theme { --color-*: var(--r2b-*) }` two-layer pattern
|
||||
- Flash prevention inline blocking script added to `index.html` `<head>` before stylesheets; reads `r2b-theme` from localStorage before first CSS paint
|
||||
- ThemeToggle segmented control: Light/Dark/System, writes localStorage + toggles `.dark` on `<html>`, no React Context overhead
|
||||
- ThemeToggle wired into App.tsx header flex row; `bg-gray-50` → `bg-surface`, `text-gray-900` → `text-on-surface`
|
||||
- All 166 tests pass (159 pre-existing + 7 new ThemeToggle unit tests)
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Define MD3 color tokens and flash prevention script** - `6027552` (feat)
|
||||
2. **Task 2 RED: Add failing tests for ThemeToggle** - `fa2c0d9` (test)
|
||||
3. **Task 2 GREEN: Implement ThemeToggle and wire into App.tsx** - `074dc33` (feat)
|
||||
|
||||
**Plan metadata:** _(docs commit follows)_
|
||||
|
||||
_Note: TDD task split into RED (fa2c0d9) and GREEN (074dc33) commits per TDD protocol_
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `src/index.css` — @import + @custom-variant dark + @layer base with 15 token pairs + @theme mapping + body transition
|
||||
- `index.html` — Inline blocking IIFE script reading r2b-theme from localStorage before paint
|
||||
- `src/components/ui/ThemeToggle.tsx` — Segmented control: 3 buttons, DOM class toggle, localStorage write
|
||||
- `src/components/ui/ThemeToggle.test.tsx` — 7 unit tests with localStorage/matchMedia stubs for Node v25
|
||||
- `src/App.tsx` — Import ThemeToggle, flex header row, bg-surface + text-on-surface token classes
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- **Two-layer token pattern confirmed:** `@theme { --color-X: var(--r2b-X); }` + `@layer base :root/.dark { --r2b-X: <hex>; }` is the only pattern that allows `.dark` class cascade to work in Tailwind v4. Static hex values in `@theme` break dark mode override.
|
||||
- **15 tokens instead of 10:** Added success/on-success (green) and warning/on-warning (amber) to cover ReviewStep semantic colors. Avoids hardcoded `dark:` prefixes in Phase 9 migration.
|
||||
- **Node v25 localStorage workaround:** Node v25 ships experimental WebStorage (with `--localstorage-file` flag). The global `localStorage` in tests lacks `setItem`/`getItem` methods. Fix: `vi.stubGlobal('localStorage', inMemoryMock)` in test `beforeEach`.
|
||||
- **matchMedia mock required:** jsdom does not implement `window.matchMedia`. `applyTheme()` calls it for system preference. Fix: `vi.stubGlobal('matchMedia', mockFn)` returning `{ matches: false, ... }`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 1 - Bug] localStorage.clear() unavailable in Node v25 jsdom test environment**
|
||||
- **Found during:** Task 2 (ThemeToggle TDD)
|
||||
- **Issue:** Node v25 experimental WebStorage provides a global `localStorage` object with no standard Storage methods (`setItem`, `getItem`, `clear`, `removeItem` all undefined). The `@vitest-environment jsdom` pragma cannot override this at the global level.
|
||||
- **Fix:** Used `vi.stubGlobal('localStorage', inMemoryMock)` with a hand-rolled Storage implementation in `beforeEach`. Added `vi.stubGlobal('matchMedia', mockFn)` for the same reason (jsdom missing matchMedia).
|
||||
- **Files modified:** `src/components/ui/ThemeToggle.test.tsx`
|
||||
- **Verification:** 7 ThemeToggle tests green; all 166 tests pass
|
||||
- **Committed in:** `074dc33` (Task 2 GREEN commit)
|
||||
|
||||
**2. [Rule 1 - Bug] toBeInTheDocument() unavailable — @testing-library/jest-dom not installed**
|
||||
- **Found during:** Task 2 (ThemeToggle TDD)
|
||||
- **Issue:** Tests initially used `toBeInTheDocument()` which requires `@testing-library/jest-dom`. Existing tests in the project use only standard Vitest assertions (`toBeDefined()`, `toBeNull()`).
|
||||
- **Fix:** Replaced all `toBeInTheDocument()` calls with `toBeDefined()`, replaced all `toHaveAttribute(attr, val)` with `element.getAttribute(attr)` + `toBe(val)`.
|
||||
- **Files modified:** `src/components/ui/ThemeToggle.test.tsx`
|
||||
- **Verification:** Tests pass without adding any new dependency
|
||||
- **Committed in:** `074dc33` (Task 2 GREEN commit)
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 2 auto-fixed (both Rule 1 - bugs in test infrastructure)
|
||||
**Impact on plan:** Both fixes necessary to get tests green in this environment. No scope creep. Component implementation is unaffected.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
- Node v25 experimental WebStorage is a new environment quirk not documented in the research. The workaround (`vi.stubGlobal`) is clean and consistent with how other projects handle this in Node 22+.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None — no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- Token system ready: `bg-surface`, `text-on-surface`, `bg-primary`, `text-on-primary`, `border-outline`, etc. are available as Tailwind utility classes
|
||||
- Dark mode working: `.dark` class on `<html>` cascades all 15 token values automatically
|
||||
- FOUC prevention in place: inline script reads `r2b-theme` before first CSS paint
|
||||
- Phase 9 (component migration) can proceed: all 10 files with 73 hardcoded color classes ready for mechanical find-and-replace migration using Pattern 4 from 08-RESEARCH.md
|
||||
- No blockers
|
||||
|
||||
---
|
||||
*Phase: 08-theme-foundation*
|
||||
*Completed: 2026-04-01*
|
||||
Reference in New Issue
Block a user