# 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 1–6. 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*
