@toolr/ui-design 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/components/lib/theme-engine.ts +130 -0
  2. package/components/sections/ai-tools-paths/tools-paths-panel.tsx +11 -11
  3. package/components/sections/captured-issues/captured-issues-panel.tsx +20 -20
  4. package/components/sections/golden-snapshots/file-diff-viewer.tsx +19 -19
  5. package/components/sections/golden-snapshots/golden-sync-panel.tsx +3 -3
  6. package/components/sections/golden-snapshots/snapshot-manager.tsx +15 -15
  7. package/components/sections/golden-snapshots/status-overview.tsx +40 -40
  8. package/components/sections/golden-snapshots/version-manager.tsx +10 -10
  9. package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +11 -11
  10. package/components/sections/prompt-editor/simulator-prompt-editor.tsx +15 -15
  11. package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +19 -19
  12. package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +10 -10
  13. package/components/sections/snapshot-browser/snapshot-tree.tsx +11 -11
  14. package/components/sections/snippets-editor/snippets-editor.tsx +24 -24
  15. package/components/settings/SettingsHeader.tsx +78 -0
  16. package/components/settings/SettingsPanel.tsx +21 -0
  17. package/components/settings/SettingsTreeNav.tsx +256 -0
  18. package/components/settings/index.ts +7 -0
  19. package/components/settings/settings-tree-utils.ts +120 -0
  20. package/components/ui/breadcrumb.tsx +16 -4
  21. package/components/ui/cookie-consent.tsx +82 -0
  22. package/components/ui/file-tree.tsx +5 -5
  23. package/components/ui/filter-dropdown.tsx +4 -4
  24. package/components/ui/form-actions.tsx +1 -1
  25. package/components/ui/label.tsx +31 -3
  26. package/components/ui/resizable-textarea.tsx +2 -2
  27. package/components/ui/segmented-toggle.tsx +17 -4
  28. package/components/ui/select.tsx +3 -3
  29. package/components/ui/sort-dropdown.tsx +2 -2
  30. package/components/ui/status-card.tsx +1 -1
  31. package/components/ui/tooltip.tsx +2 -2
  32. package/dist/index.d.ts +97 -4
  33. package/dist/index.js +1184 -608
  34. package/dist/tokens/primitives.css +10 -8
  35. package/dist/tokens/semantic.css +5 -0
  36. package/index.ts +20 -0
  37. package/package.json +1 -1
  38. package/tokens/primitives.css +10 -8
  39. package/tokens/semantic.css +5 -0
@@ -0,0 +1,130 @@
1
+ import type { FormColor } from './form-colors.ts'
2
+
3
+ /* ── Neutral scale types ──────────────────────────────────── */
4
+
5
+ export const SCALE_KEYS = ['black', '950', '900', '850', '800', '750', '700', '600', '500', '400', '300', '200'] as const
6
+ export type ScaleKey = (typeof SCALE_KEYS)[number]
7
+
8
+ /* ── Theme definitions ────────────────────────────────────── */
9
+
10
+ export type ThemeId = 'dark' | 'light'
11
+
12
+ export const DARK_THEMES: ThemeId[] = ['dark']
13
+ export const LIGHT_THEMES: ThemeId[] = ['light']
14
+
15
+ export const BASE_THEMES: Record<ThemeId, { label: string; maxSat: number; lightness: Record<ScaleKey, number> }> = {
16
+ dark: { label: 'Dark', maxSat: 22, lightness: { black: 0, '950': 5, '900': 10, '850': 12, '800': 15, '750': 18, '700': 22, '600': 30, '500': 46, '400': 64, '300': 83, '200': 90 } },
17
+ light: { label: 'Light', maxSat: 15, lightness: { black: 98, '950': 96, '900': 93, '850': 91, '800': 88, '750': 85, '700': 82, '600': 65, '500': 50, '400': 38, '300': 22, '200': 12 } },
18
+ }
19
+
20
+ /* ── Accent definitions ───────────────────────────────────── */
21
+
22
+ export interface AccentDef {
23
+ id: FormColor
24
+ hue: number | null
25
+ dotColor: string
26
+ }
27
+
28
+ export const ACCENT_DEFS: AccentDef[] = [
29
+ { id: 'blue', hue: 220, dotColor: '#3b82f6' },
30
+ { id: 'violet', hue: 260, dotColor: '#8b5cf6' },
31
+ { id: 'orange', hue: 25, dotColor: '#f97316' },
32
+ { id: 'cyan', hue: 185, dotColor: '#06b6d4' },
33
+ { id: 'emerald', hue: 155, dotColor: '#10b981' },
34
+ { id: 'amber', hue: 38, dotColor: '#f59e0b' },
35
+ { id: 'sky', hue: 200, dotColor: '#0ea5e9' },
36
+ { id: 'indigo', hue: 235, dotColor: '#6366f1' },
37
+ { id: 'purple', hue: 275, dotColor: '#a855f7' },
38
+ { id: 'green', hue: 142, dotColor: '#22c55e' },
39
+ { id: 'yellow', hue: 55, dotColor: '#eab308' },
40
+ { id: 'red', hue: 0, dotColor: '#ef4444' },
41
+ { id: 'neutral', hue: null, dotColor: '#737373' },
42
+ ]
43
+
44
+ /* ── Color utilities ──────────────────────────────────────── */
45
+
46
+ export function satCurve(l: number): number {
47
+ return Math.min(l / 10, 1) * Math.max(0, 1 - l / 115)
48
+ }
49
+
50
+ export function hslToHex(h: number, s: number, l: number): string {
51
+ const a = (s / 100) * Math.min(l / 100, 1 - l / 100)
52
+ const f = (n: number) => {
53
+ const k = (n + h / 30) % 12
54
+ const c = l / 100 - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)
55
+ return Math.round(255 * Math.max(0, Math.min(1, c))).toString(16).padStart(2, '0')
56
+ }
57
+ return `#${f(0)}${f(8)}${f(4)}`
58
+ }
59
+
60
+ function alphaToHex(alpha: number): string {
61
+ return Math.round(Math.min(alpha, 1) * 255).toString(16).padStart(2, '0')
62
+ }
63
+
64
+ /* ── Dim (glass surfaces) ────────────────────────────────── */
65
+
66
+ export const SURFACE_KEYS = ['950', '900', '850', '800', '750'] as const
67
+ export type SurfaceKey = (typeof SURFACE_KEYS)[number]
68
+
69
+ export const DEFAULT_DIMS: Record<SurfaceKey, number> = { '950': 1, '900': 2, '850': 3, '800': 4, '750': 5 }
70
+ export const DEFAULT_OUTLINE = 30
71
+
72
+ /* ── Scale generation ─────────────────────────────────────── */
73
+
74
+ export function generateScale(theme: ThemeId, hue: number | null, maxSat: number, dims: Record<SurfaceKey, number> = DEFAULT_DIMS, outline: number = DEFAULT_OUTLINE): Record<ScaleKey, string> {
75
+ const lightness = BASE_THEMES[theme].lightness
76
+ const glassBase = hue !== null ? hslToHex(hue, 80, 50) : null
77
+ const result = {} as Record<ScaleKey, string>
78
+
79
+ for (const key of SCALE_KEYS) {
80
+ const l = lightness[key]
81
+
82
+ // Surface keys: accent-at-opacity (per-key dim)
83
+ const dimVal = (dims as Partial<Record<ScaleKey, number>>)[key]
84
+ if (dimVal !== undefined && dimVal > 0 && glassBase) {
85
+ result[key] = `${glassBase}${alphaToHex(dimVal / 100)}`
86
+ continue
87
+ }
88
+
89
+ // Border key: accent at outline opacity
90
+ if (key === '700' && outline > 0 && glassBase) {
91
+ result[key] = `${glassBase}${alphaToHex(outline / 100)}`
92
+ continue
93
+ }
94
+
95
+ // Solid color (text, black, fallbacks)
96
+ if (hue === null || maxSat === 0) {
97
+ const g = Math.round(255 * l / 100).toString(16).padStart(2, '0')
98
+ result[key] = `#${g}${g}${g}`
99
+ } else {
100
+ result[key] = hslToHex(hue, maxSat * satCurve(l), l)
101
+ }
102
+ }
103
+ return result
104
+ }
105
+
106
+ /* ── Theme application helpers ────────────────────────────── */
107
+
108
+ export function isLightTheme(themeId: ThemeId): boolean {
109
+ return LIGHT_THEMES.includes(themeId)
110
+ }
111
+
112
+ export function applyTheme(themeId: ThemeId, accentHue: number | null, dims: Record<SurfaceKey, number> = DEFAULT_DIMS, outline: number = DEFAULT_OUTLINE, root: HTMLElement = document.documentElement): void {
113
+ const scale = generateScale(themeId, accentHue, BASE_THEMES[themeId].maxSat, dims, outline)
114
+ const light = isLightTheme(themeId)
115
+
116
+ for (const key of SCALE_KEYS) {
117
+ const prop = key === 'black' ? '--color-black' : `--color-neutral-${key}`
118
+ root.style.setProperty(prop, scale[key])
119
+ }
120
+ root.style.setProperty('--color-white', light ? '#0a0a0a' : '#ffffff')
121
+ root.style.setProperty('--color-neutral-100', light ? '#1a1a1a' : '#f0f0f0')
122
+ root.style.setProperty('--color-neutral-50', light ? '#0f0f0f' : '#fafafa')
123
+
124
+ // Surface hover: accent glass at 10% for visible hover states
125
+ const glassBase = accentHue !== null ? hslToHex(accentHue, 80, 50) : null
126
+ if (glassBase) {
127
+ root.style.setProperty('--surface-hover', `${glassBase}${alphaToHex(10 / 100)}`)
128
+ }
129
+
130
+ }
@@ -66,7 +66,7 @@ export function ToolsPathsPanel({
66
66
  <div className={cn('w-full', className)}>
67
67
  {/* Header */}
68
68
  <div className="flex items-center justify-between mb-4">
69
- <p className="text-xs text-[#6c7086] leading-relaxed max-w-xl">
69
+ <p className="text-xs text-neutral-500 leading-relaxed max-w-xl">
70
70
  Configure CLI paths for AI coding assistants. Specify executable locations
71
71
  to enable integration with your development workflow.
72
72
  </p>
@@ -92,7 +92,7 @@ export function ToolsPathsPanel({
92
92
  </div>
93
93
 
94
94
  {/* Tool list */}
95
- <div className="bg-[#181825] border border-[#313244] rounded-lg divide-y divide-[#313244]">
95
+ <div className="bg-neutral-900 border border-neutral-700 rounded-lg divide-y divide-neutral-700">
96
96
  {tools.map((tool) => {
97
97
  const isRefreshing = refreshingTools.has(tool.id)
98
98
  const isEnabled = tool.enabled
@@ -111,7 +111,7 @@ export function ToolsPathsPanel({
111
111
  <span
112
112
  className={cn(
113
113
  'font-medium',
114
- tool.detected && !isEnabled ? 'text-[#6c7086]' : 'text-[#cdd6f4]',
114
+ tool.detected && !isEnabled ? 'text-neutral-500' : 'text-neutral-300',
115
115
  )}
116
116
  >
117
117
  {tool.name}
@@ -121,7 +121,7 @@ export function ToolsPathsPanel({
121
121
  className="absolute inset-0 flex items-center pointer-events-none"
122
122
  aria-hidden="true"
123
123
  >
124
- <div className="w-full h-0.5 bg-[#f38ba8]/70 rounded-full" />
124
+ <div className="w-full h-0.5 bg-red-400/70 rounded-full" />
125
125
  </div>
126
126
  )}
127
127
  </div>
@@ -132,7 +132,7 @@ export function ToolsPathsPanel({
132
132
  Installed
133
133
  </span>
134
134
  ) : (
135
- <span className="flex items-center gap-1 text-xs text-[#6c7086]">
135
+ <span className="flex items-center gap-1 text-xs text-neutral-500">
136
136
  <X className="w-3.5 h-3.5" />
137
137
  Not Found
138
138
  </span>
@@ -149,24 +149,24 @@ export function ToolsPathsPanel({
149
149
  {/* Path input row */}
150
150
  <div className="space-y-1">
151
151
  <div className="flex items-center gap-2">
152
- <Terminal className="w-3 h-3 text-[#6c7086]" />
152
+ <Terminal className="w-3 h-3 text-neutral-500" />
153
153
  <div className="flex-1">
154
154
  <Input
155
155
  value={displayPath}
156
156
  onChange={(newPath) => validateAndUpdatePath(tool.id, newPath)}
157
157
  placeholder={`/usr/local/bin/${tool.id}`}
158
158
  size="sm"
159
- className={displayPath ? 'text-white' : 'text-[#a6adc8]'}
159
+ className={displayPath ? 'text-white' : 'text-neutral-400'}
160
160
  />
161
161
  </div>
162
162
  <IconButton
163
163
  icon={
164
164
  isRefreshing ? (
165
- <RefreshCw className="w-3 h-3 text-[#cdd6f4] animate-spin" />
165
+ <RefreshCw className="w-3 h-3 text-neutral-300 animate-spin" />
166
166
  ) : hasBeenScanned ? (
167
167
  <Check className="w-3 h-3 text-green-400" />
168
168
  ) : (
169
- <RefreshCw className="w-3 h-3 text-[#cdd6f4]" />
169
+ <RefreshCw className="w-3 h-3 text-neutral-300" />
170
170
  )
171
171
  }
172
172
  onClick={() => detectTool(tool.id)}
@@ -188,10 +188,10 @@ export function ToolsPathsPanel({
188
188
  </p>
189
189
  )}
190
190
  {tool.detected && !tool.binaryPath && (
191
- <p className="text-xs text-[#6c7086] pl-5">Using auto-detected path</p>
191
+ <p className="text-xs text-neutral-500 pl-5">Using auto-detected path</p>
192
192
  )}
193
193
  {!isEnabled && !displayPath?.includes('/') && !tool.detected && (
194
- <p className="text-xs text-[#6c7086] pl-5">
194
+ <p className="text-xs text-neutral-500 pl-5">
195
195
  Tool disabled. Enter path to enable.
196
196
  </p>
197
197
  )}
@@ -70,18 +70,18 @@ export function CapturedIssuesPanel({
70
70
  return (
71
71
  <div className={cn('space-y-6', className)}>
72
72
  {hasErrors ? (
73
- <div className="bg-[#181825] border border-[#313244] rounded-lg p-4 space-y-4">
73
+ <div className="bg-neutral-900 border border-neutral-700 rounded-lg p-4 space-y-4">
74
74
  {/* Error Summary */}
75
75
  <div className="flex items-center justify-between">
76
76
  <div className="flex items-center gap-2">
77
77
  <AlertTriangle className="w-4 h-4 text-red-400" />
78
- <span className="text-sm text-[#cdd6f4]">
78
+ <span className="text-sm text-neutral-300">
79
79
  {errorCount > 0 && (
80
80
  <span className="text-red-400">
81
81
  {errorCount} error{errorCount !== 1 ? 's' : ''}
82
82
  </span>
83
83
  )}
84
- {errorCount > 0 && warnCount > 0 && <span className="text-[#6c7086]">, </span>}
84
+ {errorCount > 0 && warnCount > 0 && <span className="text-neutral-500">, </span>}
85
85
  {warnCount > 0 && (
86
86
  <span className="text-yellow-400">
87
87
  {warnCount} warning{warnCount !== 1 ? 's' : ''}
@@ -93,14 +93,14 @@ export function CapturedIssuesPanel({
93
93
 
94
94
  {/* Error List */}
95
95
  <div className="space-y-2">
96
- <p className="text-sm text-[#a6adc8]">Captured Errors</p>
97
- <div className="max-h-48 overflow-y-auto bg-[#11111b] border border-[#313244] rounded-lg p-3 space-y-1">
96
+ <p className="text-sm text-neutral-400">Captured Errors</p>
97
+ <div className="max-h-48 overflow-y-auto bg-neutral-950 border border-neutral-700 rounded-lg p-3 space-y-1">
98
98
  {errors.map((error) => (
99
99
  <div key={error.fingerprint} className="text-xs font-mono break-words">
100
100
  <span className={cn('mr-2 shrink-0', error.level === 'warning' ? 'text-yellow-400' : 'text-red-400')}>
101
101
  {error.count > 1 ? `\u00d7${error.count}` : '\u2022'}
102
102
  </span>
103
- <span className="text-[#a6adc8] break-all">{error.message}</span>
103
+ <span className="text-neutral-400 break-all">{error.message}</span>
104
104
  </div>
105
105
  ))}
106
106
  </div>
@@ -108,7 +108,7 @@ export function CapturedIssuesPanel({
108
108
 
109
109
  {/* Optional Details */}
110
110
  <div className="space-y-3">
111
- <p className="text-sm text-[#a6adc8]">Add context (optional)</p>
111
+ <p className="text-sm text-neutral-400">Add context (optional)</p>
112
112
  <Input
113
113
  value={title}
114
114
  onChange={setTitle}
@@ -119,7 +119,7 @@ export function CapturedIssuesPanel({
119
119
  onChange={(e) => setDescription(e.target.value)}
120
120
  placeholder="What were you doing when this happened?"
121
121
  rows={3}
122
- className="w-full px-3 py-1.5 bg-[#313244] border border-[#45475a] rounded-lg text-[#cdd6f4] placeholder-[#6c7086] focus:outline-none focus:border-blue-500 transition-colors resize-none text-sm"
122
+ className="w-full px-3 py-1.5 bg-neutral-750 border border-neutral-600 rounded-lg text-neutral-300 placeholder-neutral-500 focus:outline-none focus:border-blue-500 transition-colors resize-none text-sm"
123
123
  />
124
124
  <Input
125
125
  type="text"
@@ -157,14 +157,14 @@ export function CapturedIssuesPanel({
157
157
  </div>
158
158
  </div>
159
159
  ) : (
160
- <div className="bg-[#181825] border border-[#313244] rounded-lg p-6">
160
+ <div className="bg-neutral-900 border border-neutral-700 rounded-lg p-6">
161
161
  <div className="flex items-center gap-4">
162
162
  <div className="w-10 h-10 bg-green-500/10 rounded-lg flex items-center justify-center">
163
163
  <Check className="w-5 h-5 text-green-400" />
164
164
  </div>
165
165
  <div>
166
- <h3 className="text-[#cdd6f4] font-medium">No Issues Captured</h3>
167
- <p className="text-sm text-[#6c7086]">Everything is running smoothly.</p>
166
+ <h3 className="text-neutral-300 font-medium">No Issues Captured</h3>
167
+ <p className="text-sm text-neutral-500">Everything is running smoothly.</p>
168
168
  </div>
169
169
  </div>
170
170
  </div>
@@ -172,31 +172,31 @@ export function CapturedIssuesPanel({
172
172
 
173
173
  {/* Previously Reported */}
174
174
  {submittedErrors.length > 0 && (
175
- <div className="bg-[#181825] border border-[#313244] rounded-lg overflow-hidden">
175
+ <div className="bg-neutral-900 border border-neutral-700 rounded-lg overflow-hidden">
176
176
  <details className="group">
177
- <summary className="flex items-center justify-between p-4 cursor-pointer hover:bg-[#1e1e2e] transition-colors">
177
+ <summary className="flex items-center justify-between p-4 cursor-pointer hover:bg-neutral-850 transition-colors">
178
178
  <div className="flex items-center gap-2">
179
179
  <Check className="w-4 h-4 text-green-400" />
180
- <span className="text-sm text-[#cdd6f4]">Previously Reported</span>
181
- <span className="text-xs text-[#6c7086]">({submittedErrors.length})</span>
180
+ <span className="text-sm text-neutral-300">Previously Reported</span>
181
+ <span className="text-xs text-neutral-500">({submittedErrors.length})</span>
182
182
  </div>
183
- <ChevronDown className="w-4 h-4 text-[#6c7086] transition-transform group-open:rotate-180" />
183
+ <ChevronDown className="w-4 h-4 text-neutral-500 transition-transform group-open:rotate-180" />
184
184
  </summary>
185
- <div className="border-t border-[#313244] p-4 space-y-2">
185
+ <div className="border-t border-neutral-700 p-4 space-y-2">
186
186
  {submittedErrors.map((error) => (
187
187
  <div
188
188
  key={error.fingerprint}
189
- className="flex items-start gap-3 p-3 bg-[#11111b] border border-[#313244] rounded-lg"
189
+ className="flex items-start gap-3 p-3 bg-neutral-950 border border-neutral-700 rounded-lg"
190
190
  >
191
191
  <Check className="w-4 h-4 text-green-400 mt-0.5 shrink-0" />
192
192
  <div className="flex-1 min-w-0">
193
193
  <p
194
- className="text-sm text-[#cdd6f4] font-mono truncate"
194
+ className="text-sm text-neutral-300 font-mono truncate"
195
195
  title={error.message}
196
196
  >
197
197
  {error.message}
198
198
  </p>
199
- <div className="flex items-center gap-3 mt-1 text-xs text-[#6c7086]">
199
+ <div className="flex items-center gap-3 mt-1 text-xs text-neutral-500">
200
200
  {error.count > 0 && (
201
201
  <span className="text-yellow-400/80">+{error.count} since</span>
202
202
  )}
@@ -81,13 +81,13 @@ function DiffFileItem({ file, isSelected, onSelect, onReset, resettingFile, anyR
81
81
  return (
82
82
  <div
83
83
  className={`flex items-center transition-colors ${
84
- isSelected ? 'bg-[#313244]' : 'hover:bg-[#1e1e2e]'
84
+ isSelected ? 'bg-neutral-700' : 'hover:bg-neutral-850'
85
85
  }`}
86
86
  >
87
87
  <button
88
88
  onClick={() => onSelect(file)}
89
89
  className={`flex-1 flex items-center gap-2 px-3 py-1 text-xs ${
90
- isSelected ? 'text-[#cdd6f4]' : 'text-[#a6adc8]'
90
+ isSelected ? 'text-neutral-300' : 'text-neutral-400'
91
91
  }`}
92
92
  >
93
93
  {icon}
@@ -166,7 +166,7 @@ function DiffFileTreePanel({ sync, componentLabels, renderFileIcon }: DiffFileTr
166
166
  // Root-level nodes get colored styling, nested folders get muted styling
167
167
  const headerClasses = isRoot
168
168
  ? `${color.text} ${color.hoverBg} border-l-2 ${color.border} ${color.bg} px-3 py-2 text-sm font-medium`
169
- : 'text-[#6c7086] hover:bg-[#1e1e2e] px-3 py-1.5'
169
+ : 'text-neutral-500 hover:bg-neutral-850 px-3 py-1.5'
170
170
 
171
171
  const chevronSize = isRoot ? 'w-4 h-4' : 'w-3 h-3'
172
172
 
@@ -175,7 +175,7 @@ function DiffFileTreePanel({ sync, componentLabels, renderFileIcon }: DiffFileTr
175
175
  {fileCount}
176
176
  </span>
177
177
  ) : (
178
- <span className="text-[#585b70] ml-auto">{fileCount}</span>
178
+ <span className="text-neutral-600 ml-auto">{fileCount}</span>
179
179
  )
180
180
 
181
181
  const containerLineClass = isRoot ? color.line : lineClass
@@ -191,7 +191,7 @@ function DiffFileTreePanel({ sync, componentLabels, renderFileIcon }: DiffFileTr
191
191
  ) : (
192
192
  <ChevronDown className={chevronSize} />
193
193
  )}
194
- {isRoot ? null : <Folder className="w-3 h-3 text-[#585b70]" />}
194
+ {isRoot ? null : <Folder className="w-3 h-3 text-neutral-600" />}
195
195
  {displayLabel}
196
196
  {countElement}
197
197
  </button>
@@ -219,9 +219,9 @@ function DiffFileTreePanel({ sync, componentLabels, renderFileIcon }: DiffFileTr
219
219
  }
220
220
 
221
221
  return (
222
- <div className="w-72 flex-shrink-0 bg-[#181825] rounded-lg border border-[#313244] overflow-hidden flex flex-col">
222
+ <div className="w-72 flex-shrink-0 bg-neutral-900 rounded-lg border border-neutral-700 overflow-hidden flex flex-col">
223
223
  {/* Summary Header */}
224
- <div className="p-3 border-b border-[#313244] flex-shrink-0">
224
+ <div className="p-3 border-b border-neutral-700 flex-shrink-0">
225
225
  <div className="flex gap-3 text-xs">
226
226
  <span className="text-green-400">+{diff!.summary.added}</span>
227
227
  <span className="text-red-400">-{diff!.summary.removed}</span>
@@ -231,7 +231,7 @@ function DiffFileTreePanel({ sync, componentLabels, renderFileIcon }: DiffFileTr
231
231
  </div>
232
232
 
233
233
  {/* Filter Tabs */}
234
- <div className="flex items-center justify-between p-2 border-b border-[#313244] flex-shrink-0">
234
+ <div className="flex items-center justify-between p-2 border-b border-neutral-700 flex-shrink-0">
235
235
  <div className="flex items-center gap-1">
236
236
  {(['all', 'modified', 'added', 'removed'] as const).map((filter) => (
237
237
  <button
@@ -239,8 +239,8 @@ function DiffFileTreePanel({ sync, componentLabels, renderFileIcon }: DiffFileTr
239
239
  onClick={() => setDiffFilter(filter)}
240
240
  className={`px-2 py-1 rounded text-xs transition-colors cursor-pointer ${
241
241
  diffFilter === filter
242
- ? 'bg-[#313244] text-[#cdd6f4]'
243
- : 'text-[#6c7086] hover:text-[#a6adc8]'
242
+ ? 'bg-neutral-700 text-neutral-300'
243
+ : 'text-neutral-500 hover:text-neutral-400'
244
244
  }`}
245
245
  >
246
246
  {filter === 'all' ? 'All' : filter.charAt(0).toUpperCase() + filter.slice(1)}
@@ -294,8 +294,8 @@ function DiffEditorPanel({ sync, componentLabels, monacoTheme }: DiffEditorPanel
294
294
 
295
295
  if (!selectedDiffFile) {
296
296
  return (
297
- <div className="flex-1 min-w-0 bg-[#181825] rounded-lg border border-[#313244] overflow-hidden flex flex-col">
298
- <div className="flex items-center justify-center h-full text-sm text-[#6c7086]">
297
+ <div className="flex-1 min-w-0 bg-neutral-900 rounded-lg border border-neutral-700 overflow-hidden flex flex-col">
298
+ <div className="flex items-center justify-center h-full text-sm text-neutral-500">
299
299
  Select a file from the tree to view differences
300
300
  </div>
301
301
  </div>
@@ -305,12 +305,12 @@ function DiffEditorPanel({ sync, componentLabels, monacoTheme }: DiffEditorPanel
305
305
  const compLabel = componentLabels?.[selectedDiffFile.component] ?? selectedDiffFile.component
306
306
 
307
307
  return (
308
- <div className="flex-1 min-w-0 bg-[#181825] rounded-lg border border-[#313244] overflow-hidden flex flex-col">
308
+ <div className="flex-1 min-w-0 bg-neutral-900 rounded-lg border border-neutral-700 overflow-hidden flex flex-col">
309
309
  {/* File Header */}
310
- <div className="flex items-center justify-between px-4 py-2 border-b border-[#313244] flex-shrink-0">
310
+ <div className="flex items-center justify-between px-4 py-2 border-b border-neutral-700 flex-shrink-0">
311
311
  <div className="flex items-center gap-2">
312
312
  <FileCode className="w-4 h-4 flex-shrink-0 text-blue-400/50" />
313
- <span className="text-sm text-[#cdd6f4] font-medium">
313
+ <span className="text-sm text-neutral-300 font-medium">
314
314
  {selectedDiffFile.component}/{selectedDiffFile.path}
315
315
  </span>
316
316
  {devtools && hasUnsavedChanges && (
@@ -319,7 +319,7 @@ function DiffEditorPanel({ sync, componentLabels, monacoTheme }: DiffEditorPanel
319
319
  </span>
320
320
  )}
321
321
  </div>
322
- <span className="text-xs text-[#6c7086]">{compLabel}</span>
322
+ <span className="text-xs text-neutral-500">{compLabel}</span>
323
323
  </div>
324
324
 
325
325
  {/* Diff Editor */}
@@ -355,7 +355,7 @@ function DiffEditorPanel({ sync, componentLabels, monacoTheme }: DiffEditorPanel
355
355
  }}
356
356
  />
357
357
  ) : (
358
- <div className="flex items-center justify-center h-full text-sm text-[#6c7086]">
358
+ <div className="flex items-center justify-center h-full text-sm text-neutral-500">
359
359
  Loading file content...
360
360
  </div>
361
361
  )}
@@ -404,14 +404,14 @@ export function FileDiffViewer({ sync, componentLabels, monacoTheme, renderFileI
404
404
  </div>
405
405
 
406
406
  {diffLoading ? (
407
- <div className="text-sm text-[#6c7086] text-center py-8">Loading diff...</div>
407
+ <div className="text-sm text-neutral-500 text-center py-8">Loading diff...</div>
408
408
  ) : diff ? (
409
409
  <div className="flex gap-4 flex-1 min-h-[400px]">
410
410
  <DiffFileTreePanel sync={sync} componentLabels={componentLabels} renderFileIcon={renderFileIcon} />
411
411
  <DiffEditorPanel sync={sync} componentLabels={componentLabels} monacoTheme={monacoTheme} />
412
412
  </div>
413
413
  ) : (
414
- <div className="text-sm text-[#6c7086] text-center py-8">
414
+ <div className="text-sm text-neutral-500 text-center py-8">
415
415
  No diff data available. Click refresh to load.
416
416
  </div>
417
417
  )}
@@ -115,10 +115,10 @@ export function GoldenSyncPanel({
115
115
  <div className="flex-shrink-0">
116
116
  <div className="flex items-start justify-between mb-4">
117
117
  <div className="space-y-1">
118
- <h3 className="text-base font-semibold text-[#cdd6f4]">
118
+ <h3 className="text-base font-semibold text-neutral-300">
119
119
  {TAB_DESCRIPTIONS[activeTab].title}
120
120
  </h3>
121
- <p className="text-sm text-[#a6adc8]">
121
+ <p className="text-sm text-neutral-400">
122
122
  {TAB_DESCRIPTIONS[activeTab].description}
123
123
  </p>
124
124
  </div>
@@ -174,7 +174,7 @@ export function GoldenSyncPanel({
174
174
  {/* Content */}
175
175
  <div className={activeTab === 'diff' ? 'flex-1 min-h-0 mt-4' : 'space-y-6 mt-4'}>
176
176
  {loading && activeTab === 'status' ? (
177
- <div className="text-sm text-[#6c7086] text-center py-8">Loading...</div>
177
+ <div className="text-sm text-neutral-500 text-center py-8">Loading...</div>
178
178
  ) : activeTab === 'status' ? (
179
179
  <StatusOverview
180
180
  status={status}
@@ -45,18 +45,18 @@ export function SnapshotManager({ sync }: SnapshotManagerProps) {
45
45
  }
46
46
 
47
47
  if (manifestLoading && !manifest) {
48
- return <div className="text-sm text-[#6c7086] text-center py-8">Loading snapshots...</div>
48
+ return <div className="text-sm text-neutral-500 text-center py-8">Loading snapshots...</div>
49
49
  }
50
50
 
51
51
  return (
52
52
  <div className="space-y-4">
53
53
  {/* Create Snapshot */}
54
- <div className="bg-[#181825] rounded-lg p-4 border border-[#313244]">
54
+ <div className="bg-neutral-900 rounded-lg p-4 border border-neutral-700">
55
55
  <div className="flex items-center gap-3 mb-3">
56
56
  <Plus className="w-5 h-5 text-green-400" />
57
- <h4 className="text-[#cdd6f4] font-medium">Create Snapshot</h4>
57
+ <h4 className="text-neutral-300 font-medium">Create Snapshot</h4>
58
58
  </div>
59
- <p className="text-xs text-[#585b70] mb-3">
59
+ <p className="text-xs text-neutral-600 mb-3">
60
60
  Archives the current live state. If components differ from golden, their patch version is auto-bumped.
61
61
  </p>
62
62
  <div className="flex gap-2">
@@ -81,12 +81,12 @@ export function SnapshotManager({ sync }: SnapshotManagerProps) {
81
81
  </div>
82
82
 
83
83
  {/* Snapshots List */}
84
- <div className="bg-[#181825] rounded-lg border border-[#313244] overflow-hidden">
85
- <div className="flex items-center justify-between px-4 py-3 border-b border-[#313244]">
84
+ <div className="bg-neutral-900 rounded-lg border border-neutral-700 overflow-hidden">
85
+ <div className="flex items-center justify-between px-4 py-3 border-b border-neutral-700">
86
86
  <div className="flex items-center gap-2">
87
- <Archive className="w-4 h-4 text-[#6c7086]" />
88
- <h4 className="text-[#cdd6f4] font-medium">Snapshots</h4>
89
- <span className="text-xs text-[#6c7086]">({manifest?.snapshots.length ?? 0})</span>
87
+ <Archive className="w-4 h-4 text-neutral-500" />
88
+ <h4 className="text-neutral-300 font-medium">Snapshots</h4>
89
+ <span className="text-xs text-neutral-500">({manifest?.snapshots.length ?? 0})</span>
90
90
  </div>
91
91
  <IconButton
92
92
  icon={manifestLoading ? <Loader2 className="animate-spin" /> : <RotateCcw />}
@@ -99,9 +99,9 @@ export function SnapshotManager({ sync }: SnapshotManagerProps) {
99
99
  </div>
100
100
 
101
101
  {!manifest || manifest.snapshots.length === 0 ? (
102
- <div className="text-sm text-[#6c7086] text-center py-8">No snapshots yet</div>
102
+ <div className="text-sm text-neutral-500 text-center py-8">No snapshots yet</div>
103
103
  ) : (
104
- <div className="divide-y divide-[#313244]">
104
+ <div className="divide-y divide-neutral-700">
105
105
  {[...manifest.snapshots].reverse().map((snap) => (
106
106
  <div
107
107
  key={snap.version}
@@ -112,7 +112,7 @@ export function SnapshotManager({ sync }: SnapshotManagerProps) {
112
112
  {/* Version + info */}
113
113
  <div className="flex-1 min-w-0">
114
114
  <div className="flex items-center gap-2">
115
- <span className="text-sm font-mono text-[#cdd6f4]">v{snap.version}</span>
115
+ <span className="text-sm font-mono text-neutral-300">v{snap.version}</span>
116
116
  {snap.version === manifest.activeVersion && (
117
117
  <span className="px-1.5 py-0.5 bg-green-500/20 text-green-400 text-[10px] rounded font-medium">
118
118
  active
@@ -122,13 +122,13 @@ export function SnapshotManager({ sync }: SnapshotManagerProps) {
122
122
  <Pin className="w-3 h-3 text-amber-400" />
123
123
  )}
124
124
  {snap.metaVersion && (
125
- <span className="text-xs text-[#585b70] font-mono">{snap.metaVersion}</span>
125
+ <span className="text-xs text-neutral-600 font-mono">{snap.metaVersion}</span>
126
126
  )}
127
127
  </div>
128
128
  <div className="flex items-center gap-2 mt-0.5">
129
- <span className="text-xs text-[#585b70]">{formatDate(snap.createdAt)}</span>
129
+ <span className="text-xs text-neutral-600">{formatDate(snap.createdAt)}</span>
130
130
  {snap.description && (
131
- <span className="text-xs text-[#6c7086] truncate">{snap.description}</span>
131
+ <span className="text-xs text-neutral-500 truncate">{snap.description}</span>
132
132
  )}
133
133
  </div>
134
134
  </div>