Gating prerequisite for the CSS -> Tailwind v4 migration: a pixel-for-pixel
safety net so each utility conversion can be verified against a known-good
baseline. There were previously no visual tests.
Approach: a lightweight Vite-served harness (NOT @playwright/experimental-ct-react)
that renders one presentational leaf component in isolation, with no Python
backend. Chosen because it adds zero new deps (root bun.lock untouched -> no
Docker frozen-lockfile risk), reuses the existing @playwright/test + bundled
chromium, and renders through the project's real Vite 8 + Tailwind v4 + token
pipeline so snapshots reflect the actual build output. CT's experimental React
runner on Vite 8 + React 19 was an avoidable compatibility risk.
- harness.html / harness.jsx: isolated render target driven by ?component=&theme=
URL params; applies themes via [data-theme] (default = bare :root Gruvbox),
loads the same fonts + token layers as the app, signals font-ready for stable
shots.
- specs.jsx: registry of pure variant spreads for Badge, Button, Panel,
SettingRow, SettingsToggle.
- manifest.ts: COMPONENTS x THEMES (default, midnight, catppuccin) the spec
iterates -> 15 committed baselines in __screenshots__/.
- playwright.visual.config.ts: dedicated config (separate from e2e), own Vite
server on port 3902, animations disabled, caret hidden.
- scripts: test:visual / test:visual:update.
- README: how to add a component, how to update baselines after an intentional
change, and why this stays local/manual (font/anti-alias differences across
OSes) rather than a blocking CI gate for now.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>