@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.
- package/components/lib/theme-engine.ts +130 -0
- package/components/sections/ai-tools-paths/tools-paths-panel.tsx +11 -11
- package/components/sections/captured-issues/captured-issues-panel.tsx +20 -20
- package/components/sections/golden-snapshots/file-diff-viewer.tsx +19 -19
- package/components/sections/golden-snapshots/golden-sync-panel.tsx +3 -3
- package/components/sections/golden-snapshots/snapshot-manager.tsx +15 -15
- package/components/sections/golden-snapshots/status-overview.tsx +40 -40
- package/components/sections/golden-snapshots/version-manager.tsx +10 -10
- package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +11 -11
- package/components/sections/prompt-editor/simulator-prompt-editor.tsx +15 -15
- package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +19 -19
- package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +10 -10
- package/components/sections/snapshot-browser/snapshot-tree.tsx +11 -11
- package/components/sections/snippets-editor/snippets-editor.tsx +24 -24
- package/components/settings/SettingsHeader.tsx +78 -0
- package/components/settings/SettingsPanel.tsx +21 -0
- package/components/settings/SettingsTreeNav.tsx +256 -0
- package/components/settings/index.ts +7 -0
- package/components/settings/settings-tree-utils.ts +120 -0
- package/components/ui/breadcrumb.tsx +16 -4
- package/components/ui/cookie-consent.tsx +82 -0
- package/components/ui/file-tree.tsx +5 -5
- package/components/ui/filter-dropdown.tsx +4 -4
- package/components/ui/form-actions.tsx +1 -1
- package/components/ui/label.tsx +31 -3
- package/components/ui/resizable-textarea.tsx +2 -2
- package/components/ui/segmented-toggle.tsx +17 -4
- package/components/ui/select.tsx +3 -3
- package/components/ui/sort-dropdown.tsx +2 -2
- package/components/ui/status-card.tsx +1 -1
- package/components/ui/tooltip.tsx +2 -2
- package/dist/index.d.ts +97 -4
- package/dist/index.js +1184 -608
- package/dist/tokens/primitives.css +10 -8
- package/dist/tokens/semantic.css +5 -0
- package/index.ts +20 -0
- package/package.json +1 -1
- package/tokens/primitives.css +10 -8
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
97
|
-
<div className="max-h-48 overflow-y-auto bg-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
167
|
-
<p className="text-sm text-
|
|
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-
|
|
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-
|
|
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-
|
|
181
|
-
<span className="text-xs text-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
243
|
-
: 'text-
|
|
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-
|
|
298
|
-
<div className="flex items-center justify-center h-full text-sm text-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
57
|
+
<h4 className="text-neutral-300 font-medium">Create Snapshot</h4>
|
|
58
58
|
</div>
|
|
59
|
-
<p className="text-xs text-
|
|
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-
|
|
85
|
-
<div className="flex items-center justify-between px-4 py-3 border-b border-
|
|
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-
|
|
88
|
-
<h4 className="text-
|
|
89
|
-
<span className="text-xs text-
|
|
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-
|
|
102
|
+
<div className="text-sm text-neutral-500 text-center py-8">No snapshots yet</div>
|
|
103
103
|
) : (
|
|
104
|
-
<div className="divide-y divide-
|
|
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-
|
|
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-
|
|
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-
|
|
129
|
+
<span className="text-xs text-neutral-600">{formatDate(snap.createdAt)}</span>
|
|
130
130
|
{snap.description && (
|
|
131
|
-
<span className="text-xs text-
|
|
131
|
+
<span className="text-xs text-neutral-500 truncate">{snap.description}</span>
|
|
132
132
|
)}
|
|
133
133
|
</div>
|
|
134
134
|
</div>
|