Lay the foundation for migrating OmniVoice's UI to clean Tailwind v4 + shadcn/ui
WITHOUT changing the look: shadcn primitives inherit the existing OmniVoice
palette (Gruvbox-pink default + every [data-theme] variant) through a semantic
token bridge. Foundation only — no existing component is replaced.
What landed:
- shadcn init for Tailwind v4 + Vite + React 19: frontend/components.json
(new-york, rsc:false, tsx:true), src/lib/utils.ts (cn = clsx + tailwind-merge),
and a @/* -> src/* alias in vite.config.js + tsconfig.json so future
`npx shadcn add` resolves.
- Token bridge in src/index.css: a single `@theme inline` block maps shadcn's
semantic vocab (--color-background/-foreground/-card/-popover/-primary/
-secondary/-muted/-muted-foreground/-accent-foreground/-destructive/-input/
-ring + --radius) onto the existing OmniVoice --color-* tokens. Because those
tokens are re-declared per theme in ui/themes.css, theme switching recolors
shadcn components automatically — no per-theme shadcn block. Existing
--color-accent/--color-border and the --radius-* scale are left intact.
- Two proof components: src/components/ui/button.tsx + input.tsx (verbatim
shadcn new-york), rendered across default/midnight/catppuccin in the visual
harness with committed baselines (brand-pink / purple / lavender confirmed).
- New deps: class-variance-authority, clsx, tailwind-merge, tw-animate-css,
@radix-ui/react-slot. Root bun.lock regenerated; `bun install
--frozen-lockfile` verified in sync (Docker-green).
- Migration plan at docs/shadcn-migration.md (bridge table, primitive->shadcn
mapping, prop-compat wrapper strategy, staged waves, honest risk/effort).
Verified: vite build, typecheck:ci, oxlint (0 errors), oxfmt --check, vitest
(641 pass), test:visual (48 pass incl. 6 new baselines), frozen lockfile in sync.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>