chore: Tailwind CSS v3 → v4 migration (#99)

* chore: run Tailwind v4 upgrade tool (Phase 1)

- Upgraded tailwindcss v3 → v4.2.1, postcss plugin to @tailwindcss/postcss
- Deleted tailwind.config.js, migrated theme to CSS @theme block in index.css
- Replaced @tailwind directives with @import 'tailwindcss'
- Added @custom-variant dark, @utility blocks for custom utilities
- Updated class names across 128 files (shadow-sm → shadow-xs, etc.)
- Removed autoprefixer (built into v4)
- Added migration plan doc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: switch from @tailwindcss/postcss to @tailwindcss/vite (Phase 2)

- Replaced @tailwindcss/postcss with @tailwindcss/vite plugin
- Deleted postcss.config.js (no longer needed)
- Tailwind now runs as a native Vite plugin for faster HMR

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: convert to OKLCH colors, move keyframes into @theme (Phase 3-4)

- Replaced all HSL color indirection with direct OKLCH values in @theme
- Moved all keyframes inside @theme block (v4 pattern)
- Eliminated hsl(var(--x)) double-indirection across 17 component files
- Replaced hsl() inline styles with var(--color-*) theme references
- Cleaned up redundant rdp-* utility blocks
- Fixed @custom-variant dark syntax to use :where()
- Added sidebar/glass/shadow vars as OKLCH in :root

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #99.
This commit is contained in:
chihlasm
2026-03-07 22:10:44 -05:00
committed by GitHub
parent 732ccba966
commit d365c38b61
137 changed files with 1922 additions and 1709 deletions

View File

@@ -0,0 +1,633 @@
# ResolutionFlow — Tailwind v4 Migration & Feature Guide
**Claude Code Handoff Document · Pre-Investor Pitch Sprint**
---
## Purpose
This document gives Claude Code everything needed to:
1. Execute the mechanical Tailwind v3 → v4 upgrade
2. Know which new v4 features to use going forward so the ResolutionFlow UI looks and feels premium for the investor pitch demo
## Branch
This migration touches nearly every file in the frontend. **Always work on a dedicated branch:**
```bash
git checkout -b feat/tailwind-v4-upgrade
```
Do not merge to `main` until all phases are complete and the full visual QA pass is done.
---
## Stack Context
| | |
|---|---|
| Frontend | React 19.2 + Vite 7 + TypeScript 5.9 |
| Current Tailwind | v3.4.19 |
| Component Library | shadcn/ui (target: new-york style post-upgrade) |
| Canvas Editor | React Flow (@xyflow/react) |
| Brand Color | Cyan — `#06b6d4 → #22d3ee` (hsl 187 72% 43%) |
| Background | `hsl(228 12% 7%)` — dark theme only, no light mode |
| Body Font | IBM Plex Sans |
| Heading Font | Bricolage Grotesque |
| Label/Mono Font | JetBrains Mono |
| Brand Gradient | `linear-gradient(135deg, #06b6d4 0%, #22d3ee 100%)` |
| Animation Library | tailwindcss-animate → **replace with tw-animate-css** |
| Tailwind Plugins | None |
---
---
# PART 1: Mechanical Migration
Execute phases in order. Verify the app builds and renders correctly after each phase before proceeding.
---
## Phase 1 — Update Dependencies
Run the official Tailwind upgrade tool from the `/frontend` directory. It handles the majority of migration automatically:
```bash
# Run from /frontend
npx @tailwindcss/upgrade@latest
```
This tool will:
- Update `tailwindcss` to v4
- Install `@tailwindcss/vite` (replaces the PostCSS plugin)
- Migrate `tailwind.config.js` → CSS `@theme` block
- Update `@tailwind` directives to `@import`
- Handle renamed utility classes in template files
After running, verify `package.json` reflects:
```json
// devDependencies should now include:
"tailwindcss": "^4.x.x",
"@tailwindcss/vite": "^4.x.x"
// These are no longer needed and can be removed:
// "autoprefixer" (bundled in v4 via Lightning CSS)
// "postcss" (unless used for something other than Tailwind)
```
---
## Phase 2 — Update Vite Config
Replace the PostCSS-based Tailwind setup with the first-party Vite plugin:
```ts
// vite.config.ts — BEFORE
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})
// vite.config.ts — AFTER
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
})
```
Remove `postcss.config.js` if it exists and was only used for Tailwind. Lightning CSS is now bundled inside Tailwind v4.
---
## Phase 3 — Migrate CSS Entry Point
Update `index.css`:
```css
/* BEFORE — v3 directives */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* AFTER — v4 single import */
@import "tailwindcss";
@import "tw-animate-css";
/* ResolutionFlow is dark-only. Replace darkMode: ["class"] from tailwind.config.js
with this custom variant declaration. This is the v4 equivalent. */
@custom-variant dark (&:where(.dark, .dark *));
/* React Flow styles must now live in CSS, not imported in App.tsx */
@layer base {
@import "@xyflow/react/dist/style.css";
}
```
> **Important:** Remove `import '@xyflow/react/dist/style.css'` from `App.tsx` after adding it here.
> **Important:** Remove `darkMode: ["class"]` from `tailwind.config.js` — it is now handled by `@custom-variant dark` above. Since ResolutionFlow is dark-only with no light mode toggle, verify that the `.dark` class is still present on the `<html>` element in `index.html`.
---
## Phase 4 — Migrate Theme Configuration
The upgrade tool migrates `tailwind.config.js` automatically, but verify the ResolutionFlow design tokens are correctly expressed. The `@theme` block lives in `index.css` after the imports.
### OKLCH Color Values
Claude Code flagged that the cyan range benefits from OKLCH for better gradient interpolation. Use these OKLCH equivalents for brand colors inside `@theme` — they render more accurately on wide-gamut displays (MacBooks, modern monitors used in pitch settings):
| Hex | OKLCH |
|---|---|
| `#06b6d4` (brand-from) | `oklch(72% 0.15 195)` |
| `#22d3ee` (brand-to) | `oklch(82% 0.13 195)` |
| `#0891b2` (brand-dark) | `oklch(62% 0.14 195)` |
### Full `@theme` Block
```css
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
/* ResolutionFlow Brand — Cyan system (OKLCH for wide-gamut accuracy) */
--color-brand-from: oklch(72% 0.15 195); /* #06b6d4 */
--color-brand-to: oklch(82% 0.13 195); /* #22d3ee */
--color-brand-dark: oklch(62% 0.14 195); /* #0891b2 */
/* Dark surface palette */
--color-dark-DEFAULT: #101114;
--color-dark-card: #14161a;
--color-dark-surface: #14161a;
/* Text palette */
--color-text-primary: #f8fafc;
--color-text-secondary: #8891a0;
--color-text-muted: #5a6170;
/* Typography */
--font-sans: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;
--font-heading: 'Bricolage Grotesque', system-ui, sans-serif;
--font-label: 'JetBrains Mono', monospace;
/* Border radius */
--radius-lg: 0.75rem;
--radius-md: calc(0.75rem - 2px);
--radius-sm: calc(0.75rem - 4px);
/* Keyframe animations — move ALL @keyframes from index.css into @theme */
--animate-fade-in: fade-in 200ms ease-out;
--animate-fade-in-up: fade-in-up 200ms ease-out;
--animate-slide-in-left: slide-in-from-left 200ms ease-out;
--animate-slide-in-bottom: slide-in-from-bottom 200ms ease-out;
--animate-scale-in: scale-in 150ms ease-out;
--animate-breathe-glow: breatheGlow 3s ease-in-out infinite alternate;
--animate-bell-wobble: bellWobble 0.5s ease-in-out;
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slide-in-from-left {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
@keyframes slide-in-from-bottom {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes scale-in {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes breatheGlow {
from { box-shadow: 0 8px 32px rgba(0,0,0,0.3), 0 0 20px oklch(72% 0.15 195 / 0.04); }
to { box-shadow: 0 8px 32px rgba(0,0,0,0.3), 0 0 30px oklch(72% 0.15 195 / 0.12); }
}
@keyframes bellWobble {
0% { transform: rotate(0deg); }
20% { transform: rotate(8deg); }
40% { transform: rotate(-6deg); }
60% { transform: rotate(4deg); }
80% { transform: rotate(-2deg); }
100% { transform: rotate(0deg); }
}
}
```
> **Important:** Remove the loose `@keyframes` blocks from `index.css` after moving them into `@theme`. They should not exist in both places.
### shadcn/ui CSS Variable Bridge
Keep the existing `:root` block as-is — shadcn still uses HSL format for its variables:
```css
/* shadcn/ui CSS variable bridge — keep as-is from existing index.css */
@layer base {
:root {
--background: 228 12% 7%;
--foreground: 210 40% 98%;
--card: 220 10% 10%;
--card-foreground: 210 40% 98%;
--popover: 220 10% 10%;
--popover-foreground: 210 40% 98%;
--primary: 187 72% 43%;
--primary-foreground: 228 12% 7%;
--secondary: 220 8% 14%;
--secondary-foreground: 210 40% 98%;
--muted: 220 8% 14%;
--muted-foreground: 215 10% 58%;
--accent: 220 8% 14%;
--accent-foreground: 210 40% 98%;
--destructive: 350 81% 55%;
--destructive-foreground: 210 40% 98%;
--border: 220 8% 14%;
--input: 220 8% 14%;
--ring: 187 72% 43%;
--radius: 0.75rem;
/* ... rest of existing tokens ... */
}
}
---
## Phase 5 Replace Animation Library
```bash
# Remove old plugin
npm uninstall tailwindcss-animate
# Install replacement
npm install -D tw-animate-css
```
Remove any `@plugin 'tailwindcss-animate'` references from CSS files. The `@import "tw-animate-css"` added in Phase 3 replaces it.
---
## Phase 7 — Consolidate Hardcoded Inline Colors
Claude Code identified **128 instances of `style={{}}` inline color values across 44 files**, including atmosphere orbs in `AppLayout.tsx` using hardcoded `rgba(6,182,212,...)`. Now that brand colors are proper CSS variables in `@theme`, replace all hardcoded cyan/brand references with variables.
**Search and replace targets:**
```ts
// FIND these hardcoded patterns:
rgba(6, 182, 212, ...) // brand-from with opacity
rgba(34, 211, 238, ...) // brand-to with opacity
#06b6d4 // brand-from hex
#22d3ee // brand-to hex
#0891b2 // brand-dark hex
// REPLACE with CSS variable equivalents:
oklch(72% 0.15 195 / 0.XX) // brand-from with opacity (XX = your alpha)
oklch(82% 0.13 195 / 0.XX) // brand-to with opacity
var(--color-brand-from) // brand-from solid
var(--color-brand-to) // brand-to solid
var(--color-brand-dark) // brand-dark solid
```
**Specifically in `AppLayout.tsx`** — atmosphere orbs should become:
```tsx
// BEFORE
style={{ background: 'rgba(6, 182, 212, 0.08)' }}
// AFTER — references the theme token, respects any future brand color changes
style={{ background: 'oklch(72% 0.15 195 / 0.08)' }}
// Or better yet, move to a CSS utility class entirely:
// .atmosphere-orb { background: oklch(72% 0.15 195 / 0.08); }
```
> This is the right time to do this cleanup — the theme is already being touched, and it means future brand color changes only require updating `@theme` instead of hunting through 44 files.
---
## Phase 8 — Reinstall shadcn/ui Components
shadcn/ui components have been updated for Tailwind v4 and React 19. Commit any custom component modifications first, then reinstall to get refreshed versions.
```bash
# Re-initialize with v4 defaults
npx shadcn@latest init
# When prompted:
# Style: new-york (more refined than 'default')
# Base color: cyan (matches ResolutionFlow brand)
# Reinstall actively used components:
npx shadcn@latest add button card dialog dropdown-menu
npx shadcn@latest add badge input label select textarea
npx shadcn@latest add table tabs tooltip separator
npx shadcn@latest add sheet command popover
```
**What's new in shadcn v4 components:**
- `forwardRef` removed from all components (React 19 handles ref natively)
- `data-slot` attributes added for easier CSS targeting
- HSL colors converted to OKLCH for wider color gamut
- Dark mode colors revisited for better accessibility
- `tailwindcss-animate` deprecated in favor of `tw-animate-css`
---
## Phase 9 — Visual QA Checklist
Work through the app after Phases 16. These are the most likely regression areas:
- [ ] **Borders** — default border color changed from `gray-200` to `currentColor`. Check all card, table, and input borders against the `hsl(var(--border))` token.
- [ ] **Focus rings** — default ring changed from `3px blue` to `1px currentColor`. Verify form fields and button focus states look intentional.
- [ ] **Placeholder text** — changed from `gray-400` to `currentColor` at 50% opacity. Check all inputs.
- [ ] **Button cursors** — now `cursor:default` (browser standard). Add `cursor-pointer` explicitly where desired.
- [ ] **React Flow canvas** — verify nodes, handles, edges, and controls render correctly with CSS import moved to `@layer base`.
- [ ] **Animations** — check dialog open/close, sheet slide, dropdown appear animations after the animation library swap.
- [ ] **Keyframe animations** — verify `breatheGlow` on stat cards, `bellWobble` on notification bell, and all `fade-in-*` utilities still work after moving keyframes into `@theme`.
- [ ] **Cyan brand gradient** — verify `bg-gradient-brand` still renders correctly. Check it looks at least as good (ideally better) with OKLCH values.
- [ ] **Atmosphere orbs** — verify `AppLayout.tsx` orbs render correctly after inline style replacement with OKLCH variables.
- [ ] **Glass morphism** — verify `.glass-card` and `.glass-card-static` backdrop blur still applies on all surfaces.
- [ ] **JetBrains Mono** — verify label/code elements still use mono font correctly.
- [ ] **Sonner toasts** — verify custom toast styling (card bg, border, icon colors) still applies.
- [ ] **Dark variant** — verify `.dark` class on `<html>` is present and the `@custom-variant dark` replacement behaves identically to the old `darkMode: ["class"]` config.
---
## Commit Strategy
```bash
git checkout -b feat/tailwind-v4-upgrade
git commit -m "chore: upgrade Tailwind v3 → v4, update Vite plugin"
git commit -m "chore: migrate CSS entry point — @import, @custom-variant dark"
git commit -m "chore: migrate theme to @theme block — OKLCH colors, keyframes"
git commit -m "chore: swap tailwindcss-animate for tw-animate-css"
git commit -m "chore: consolidate 128 hardcoded inline colors to CSS variables"
git commit -m "chore: reinstall shadcn/ui components for v4 (new-york style, cyan)"
git commit -m "fix: visual QA pass — borders, rings, placeholders, glass, animations"
git commit -m "feat: apply v4 feature enhancements across components"
# When complete and QA passes:
git checkout main
git merge feat/tailwind-v4-upgrade
```
---
---
# PART 2: Tailwind v4 Feature Guide
**These are new capabilities available after the upgrade. Apply them proactively when building or modifying components — do not default to v3 patterns when a v4 equivalent is listed here.**
---
## Feature 1 — Auto-Resizing Textareas (`field-sizing-content`)
Apply to every multi-line text input in the app. Engineers typing session notes and documentation should never fight a fixed-height box.
**Where to apply:** Session runner documentation field · step notes input · FlowPilot prompt input · any multi-line input in the flow editor
```tsx
// BEFORE — v3, fixed height or JS resize logic
<textarea
className="w-full resize-none h-32 rounded-md border p-3"
/>
// AFTER — v4, grows automatically as the engineer types
<textarea
className="w-full field-sizing-content min-h-[80px] max-h-[400px]
rounded-md border border-input bg-background p-3
text-sm focus-visible:ring-2 focus-visible:ring-ring"
/>
```
---
## Feature 2 — CSS-Native Enter Animations (`@starting-style`)
Use for elements that appear in the session runner — step transitions, panel reveals, status banners. No JavaScript animation library needed for these patterns.
**Where to apply:** Step cards appearing in session runner · status/alert banners · completion state reveals · FlowPilot suggestion panels
```css
/* index.css — define the utility */
@utility fade-in-up {
transition: opacity 300ms ease, transform 300ms ease;
opacity: 1;
transform: translateY(0);
@starting-style {
opacity: 0;
transform: translateY(8px);
}
}
@utility scale-in {
transition: opacity 150ms ease, transform 150ms ease;
opacity: 1;
transform: scale(1);
@starting-style {
opacity: 0;
transform: scale(0.97);
}
}
```
```tsx
// Component usage — animates in on mount with no JS
<div className="fade-in-up">
<StepCard step={currentStep} />
</div>
<div className="scale-in">
<FlowPilotPanel />
</div>
```
---
## Feature 3 — Container Queries (`@container`)
Use for components that need to adapt based on where they are rendered — not the viewport. Critical for ResolutionFlow where the same component may appear in a sidebar, modal, or full-width panel.
**Where to apply:** StepCard (sidebar vs full-width) · FlowPilot panel (collapsed vs expanded) · documentation preview panel · step library items
```tsx
// Wrap the parent with @container
<div className="@container w-full">
<StepCard step={step} />
</div>
// Inside StepCard — responds to its container, not the viewport
function StepCard({ step }: { step: Step }) {
return (
<div className="
flex flex-col gap-2
@sm:flex-row @sm:items-center
@lg:gap-4
p-4 rounded-lg border border-border bg-card
">
<StepIcon type={step.type} />
<StepContent step={step} />
<div className="@sm:ml-auto">
<StepActions step={step} />
</div>
</div>
)
}
```
---
## Feature 4 — Enhanced Gradient APIs
The cyan brand gradient is ResolutionFlow's primary visual identity. v4 unlocks radial gradients, angle control, and OKLCH interpolation for richer rendering.
**Where to apply:** Hero/header sections · card hover states · active step indicators · FlowPilot branding elements · progress indicators
```tsx
// Linear — explicit angle control (v4)
<div className="bg-linear-135 from-[#06b6d4] to-[#22d3ee]">
// Radial — great for glow effects on active/highlighted elements (v4)
<div className="bg-radial from-[#06b6d4]/20 to-transparent">
// Radial with position — spotlight effect (v4)
<div className="bg-radial-[at_30%_50%] from-[#06b6d4]/15 via-[#22d3ee]/8 to-transparent">
// OKLCH interpolation — richer, more accurate gradient transition (v4)
<div className="bg-linear-to-r from-[#06b6d4] to-[#22d3ee] [color-interpolation-method:oklch]">
// Combine with glass card for the premium demo look
<div className="glass-card-static bg-radial-[at_top_left] from-[#06b6d4]/10 to-transparent">
```
---
## Feature 5 — Dynamic Utility Values
In v3, non-standard values required bracket notation. In v4 the scale is continuous — use values directly.
```tsx
// v3 — arbitrary brackets for anything off-scale
<div className="w-[18px] h-[18px] z-[60] grid-cols-[repeat(7,1fr)]">
// v4 — direct values work without brackets
<div className="w-4.5 h-4.5 z-60 grid-cols-7">
// Grid columns support any number directly
<div className="grid grid-cols-7"> // 7-column grid, no config needed
<div className="grid grid-cols-15"> // 15-column, works out of the box
```
---
## Feature 6 — `not-*` Variant
Style elements only when they don't match a condition. Useful in the session runner step list.
```tsx
// Bottom border on all steps except the last
<div className="not-last:border-b border-border pb-4 mb-4">
<StepCard />
</div>
// Dim inactive steps
<div className="not-[.active]:opacity-60 not-[.active]:hover:opacity-80 transition-opacity">
<StepCard />
</div>
```
---
## Feature 7 — Theme Tokens as Native CSS Variables
After the v4 migration, all `@theme` tokens are exposed as native CSS custom properties. This means you can access ResolutionFlow design tokens in TypeScript — useful for React Flow node styling and Recharts chart colors.
```ts
// Access brand colors in JS/TS — no more hardcoded hex values
const style = getComputedStyle(document.documentElement)
const brandColor = style.getPropertyValue('--color-brand-from').trim() // '#06b6d4'
// Use directly in React Flow node styles
const nodeStyles = {
background: 'var(--color-dark-card)',
border: '1px solid var(--color-brand-from)',
boxShadow: '0 0 12px rgba(6, 182, 212, 0.15)',
}
// Use in Recharts chartConfig — no more hsl() wrapper needed
const chartConfig = {
sessions: {
label: 'Sessions',
color: 'var(--color-brand-from)',
},
resolved: {
label: 'Resolved',
color: 'var(--color-brand-to)',
},
}
```
---
## Feature 8 — Color Scheme Utilities
Control native browser UI element theming (scrollbars, form controls) to match the dark theme — previously required custom CSS.
```tsx
// Apply to the root html element in index.html or App.tsx
<html className="scheme-dark">
// This makes native browser scrollbars, select dropdowns,
// date inputs, and form controls render in dark mode automatically.
// ResolutionFlow is dark-only so this should be applied globally.
```
---
## Feature 9 — `inert` Utility
Disable interaction on an entire subtree without JavaScript. Useful for locking the flow editor or step list during FlowPilot AI generation.
```tsx
// Disable the step list while FlowPilot is generating
<div className={isGenerating ? 'inert' : ''}>
<StepList steps={steps} />
</div>
// The inert attribute disables all pointer events, focus, and
// accessibility interaction on the entire subtree at once.
```
---
## General Rules for v4 Going Forward
1. **Use `field-sizing-content` on every `<textarea>`** — no exceptions
2. **Use `@starting-style` animations** instead of adding/removing CSS classes for enter effects
3. **Wrap repeated components in `@container`** when they appear in variable-width contexts
4. **Use `not-last:` instead of custom `:not(:last-child)` selectors** in lists
5. **Reference `var(--color-brand-from)` and `var(--color-brand-to)`** in JS instead of hardcoded `#06b6d4` or `rgba(6,182,212,...)`
6. **Use OKLCH for any new color values**`oklch(72% 0.15 195)` not `#06b6d4`
7. **All new keyframe animations go inside `@theme`** — not loose in `index.css`
8. **Do not use `tailwindcss-animate`** — replaced by `tw-animate-css`
9. **Do not add `@tailwind base/components/utilities` directives** — replaced by `@import "tailwindcss"`
10. **Do not add `darkMode: ["class"]` to any config** — replaced by `@custom-variant dark`
11. **Prefer `bg-linear-135` syntax** over `bg-gradient-to-r` for the brand gradient — more explicit and v4-native
---
*ResolutionFlow · Tailwind v4 Migration Doc · Pre-Investor Pitch Sprint*