* 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>
21 KiB
ResolutionFlow — Tailwind v4 Migration & Feature Guide
Claude Code Handoff Document · Pre-Investor Pitch Sprint
Purpose
This document gives Claude Code everything needed to:
- Execute the mechanical Tailwind v3 → v4 upgrade
- 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:
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:
# Run from /frontend
npx @tailwindcss/upgrade@latest
This tool will:
- Update
tailwindcssto v4 - Install
@tailwindcss/vite(replaces the PostCSS plugin) - Migrate
tailwind.config.js→ CSS@themeblock - Update
@tailwinddirectives to@import - Handle renamed utility classes in template files
After running, verify package.json reflects:
// 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:
// 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:
/* 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'fromApp.tsxafter adding it here. Important: RemovedarkMode: ["class"]fromtailwind.config.js— it is now handled by@custom-variant darkabove. Since ResolutionFlow is dark-only with no light mode toggle, verify that the.darkclass is still present on the<html>element inindex.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
@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
@keyframesblocks fromindex.cssafter 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:
/* 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:
// 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:
// 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
@themeinstead 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.
# 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:
forwardRefremoved from all components (React 19 handles ref natively)data-slotattributes added for easier CSS targeting- HSL colors converted to OKLCH for wider color gamut
- Dark mode colors revisited for better accessibility
tailwindcss-animatedeprecated in favor oftw-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-200tocurrentColor. Check all card, table, and input borders against thehsl(var(--border))token. - Focus rings — default ring changed from
3px blueto1px currentColor. Verify form fields and button focus states look intentional. - Placeholder text — changed from
gray-400tocurrentColorat 50% opacity. Check all inputs. - Button cursors — now
cursor:default(browser standard). Addcursor-pointerexplicitly 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
breatheGlowon stat cards,bellWobbleon notification bell, and allfade-in-*utilities still work after moving keyframes into@theme. - Cyan brand gradient — verify
bg-gradient-brandstill renders correctly. Check it looks at least as good (ideally better) with OKLCH values. - Atmosphere orbs — verify
AppLayout.tsxorbs render correctly after inline style replacement with OKLCH variables. - Glass morphism — verify
.glass-cardand.glass-card-staticbackdrop 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
.darkclass on<html>is present and the@custom-variant darkreplacement behaves identically to the olddarkMode: ["class"]config.
Commit Strategy
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
// 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
/* 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);
}
}
// 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
// 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
// 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.
// 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.
// 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.
// 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.
// 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.
// 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
- Use
field-sizing-contenton every<textarea>— no exceptions - Use
@starting-styleanimations instead of adding/removing CSS classes for enter effects - Wrap repeated components in
@containerwhen they appear in variable-width contexts - Use
not-last:instead of custom:not(:last-child)selectors in lists - Reference
var(--color-brand-from)andvar(--color-brand-to)in JS instead of hardcoded#06b6d4orrgba(6,182,212,...) - Use OKLCH for any new color values —
oklch(72% 0.15 195)not#06b6d4 - All new keyframe animations go inside
@theme— not loose inindex.css - Do not use
tailwindcss-animate— replaced bytw-animate-css - Do not add
@tailwind base/components/utilitiesdirectives — replaced by@import "tailwindcss" - Do not add
darkMode: ["class"]to any config — replaced by@custom-variant dark - Prefer
bg-linear-135syntax overbg-gradient-to-rfor the brand gradient — more explicit and v4-native
ResolutionFlow · Tailwind v4 Migration Doc · Pre-Investor Pitch Sprint