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:
@@ -39,7 +39,7 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
||||
const allHandled = pendingFixes.length === 0
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs p-4">
|
||||
<div className="relative flex h-[80vh] w-full max-w-2xl flex-col bg-card border border-border rounded-2xl shadow-lg">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
||||
@@ -127,7 +127,7 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
||||
<div className="mt-3 flex gap-2">
|
||||
<button
|
||||
onClick={() => handleApply(fix)}
|
||||
className="flex items-center gap-1 rounded-md bg-gradient-brand px-3 py-1.5 text-xs font-medium text-white shadow-sm shadow-primary/20 hover:opacity-90"
|
||||
className="flex items-center gap-1 rounded-md bg-gradient-brand px-3 py-1.5 text-xs font-medium text-white shadow-xs shadow-primary/20 hover:opacity-90"
|
||||
>
|
||||
<Check className="h-3 w-3" />
|
||||
Apply
|
||||
|
||||
@@ -139,14 +139,14 @@ function FlowCanvasInner({ selectedNodeId, onNodeSelect, onSelectAnswerType, onN
|
||||
proOptions={{ hideAttribution: true }}
|
||||
className="dark bg-accent/30"
|
||||
>
|
||||
<Background variant={BackgroundVariant.Dots} gap={20} size={1.5} color="hsl(var(--muted-foreground) / 0.25)" />
|
||||
<Controls showInteractive={false} className="!bg-card !border-border !shadow-lg" />
|
||||
<Background variant={BackgroundVariant.Dots} gap={20} size={1.5} color="oklch(0.63 0.02 260 / 0.25)" />
|
||||
<Controls showInteractive={false} className="bg-card! border-border! shadow-lg!" />
|
||||
{minimapVisible && (
|
||||
<MiniMap
|
||||
pannable
|
||||
zoomable
|
||||
nodeColor={minimapNodeColor}
|
||||
className="!bg-card !border-border"
|
||||
className="bg-card! border-border!"
|
||||
nodeStrokeWidth={2}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -16,7 +16,7 @@ function FlowCanvasAnswerNodeComponent({ data, selected }: NodeProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={Position.Top} className="!bg-border !w-2 !h-2 !border-0" />
|
||||
<Handle type="target" position={Position.Top} className="bg-border! w-2! h-2! border-0!" />
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
@@ -61,7 +61,7 @@ function FlowCanvasAnswerNodeComponent({ data, selected }: NodeProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Handle type="source" position={Position.Bottom} className="!bg-border !w-2 !h-2 !border-0" />
|
||||
<Handle type="source" position={Position.Bottom} className="bg-border! w-2! h-2! border-0!" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -62,15 +62,15 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
||||
return (
|
||||
<>
|
||||
{/* Target handle at top */}
|
||||
<Handle type="target" position={Position.Top} className="!bg-border !w-2 !h-2 !border-0" />
|
||||
<Handle type="target" position={Position.Top} className="bg-border! w-2! h-2! border-0!" />
|
||||
|
||||
<div
|
||||
onContextMenu={(e) => onContextMenu?.(e, node.id)}
|
||||
className={cn(
|
||||
'w-[280px] rounded-xl border border-border bg-card shadow-sm cursor-pointer transition-all',
|
||||
'w-[280px] rounded-xl border border-border bg-card shadow-xs cursor-pointer transition-all',
|
||||
config.borderClass,
|
||||
selected && 'ring-1 ring-primary shadow-md',
|
||||
isGhost && 'border-dashed !border-primary/40 opacity-60'
|
||||
isGhost && 'border-dashed border-primary/40! opacity-60'
|
||||
)}
|
||||
>
|
||||
{/* Header */}
|
||||
@@ -175,7 +175,7 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
||||
</div>
|
||||
|
||||
{/* Source handle at bottom */}
|
||||
<Handle type="source" position={Position.Bottom} className="!bg-border !w-2 !h-2 !border-0" />
|
||||
<Handle type="source" position={Position.Bottom} className="bg-border! w-2! h-2! border-0!" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export function MetadataSidePanel({ isOpen, onClose }: MetadataSidePanelProps) {
|
||||
<>
|
||||
{/* Backdrop — click to close */}
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-background/40 backdrop-blur-sm"
|
||||
className="fixed inset-0 z-40 bg-background/40 backdrop-blur-xs"
|
||||
onClick={onClose}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
@@ -62,7 +62,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
titleError ? 'border-red-400' : 'border-border'
|
||||
)}
|
||||
/>
|
||||
@@ -107,7 +107,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@@ -134,7 +134,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
className={cn(
|
||||
'block w-full rounded-md border border-border px-3 py-2 font-mono text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@@ -154,7 +154,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -195,7 +195,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
<option value="">Link to existing node...</option>
|
||||
|
||||
@@ -104,7 +104,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
questionError ? 'border-red-400' : 'border-border'
|
||||
)}
|
||||
/>
|
||||
@@ -126,7 +126,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -187,7 +187,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
className={cn(
|
||||
'block w-full rounded-md border px-3 py-2 text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary',
|
||||
optionLabelError ? 'border-red-400' : 'border-border'
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -59,7 +59,7 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
titleError ? 'border-red-400' : 'border-border'
|
||||
)}
|
||||
/>
|
||||
@@ -103,7 +103,7 @@ Document what was done and the outcome.
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@@ -134,7 +134,7 @@ Document what was done and the outcome.
|
||||
className={cn(
|
||||
'block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -184,7 +184,7 @@ function NodeListItem({
|
||||
'group flex items-center gap-1 rounded-md px-2 py-1.5 text-sm transition-colors cursor-pointer',
|
||||
isRootNode
|
||||
? isSelected
|
||||
? 'bg-blue-500/20 ring-2 ring-blue-500 shadow-sm'
|
||||
? 'bg-blue-500/20 ring-2 ring-blue-500 shadow-xs'
|
||||
: 'bg-blue-500/10 border border-blue-500/30 hover:bg-blue-500/15'
|
||||
: isSelected
|
||||
? 'bg-primary/10 ring-1 ring-primary'
|
||||
@@ -581,7 +581,7 @@ export function NodeList() {
|
||||
|
||||
{/* Add Node Type Selector */}
|
||||
{addingToParent && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm">
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-xs">
|
||||
<div className="w-full max-w-xs rounded-lg border border-border bg-card p-4 shadow-lg">
|
||||
<h3 className="mb-3 text-sm font-semibold">Select Node Type</h3>
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -167,7 +167,7 @@ export function NodePicker({
|
||||
className={cn(
|
||||
'flex-1 rounded-md border border-border px-2 py-1 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -201,7 +201,7 @@ export function NodePicker({
|
||||
className={cn(
|
||||
'block w-full rounded-md border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
error ? 'border-red-400' : 'border-border'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -64,7 +64,7 @@ interface AddNodePickerProps {
|
||||
|
||||
function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded-xl border border-dashed border-primary/40 bg-card px-3 py-2 shadow-sm">
|
||||
<div className="flex items-center gap-2 rounded-xl border border-dashed border-primary/40 bg-card px-3 py-2 shadow-xs">
|
||||
<span className="text-xs text-muted-foreground shrink-0">Add:</span>
|
||||
|
||||
<button
|
||||
@@ -691,7 +691,7 @@ export function TreeCanvas() {
|
||||
style={{
|
||||
// Subtle dot grid background
|
||||
backgroundImage:
|
||||
'radial-gradient(circle, hsl(var(--border)) 1px, transparent 1px)',
|
||||
'radial-gradient(circle, var(--color-border) 1px, transparent 1px)',
|
||||
backgroundSize: '24px 24px',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -168,7 +168,7 @@ export function TreeCanvasNode({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative rounded-xl border border-border bg-card shadow-sm transition-all duration-150',
|
||||
'relative rounded-xl border border-border bg-card shadow-xs transition-all duration-150',
|
||||
config.borderClass,
|
||||
isExpanded && 'ring-1 ring-primary shadow-md',
|
||||
isSelected && !isExpanded && 'ring-1 ring-primary/50',
|
||||
|
||||
@@ -73,7 +73,7 @@ export function TreeMetadataForm() {
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
nameError ? 'border-red-400' : 'border-border'
|
||||
)}
|
||||
/>
|
||||
@@ -94,7 +94,7 @@ export function TreeMetadataForm() {
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -112,7 +112,7 @@ export function TreeMetadataForm() {
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
<option value="">No category</option>
|
||||
@@ -134,7 +134,7 @@ export function TreeMetadataForm() {
|
||||
className={cn(
|
||||
'block min-w-0 flex-1 rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
@@ -76,7 +76,7 @@ export function ValidationSummary({ errors, onSelectNode, onFixWithAI, isFixing
|
||||
'flex items-center gap-1.5 rounded-md px-3 py-1 text-xs font-medium transition-colors',
|
||||
isFixing
|
||||
? 'bg-primary/10 text-primary cursor-wait'
|
||||
: 'bg-gradient-brand text-white shadow-sm shadow-primary/20 hover:opacity-90'
|
||||
: 'bg-gradient-brand text-white shadow-xs shadow-primary/20 hover:opacity-90'
|
||||
)}
|
||||
>
|
||||
{isFixing ? (
|
||||
@@ -109,7 +109,7 @@ export function ValidationSummary({ errors, onSelectNode, onFixWithAI, isFixing
|
||||
: 'cursor-default'
|
||||
)}
|
||||
>
|
||||
<AlertCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-red-400" />
|
||||
<AlertCircle className="mt-0.5 h-4 w-4 shrink-0 text-red-400" />
|
||||
<div className="flex-1">
|
||||
<p className="text-red-400">{error.message}</p>
|
||||
{error.nodeId && (
|
||||
@@ -133,7 +133,7 @@ export function ValidationSummary({ errors, onSelectNode, onFixWithAI, isFixing
|
||||
: 'cursor-default'
|
||||
)}
|
||||
>
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-yellow-400" />
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-yellow-400" />
|
||||
<div className="flex-1">
|
||||
<p className="text-yellow-400">{warning.message}</p>
|
||||
{warning.nodeId && (
|
||||
|
||||
@@ -155,10 +155,10 @@ export function useTreeLayout(): UseTreeLayoutResult {
|
||||
target: child.id,
|
||||
type: 'smoothstep',
|
||||
label: edgeLabel,
|
||||
labelStyle: { fill: 'hsl(var(--muted-foreground))', fontSize: 11 },
|
||||
labelBgStyle: { fill: 'hsl(var(--card))', fillOpacity: 0.9 },
|
||||
labelStyle: { fill: 'var(--color-muted-foreground)', fontSize: 11 },
|
||||
labelBgStyle: { fill: 'var(--color-card)', fillOpacity: 0.9 },
|
||||
labelBgPadding: [4, 2] as [number, number],
|
||||
style: { stroke: 'hsl(var(--border))' },
|
||||
style: { stroke: 'var(--color-border)' },
|
||||
})
|
||||
|
||||
walk(child, node.id)
|
||||
@@ -183,17 +183,17 @@ export function useTreeLayout(): UseTreeLayoutResult {
|
||||
type: 'smoothstep',
|
||||
animated: true,
|
||||
label: ref.label ? truncateLabel(ref.label) : undefined,
|
||||
labelStyle: { fill: 'hsl(var(--primary))', fontSize: 10, fontWeight: 500 },
|
||||
labelBgStyle: { fill: 'hsl(var(--card))', fillOpacity: 0.95 },
|
||||
labelStyle: { fill: 'var(--color-primary)', fontSize: 10, fontWeight: 500 },
|
||||
labelBgStyle: { fill: 'var(--color-card)', fillOpacity: 0.95 },
|
||||
labelBgPadding: [4, 2] as [number, number],
|
||||
style: {
|
||||
stroke: 'hsl(var(--primary))',
|
||||
stroke: 'var(--color-primary)',
|
||||
strokeWidth: 2,
|
||||
strokeDasharray: '6 3',
|
||||
},
|
||||
markerEnd: {
|
||||
type: 'arrowclosed' as const,
|
||||
color: 'hsl(var(--primary))',
|
||||
color: 'var(--color-primary)',
|
||||
width: 16,
|
||||
height: 16,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user