@toolr/ui-design 0.1.5 → 0.1.7

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 (82) hide show
  1. package/agent-rules.json +91 -0
  2. package/ai-manifest.json +190 -0
  3. package/components/content/info-panel-primitives.tsx +14 -14
  4. package/components/hooks/use-click-outside.ts +10 -3
  5. package/components/hooks/use-modal-behavior.ts +24 -0
  6. package/components/hooks/use-navigation-history.ts +7 -2
  7. package/components/hooks/use-resizable-sidebar.ts +38 -0
  8. package/components/lib/ai-tools.tsx +1 -1
  9. package/components/lib/form-colors.ts +40 -0
  10. package/components/sections/ai-tools-paths/tools-paths-panel.tsx +7 -7
  11. package/components/sections/captured-issues/captured-issues-panel.tsx +13 -13
  12. package/components/sections/captured-issues/use-captured-issues.ts +9 -3
  13. package/components/sections/golden-snapshots/file-diff-viewer.tsx +13 -13
  14. package/components/sections/golden-snapshots/golden-sync-panel.tsx +5 -5
  15. package/components/sections/golden-snapshots/snapshot-manager.tsx +11 -11
  16. package/components/sections/golden-snapshots/status-overview.tsx +20 -20
  17. package/components/sections/golden-snapshots/version-manager.tsx +8 -8
  18. package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +8 -44
  19. package/components/sections/prompt-editor/index.ts +0 -7
  20. package/components/sections/prompt-editor/simulator-prompt-editor.tsx +9 -45
  21. package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +11 -43
  22. package/components/sections/report-bug/report-bug-form.tsx +14 -14
  23. package/components/sections/report-bug/screenshot-uploader.tsx +6 -6
  24. package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +3 -3
  25. package/components/sections/snapshot-browser/snapshot-tree.tsx +8 -8
  26. package/components/sections/snippets-editor/snippets-editor.tsx +74 -48
  27. package/components/settings/SettingsHeader.tsx +1 -2
  28. package/components/settings/SettingsTreeNav.tsx +31 -16
  29. package/components/ui/action-dialog.tsx +12 -56
  30. package/components/ui/badge.tsx +8 -24
  31. package/components/ui/bottom-panel-header.tsx +4 -4
  32. package/components/ui/breadcrumb.tsx +8 -68
  33. package/components/ui/checkbox.tsx +2 -16
  34. package/components/ui/collapsible-section.tsx +4 -42
  35. package/components/ui/confirm-badge.tsx +3 -20
  36. package/components/ui/cookie-consent.tsx +21 -5
  37. package/components/ui/debounce-border-overlay.tsx +31 -0
  38. package/components/ui/detail-section.tsx +5 -22
  39. package/components/ui/editor-placeholder-card.tsx +17 -16
  40. package/components/ui/editor-toolbar.tsx +12 -0
  41. package/components/ui/execution-details-panel.tsx +8 -13
  42. package/components/ui/extension-list-card.tsx +3 -3
  43. package/components/ui/file-structure-section.tsx +20 -35
  44. package/components/ui/file-tree.tsx +4 -14
  45. package/components/ui/files-panel.tsx +28 -18
  46. package/components/ui/filter-dropdown.tsx +5 -5
  47. package/components/ui/form-actions.tsx +7 -6
  48. package/components/ui/frontmatter-form-header.tsx +4 -4
  49. package/components/ui/icon-button.tsx +3 -2
  50. package/components/ui/input.tsx +15 -31
  51. package/components/ui/label.tsx +7 -21
  52. package/components/ui/layout-tab-bar.tsx +4 -4
  53. package/components/ui/modal.tsx +5 -17
  54. package/components/ui/nav-card.tsx +5 -20
  55. package/components/ui/navigation-bar.tsx +13 -74
  56. package/components/ui/number-input.tsx +4 -4
  57. package/components/ui/registry-browser.tsx +10 -24
  58. package/components/ui/registry-card.tsx +16 -20
  59. package/components/ui/registry-detail.tsx +6 -6
  60. package/components/ui/resizable-textarea.tsx +13 -35
  61. package/components/ui/segmented-toggle.tsx +6 -5
  62. package/components/ui/select.tsx +7 -16
  63. package/components/ui/selection-grid.tsx +6 -54
  64. package/components/ui/setting-row.tsx +2 -4
  65. package/components/ui/settings-card.tsx +3 -3
  66. package/components/ui/settings-info-box.tsx +6 -23
  67. package/components/ui/settings-section-title.tsx +1 -1
  68. package/components/ui/snapshot-card.tsx +7 -7
  69. package/components/ui/snippets-panel.tsx +10 -10
  70. package/components/ui/sort-dropdown.tsx +2 -2
  71. package/components/ui/status-card.tsx +6 -17
  72. package/components/ui/tab-bar.tsx +5 -31
  73. package/components/ui/toggle.tsx +3 -19
  74. package/components/ui/tooltip.tsx +9 -21
  75. package/dist/content.js +14 -14
  76. package/dist/index.d.ts +71 -141
  77. package/dist/index.js +1634 -2450
  78. package/dist/tokens/primitives.css +9 -2
  79. package/index.ts +8 -7
  80. package/package.json +13 -3
  81. package/tokens/primitives.css +9 -2
  82. package/components/sections/prompt-editor/use-prompt-editor.ts +0 -131
@@ -19,19 +19,12 @@
19
19
  * />
20
20
  */
21
21
 
22
- import { useState, useRef, useCallback } from 'react'
22
+ import { useState, useCallback } from 'react'
23
+ import { useResizableSidebar } from '../../hooks/use-resizable-sidebar.ts'
23
24
  import { FileCode, GripVertical, Crosshair } from 'lucide-react'
24
25
  import { TabbedPromptEditor } from './tabbed-prompt-editor.tsx'
25
26
  import type { ToolTab, PromptPlaceholder, FileTypeOption } from './types.ts'
26
27
 
27
- // ---------------------------------------------------------------------------
28
- // Constants
29
- // ---------------------------------------------------------------------------
30
-
31
- const MIN_SIDEBAR_WIDTH = 160
32
- const MAX_SIDEBAR_WIDTH = 320
33
- const DEFAULT_SIDEBAR_WIDTH = 200
34
-
35
28
  // ---------------------------------------------------------------------------
36
29
  // Props
37
30
  // ---------------------------------------------------------------------------
@@ -81,8 +74,7 @@ export function FileTypeTabbedPromptEditor({
81
74
  className = '',
82
75
  }: FileTypeTabbedPromptEditorProps) {
83
76
  const [selectedFileType, setSelectedFileType] = useState(fileTypes[0]?.id ?? '')
84
- const [sidebarWidth, setSidebarWidth] = useState(DEFAULT_SIDEBAR_WIDTH)
85
- const isDraggingRef = useRef(false)
77
+ const { width: sidebarWidth, onPointerDown: handleSidebarPointerDown } = useResizableSidebar({ min: 160, max: 320, defaultWidth: 200, direction: 'right' })
86
78
 
87
79
  // Derive prompts for current file type
88
80
  const currentPrompts = prompts[selectedFileType] ?? Object.fromEntries(tools.map((t) => [t.id, '']))
@@ -105,48 +97,20 @@ export function FileTypeTabbedPromptEditor({
105
97
  ? (tool: string, content: string) => onSave(selectedFileType, tool, content)
106
98
  : undefined
107
99
 
108
- // Sidebar resize via pointer events
109
- const handleSidebarPointerDown = useCallback((e: React.PointerEvent) => {
110
- e.preventDefault()
111
- e.stopPropagation()
112
- isDraggingRef.current = true
113
- const target = e.currentTarget as HTMLElement
114
- target.setPointerCapture(e.pointerId)
115
-
116
- const startX = e.clientX
117
- const startWidth = sidebarWidth
118
-
119
- const handlePointerMove = (moveEvent: PointerEvent) => {
120
- if (!isDraggingRef.current) return
121
- const deltaX = moveEvent.clientX - startX
122
- const newWidth = Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, startWidth + deltaX))
123
- setSidebarWidth(newWidth)
124
- }
125
-
126
- const handlePointerUp = () => {
127
- isDraggingRef.current = false
128
- target.removeEventListener('pointermove', handlePointerMove)
129
- target.removeEventListener('pointerup', handlePointerUp)
130
- }
131
-
132
- target.addEventListener('pointermove', handlePointerMove)
133
- target.addEventListener('pointerup', handlePointerUp)
134
- }, [sidebarWidth])
135
-
136
100
  return (
137
101
  <div className={`flex w-full max-w-full bg-neutral-900 border border-neutral-700 rounded-lg overflow-hidden ${className}`}>
138
102
  {/* Left Sidebar — File Type Selector */}
139
103
  <div
140
104
  className="relative shrink-0 bg-neutral-950 overflow-hidden flex flex-col"
141
- style={{ width: sidebarWidth, minWidth: MIN_SIDEBAR_WIDTH, maxWidth: MAX_SIDEBAR_WIDTH }}
105
+ style={{ width: sidebarWidth, minWidth: 160, maxWidth: 320 }}
142
106
  >
143
107
  {/* Header */}
144
108
  <div className="h-[52px] px-3 flex items-center border-b border-neutral-700 shrink-0">
145
109
  <div className="flex items-center gap-2">
146
110
  <Crosshair className="w-4 h-4 text-neutral-500 shrink-0" />
147
111
  <div className="min-w-0">
148
- <div className="text-xs font-medium text-neutral-400">{selectorLabel}</div>
149
- <div className="text-xs text-neutral-600">{selectorSublabel}</div>
112
+ <div className="text-sm font-medium text-neutral-400">{selectorLabel}</div>
113
+ <div className="text-sm text-neutral-600">{selectorSublabel}</div>
150
114
  </div>
151
115
  </div>
152
116
  </div>
@@ -169,11 +133,11 @@ export function FileTypeTabbedPromptEditor({
169
133
  {ft.icon ?? <FileCode className="w-4 h-4" />}
170
134
  </div>
171
135
  <div className="min-w-0 flex-1">
172
- <div className={`text-sm font-medium truncate ${isSelected ? 'text-neutral-300' : 'text-neutral-400'}`}>
136
+ <div className={`text-md font-medium truncate ${isSelected ? 'text-neutral-300' : 'text-neutral-400'}`}>
173
137
  {ft.name}
174
138
  </div>
175
139
  {ft.description && (
176
- <div className="text-xs text-neutral-500 mt-0.5 leading-relaxed">
140
+ <div className="text-sm text-neutral-500 mt-0.5 leading-relaxed">
177
141
  {ft.description}
178
142
  </div>
179
143
  )}
@@ -97,13 +97,6 @@ export type {
97
97
  ScenarioOption,
98
98
  } from './types.ts'
99
99
 
100
- // Hook
101
- export {
102
- usePromptEditor,
103
- type UsePromptEditorOptions,
104
- type UsePromptEditorReturn,
105
- } from './use-prompt-editor.ts'
106
-
107
100
  // Components
108
101
  export {
109
102
  TabbedPromptEditor,
@@ -19,19 +19,12 @@
19
19
  * />
20
20
  */
21
21
 
22
- import { useState, useRef, useCallback, useMemo } from 'react'
22
+ import { useState, useCallback, useMemo } from 'react'
23
+ import { useResizableSidebar } from '../../hooks/use-resizable-sidebar.ts'
23
24
  import { ChevronDown, ChevronRight, GripVertical, Crosshair } from 'lucide-react'
24
25
  import { TabbedPromptEditor } from './tabbed-prompt-editor.tsx'
25
26
  import type { ToolTab, PromptPlaceholder, ScenarioOption } from './types.ts'
26
27
 
27
- // ---------------------------------------------------------------------------
28
- // Constants
29
- // ---------------------------------------------------------------------------
30
-
31
- const MIN_SIDEBAR_WIDTH = 180
32
- const MAX_SIDEBAR_WIDTH = 360
33
- const DEFAULT_SIDEBAR_WIDTH = 220
34
-
35
28
  // ---------------------------------------------------------------------------
36
29
  // Props
37
30
  // ---------------------------------------------------------------------------
@@ -82,8 +75,7 @@ export function SimulatorPromptEditor({
82
75
  const [expandedScenarios, setExpandedScenarios] = useState<Set<string>>(
83
76
  new Set([defaultScenarioId]),
84
77
  )
85
- const [sidebarWidth, setSidebarWidth] = useState(DEFAULT_SIDEBAR_WIDTH)
86
- const isDraggingRef = useRef(false)
78
+ const { width: sidebarWidth, onPointerDown: handleSidebarPointerDown } = useResizableSidebar({ min: 180, max: 360, defaultWidth: 220, direction: 'right' })
87
79
 
88
80
  // Ensure selected scenario is always expanded
89
81
  const effectiveExpanded = useMemo(() => {
@@ -138,48 +130,20 @@ export function SimulatorPromptEditor({
138
130
  }
139
131
  }
140
132
 
141
- // Sidebar resize via pointer events
142
- const handleSidebarPointerDown = useCallback((e: React.PointerEvent) => {
143
- e.preventDefault()
144
- e.stopPropagation()
145
- isDraggingRef.current = true
146
- const target = e.currentTarget as HTMLElement
147
- target.setPointerCapture(e.pointerId)
148
-
149
- const startX = e.clientX
150
- const startWidth = sidebarWidth
151
-
152
- const handlePointerMove = (moveEvent: PointerEvent) => {
153
- if (!isDraggingRef.current) return
154
- const deltaX = moveEvent.clientX - startX
155
- const newWidth = Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, startWidth + deltaX))
156
- setSidebarWidth(newWidth)
157
- }
158
-
159
- const handlePointerUp = () => {
160
- isDraggingRef.current = false
161
- target.removeEventListener('pointermove', handlePointerMove)
162
- target.removeEventListener('pointerup', handlePointerUp)
163
- }
164
-
165
- target.addEventListener('pointermove', handlePointerMove)
166
- target.addEventListener('pointerup', handlePointerUp)
167
- }, [sidebarWidth])
168
-
169
133
  return (
170
134
  <div className={`flex w-full max-w-full bg-neutral-900 border border-neutral-700 rounded-lg overflow-hidden ${className}`}>
171
135
  {/* Left Sidebar — Tree Selector */}
172
136
  <div
173
137
  className="relative shrink-0 bg-neutral-950 overflow-hidden flex flex-col"
174
- style={{ width: sidebarWidth, minWidth: MIN_SIDEBAR_WIDTH, maxWidth: MAX_SIDEBAR_WIDTH }}
138
+ style={{ width: sidebarWidth, minWidth: 180, maxWidth: 360 }}
175
139
  >
176
140
  {/* Header */}
177
141
  <div className="h-[52px] px-3 flex items-center border-b border-neutral-700 shrink-0">
178
142
  <div className="flex items-center gap-2">
179
143
  <Crosshair className="w-4 h-4 text-neutral-500 shrink-0" />
180
144
  <div className="min-w-0">
181
- <div className="text-xs font-medium text-neutral-400">Scenario</div>
182
- <div className="text-xs text-neutral-600">Select scenario and step</div>
145
+ <div className="text-sm font-medium text-neutral-400">Scenario</div>
146
+ <div className="text-sm text-neutral-600">Select scenario and step</div>
183
147
  </div>
184
148
  </div>
185
149
  </div>
@@ -207,11 +171,11 @@ export function SimulatorPromptEditor({
207
171
  )}
208
172
  </div>
209
173
  <div className="min-w-0 flex-1">
210
- <div className={`text-sm font-medium ${isScenarioActive ? 'text-neutral-300' : 'text-neutral-400'}`}>
174
+ <div className={`text-md font-medium ${isScenarioActive ? 'text-neutral-300' : 'text-neutral-400'}`}>
211
175
  {scenario.name}
212
176
  </div>
213
177
  {scenario.description && (
214
- <div className="text-xs text-neutral-500 mt-0.5 leading-relaxed">
178
+ <div className="text-sm text-neutral-500 mt-0.5 leading-relaxed">
215
179
  {scenario.description}
216
180
  </div>
217
181
  )}
@@ -236,7 +200,7 @@ export function SimulatorPromptEditor({
236
200
  <div className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${
237
201
  isStepSelected ? 'bg-blue-400' : 'bg-neutral-600'
238
202
  }`} />
239
- <span className={`text-xs ${isStepSelected ? 'text-neutral-300' : 'text-neutral-400'}`}>
203
+ <span className={`text-sm ${isStepSelected ? 'text-neutral-300' : 'text-neutral-400'}`}>
240
204
  {step.name}
241
205
  </span>
242
206
  </button>
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  import { useState, useRef, useCallback, useEffect, useMemo } from 'react'
23
+ import { useResizableSidebar } from '../../hooks/use-resizable-sidebar.ts'
23
24
  import Editor, { type Monaco } from '@monaco-editor/react'
24
25
  import type { editor, languages } from 'monaco-editor'
25
26
  import { Variable, Info, Search, X, AlertTriangle } from 'lucide-react'
@@ -37,9 +38,6 @@ import { AiToolIcon } from '../../lib/ai-tools.tsx'
37
38
  const THEME_NAME = 'prompt-editor-dark'
38
39
  let themeRegistered = false
39
40
 
40
- const MIN_SIDEBAR_WIDTH = 220
41
- const MAX_SIDEBAR_WIDTH = 400
42
- const DEFAULT_SIDEBAR_WIDTH = 280
43
41
  const DEFAULT_EDITOR_HEIGHT = 400
44
42
 
45
43
  // ---------------------------------------------------------------------------
@@ -85,12 +83,11 @@ export function TabbedPromptEditor({
85
83
  className = '',
86
84
  }: TabbedPromptEditorProps) {
87
85
  const [activeTab, setActiveTab] = useState(tools[0]?.id ?? '')
88
- const [sidebarWidth, setSidebarWidth] = useState(DEFAULT_SIDEBAR_WIDTH)
86
+ const { width: sidebarWidth, onPointerDown: handleSidebarPointerDown } = useResizableSidebar({ min: 220, max: 400, defaultWidth: 280, direction: 'left' })
89
87
  const [variableSearch, setVariableSearch] = useState('')
90
88
  const [localContent, setLocalContent] = useState<Record<string, string>>(prompts)
91
89
  const [isDirty, setIsDirty] = useState(false)
92
90
 
93
- const isDraggingSidebarRef = useRef(false)
94
91
  const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null)
95
92
  const monacoRef = useRef<Monaco | null>(null)
96
93
  const decorationsRef = useRef<string[]>([])
@@ -236,35 +233,6 @@ export function TabbedPromptEditor({
236
233
  }
237
234
  }, [variables, registerCompletionProvider])
238
235
 
239
- // --- Sidebar resize ---
240
-
241
- const handleSidebarMouseDown = useCallback((e: React.PointerEvent) => {
242
- e.preventDefault()
243
- e.stopPropagation()
244
- isDraggingSidebarRef.current = true
245
- const target = e.currentTarget as HTMLElement
246
- target.setPointerCapture(e.pointerId)
247
-
248
- const startX = e.clientX
249
- const startWidth = sidebarWidth
250
-
251
- const handlePointerMove = (moveEvent: PointerEvent) => {
252
- if (!isDraggingSidebarRef.current) return
253
- const deltaX = startX - moveEvent.clientX
254
- const newWidth = Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, startWidth + deltaX))
255
- setSidebarWidth(newWidth)
256
- }
257
-
258
- const handlePointerUp = () => {
259
- isDraggingSidebarRef.current = false
260
- target.removeEventListener('pointermove', handlePointerMove)
261
- target.removeEventListener('pointerup', handlePointerUp)
262
- }
263
-
264
- target.addEventListener('pointermove', handlePointerMove)
265
- target.addEventListener('pointerup', handlePointerUp)
266
- }, [sidebarWidth])
267
-
268
236
  // --- Content change ---
269
237
 
270
238
  const handleEditorChange = useCallback(
@@ -350,7 +318,7 @@ export function TabbedPromptEditor({
350
318
  <button
351
319
  key={tool.id}
352
320
  onClick={() => setActiveTab(tool.id)}
353
- className={`flex-1 flex items-center justify-center gap-1.5 px-2 text-xs font-medium border-b-2 transition-colors ${
321
+ className={`flex-1 flex items-center justify-center gap-1.5 px-2 text-sm font-medium border-b-2 transition-colors ${
354
322
  isActive
355
323
  ? `border-current ${activeColor} bg-neutral-850`
356
324
  : 'border-neutral-700 text-neutral-500 hover:text-neutral-400 hover:bg-neutral-850/50'
@@ -368,7 +336,7 @@ export function TabbedPromptEditor({
368
336
  {checklistMissing && (
369
337
  <div className="flex items-start gap-2 px-3 py-2 bg-red-400/10 border-b border-red-400/30 shrink-0">
370
338
  <AlertTriangle className="w-4 h-4 text-red-400 shrink-0 mt-0.5" />
371
- <div className="text-xs text-red-400">
339
+ <div className="text-sm text-red-400">
372
340
  <span className="font-medium">Missing "# Verification Checklist" section.</span>
373
341
  {' '}When verification is enabled, only content under this heading will be sent to the AI for output validation.
374
342
  </div>
@@ -431,11 +399,11 @@ export function TabbedPromptEditor({
431
399
  {hasVariables && (
432
400
  <div
433
401
  className="relative shrink-0 bg-neutral-950 overflow-hidden flex flex-col border-l border-neutral-700"
434
- style={{ width: sidebarWidth, minWidth: MIN_SIDEBAR_WIDTH, maxWidth: MAX_SIDEBAR_WIDTH }}
402
+ style={{ width: sidebarWidth, minWidth: 220, maxWidth: 400 }}
435
403
  >
436
404
  {/* Resize handle on left edge */}
437
405
  <div
438
- onPointerDown={handleSidebarMouseDown}
406
+ onPointerDown={handleSidebarPointerDown}
439
407
  className="absolute left-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-violet-400/30 transition-colors z-10"
440
408
  />
441
409
 
@@ -444,11 +412,11 @@ export function TabbedPromptEditor({
444
412
  <div className="flex items-center gap-2">
445
413
  <Variable className="w-4 h-4 text-neutral-500 shrink-0" />
446
414
  <div className="min-w-0">
447
- <div className="text-xs font-medium text-neutral-400">Placeholders</div>
448
- <div className="text-xs text-neutral-600 truncate">Available placeholders</div>
415
+ <div className="text-sm font-medium text-neutral-400">Placeholders</div>
416
+ <div className="text-sm text-neutral-600 truncate">Available placeholders</div>
449
417
  </div>
450
418
  </div>
451
- <span className="px-1.5 py-0.5 bg-violet-400/20 text-violet-400 rounded text-xs">
419
+ <span className="px-1.5 py-0.5 bg-violet-400/20 text-violet-400 rounded text-sm">
452
420
  {variableSearch.trim() ? `${filteredVariables.length}/${variables!.length}` : variables!.length}
453
421
  </span>
454
422
  </div>
@@ -479,7 +447,7 @@ export function TabbedPromptEditor({
479
447
 
480
448
  {/* Autocomplete tip */}
481
449
  <div className="px-3 py-2 border-b border-neutral-700 shrink-0">
482
- <div className="flex items-center gap-1.5 text-xs text-neutral-500">
450
+ <div className="flex items-center gap-1.5 text-sm text-neutral-500">
483
451
  <Info className="w-3 h-3 flex-shrink-0" />
484
452
  <span>Type <code className="text-violet-400">{'{{'}</code> in editor for autocomplete</span>
485
453
  </div>
@@ -502,7 +470,7 @@ export function TabbedPromptEditor({
502
470
  </div>
503
471
  ))
504
472
  ) : variableSearch.trim() ? (
505
- <div className="text-center py-4 text-xs text-neutral-500">
473
+ <div className="text-center py-4 text-sm text-neutral-500">
506
474
  No placeholders match "{variableSearch}"
507
475
  </div>
508
476
  ) : null}
@@ -124,13 +124,13 @@ export function ReportBugForm({
124
124
  return (
125
125
  <div className={cn('rounded-lg border border-neutral-700 bg-neutral-800', className)}>
126
126
  <div className="border-b border-neutral-700 px-4 py-3">
127
- <h3 className="text-sm font-medium text-neutral-200">Report a Bug</h3>
127
+ <h3 className="text-md font-medium text-neutral-200">Report a Bug</h3>
128
128
  </div>
129
129
 
130
130
  <div className="space-y-4 p-4">
131
131
  {/* Issue type pills */}
132
132
  <div>
133
- <label className="mb-2 block text-xs text-neutral-400">Issue Type</label>
133
+ <label className="mb-2 block text-sm text-neutral-400">Issue Type</label>
134
134
  <div className="flex flex-wrap gap-2">
135
135
  {ISSUE_TYPES.map((type) => (
136
136
  <button
@@ -138,7 +138,7 @@ export function ReportBugForm({
138
138
  type="button"
139
139
  onClick={() => setIssueType(type.value as IssueType)}
140
140
  className={cn(
141
- 'px-3 py-1.5 text-xs font-medium rounded-md transition-all',
141
+ 'px-3 py-1.5 text-sm font-medium rounded-md transition-all',
142
142
  issueType === type.value
143
143
  ? 'bg-blue-600 text-white'
144
144
  : 'bg-neutral-700 text-neutral-400 hover:bg-neutral-600 hover:text-neutral-200',
@@ -152,7 +152,7 @@ export function ReportBugForm({
152
152
 
153
153
  {/* Title */}
154
154
  <div>
155
- <label className="mb-1.5 block text-xs text-neutral-400">
155
+ <label className="mb-1.5 block text-sm text-neutral-400">
156
156
  Title <span className="text-red-400">*</span>
157
157
  </label>
158
158
  <Input
@@ -165,7 +165,7 @@ export function ReportBugForm({
165
165
 
166
166
  {/* Description */}
167
167
  <div>
168
- <label className="mb-1.5 block text-xs text-neutral-400">
168
+ <label className="mb-1.5 block text-sm text-neutral-400">
169
169
  Description <span className="text-red-400">*</span>
170
170
  </label>
171
171
  <ResizableTextarea
@@ -173,13 +173,13 @@ export function ReportBugForm({
173
173
  onChange={(e) => setDescription(e.target.value)}
174
174
  placeholder="Describe the issue in detail. Include steps to reproduce if applicable."
175
175
  rows={6}
176
- className="w-full px-3 py-1.5 bg-neutral-800 border border-neutral-700 rounded-lg text-sm text-neutral-200 placeholder-neutral-500 focus:outline-none focus:border-blue-500 transition-colors resize-none min-h-[120px]"
176
+ className="w-full px-3 py-1.5 bg-neutral-800 border border-neutral-700 rounded-lg text-md text-neutral-200 placeholder-neutral-500 focus:outline-none focus:border-blue-500 transition-colors resize-none min-h-[120px]"
177
177
  />
178
178
  </div>
179
179
 
180
180
  {/* Email */}
181
181
  <div>
182
- <label className="mb-1.5 block text-xs text-neutral-400">Email (optional)</label>
182
+ <label className="mb-1.5 block text-sm text-neutral-400">Email (optional)</label>
183
183
  <Input
184
184
  type="text"
185
185
  value={email}
@@ -192,7 +192,7 @@ export function ReportBugForm({
192
192
 
193
193
  {/* Screenshots */}
194
194
  <div>
195
- <label className="mb-2 block text-xs text-neutral-400">
195
+ <label className="mb-2 block text-sm text-neutral-400">
196
196
  Screenshots (optional, max 20MB total)
197
197
  </label>
198
198
  <ScreenshotUploader
@@ -208,7 +208,7 @@ export function ReportBugForm({
208
208
  <div className="flex items-center gap-2.5">
209
209
  <Checkbox checked={includeLogs} onChange={setIncludeLogs} />
210
210
  <span
211
- className="text-sm text-neutral-400 cursor-pointer select-none"
211
+ className="text-md text-neutral-400 cursor-pointer select-none"
212
212
  onClick={() => setIncludeLogs(!includeLogs)}
213
213
  >
214
214
  Include error logs
@@ -224,11 +224,11 @@ export function ReportBugForm({
224
224
  {includeLogs && capturedErrors && capturedErrors.length > 0 && (
225
225
  <div className="rounded-md border border-neutral-700 bg-neutral-900/50">
226
226
  <div className="px-3 py-2 border-b border-neutral-700">
227
- <span className="text-xs font-medium text-neutral-400">Captured Errors</span>
227
+ <span className="text-sm font-medium text-neutral-400">Captured Errors</span>
228
228
  </div>
229
229
  <div className="max-h-[200px] overflow-y-auto divide-y divide-neutral-800">
230
230
  {capturedErrors.map((error) => (
231
- <div key={error.fingerprint} className="px-3 py-2 text-xs">
231
+ <div key={error.fingerprint} className="px-3 py-2 text-sm">
232
232
  <div className="flex items-start gap-2">
233
233
  {error.count > 1 && (
234
234
  <span className="shrink-0 text-orange-400 font-mono font-medium">
@@ -251,14 +251,14 @@ export function ReportBugForm({
251
251
  {/* Footer */}
252
252
  <div className="flex items-center justify-end gap-2 border-t border-neutral-700 px-4 py-3">
253
253
  {isSubmitting && submissionStatus && (
254
- <span className="text-xs text-neutral-400 mr-auto">{submissionStatus}</span>
254
+ <span className="text-sm text-neutral-400 mr-auto">{submissionStatus}</span>
255
255
  )}
256
256
  {onCancel && (
257
257
  <button
258
258
  type="button"
259
259
  onClick={onCancel}
260
260
  disabled={isSubmitting}
261
- className="rounded-md border border-neutral-700 bg-transparent px-3 py-1.5 text-sm text-neutral-400 transition-colors hover:bg-neutral-700 hover:text-neutral-200 disabled:opacity-50"
261
+ className="rounded-md border border-neutral-700 bg-transparent px-3 py-1.5 text-md text-neutral-400 transition-colors hover:bg-neutral-700 hover:text-neutral-200 disabled:opacity-50"
262
262
  >
263
263
  Cancel
264
264
  </button>
@@ -267,7 +267,7 @@ export function ReportBugForm({
267
267
  type="button"
268
268
  onClick={handleSubmit}
269
269
  disabled={!canSubmit}
270
- className="flex items-center gap-1.5 rounded-md bg-blue-600 px-3 py-1.5 text-sm text-white transition-colors hover:bg-blue-500 disabled:cursor-not-allowed disabled:opacity-50"
270
+ className="flex items-center gap-1.5 rounded-md bg-blue-600 px-3 py-1.5 text-md text-white transition-colors hover:bg-blue-500 disabled:cursor-not-allowed disabled:opacity-50"
271
271
  >
272
272
  {isSubmitting ? (
273
273
  <Loader2 className="w-3.5 h-3.5 animate-spin" />
@@ -158,10 +158,10 @@ export function ScreenshotUploader({
158
158
  >
159
159
  <ImagePlus className={cn('w-6 h-6', isDragging ? 'text-blue-400' : 'text-neutral-500')} />
160
160
  <div className="text-center">
161
- <p className="text-sm text-neutral-400">
161
+ <p className="text-md text-neutral-400">
162
162
  {isDragging ? 'Drop images here' : 'Click or drag images to attach'}
163
163
  </p>
164
- <p className="text-xs text-neutral-500 mt-1">
164
+ <p className="text-sm text-neutral-500 mt-1">
165
165
  {formatFileSize(remainingSize)} remaining of {formatFileSize(maxTotalSize)}
166
166
  </p>
167
167
  </div>
@@ -177,7 +177,7 @@ export function ScreenshotUploader({
177
177
  </div>
178
178
 
179
179
  {error && (
180
- <div className="flex items-center gap-2 text-sm text-red-400">
180
+ <div className="flex items-center gap-2 text-md text-red-400">
181
181
  <AlertCircle className="w-4 h-4 flex-shrink-0" />
182
182
  <span>{error}</span>
183
183
  </div>
@@ -207,9 +207,9 @@ export function ScreenshotUploader({
207
207
  >
208
208
  <X className="w-3 h-3" />
209
209
  </button>
210
- <span className="text-xs text-white truncate">{s.filename}</span>
210
+ <span className="text-sm text-white truncate">{s.filename}</span>
211
211
  </div>
212
- <div className="absolute bottom-1 right-1 px-1.5 py-0.5 bg-[var(--background)]/70 rounded text-xs text-neutral-400">
212
+ <div className="absolute bottom-1 right-1 px-1.5 py-0.5 bg-[var(--background)]/70 rounded text-sm text-neutral-400">
213
213
  {formatFileSize(s.size)}
214
214
  </div>
215
215
  </div>
@@ -218,7 +218,7 @@ export function ScreenshotUploader({
218
218
  )}
219
219
 
220
220
  {screenshots.length > 0 && (
221
- <div className="flex items-center justify-between text-xs text-neutral-500">
221
+ <div className="flex items-center justify-between text-sm text-neutral-500">
222
222
  <span>{screenshots.length} image{screenshots.length !== 1 ? 's' : ''} attached</span>
223
223
  <span>Total: {formatFileSize(totalSize)}</span>
224
224
  </div>
@@ -75,7 +75,7 @@ export function SnapshotBrowserPanel({
75
75
  <div className="flex items-center justify-between">
76
76
  <div>
77
77
  <label className="text-neutral-300">Snapshot Limit</label>
78
- <p className="text-sm text-neutral-500">
78
+ <p className="text-md text-neutral-500">
79
79
  Maximum number of snapshots to keep per item (1-50)
80
80
  </p>
81
81
  </div>
@@ -92,7 +92,7 @@ export function SnapshotBrowserPanel({
92
92
  <div className="flex items-center justify-between mb-3">
93
93
  <div>
94
94
  <label className="text-neutral-300">Browse Snapshots</label>
95
- <p className="text-sm text-neutral-500">
95
+ <p className="text-md text-neutral-500">
96
96
  {totalSnapshotCount === 0
97
97
  ? 'No snapshots saved yet'
98
98
  : `${totalSnapshotCount} snapshot${totalSnapshotCount === 1 ? '' : 's'} stored`}
@@ -129,7 +129,7 @@ export function SnapshotBrowserPanel({
129
129
  <div className="bg-neutral-900/50 border border-neutral-700 rounded-lg p-4">
130
130
  <div className="flex items-start gap-3">
131
131
  <HelpCircle className="w-4 h-4 text-neutral-500 mt-0.5 shrink-0" />
132
- <div className="text-sm text-neutral-500 space-y-2">
132
+ <div className="text-md text-neutral-500 space-y-2">
133
133
  <p>
134
134
  <strong className="text-neutral-400">How snapshots work:</strong>
135
135
  </p>
@@ -194,14 +194,14 @@ function SnapshotEntryRow({
194
194
 
195
195
  return (
196
196
  <div
197
- className="flex items-center gap-2 px-2 py-1.5 text-sm rounded-md group transition-colors hover:bg-neutral-700/50 text-neutral-400"
197
+ className="flex items-center gap-2 px-2 py-1.5 text-md rounded-md group transition-colors hover:bg-neutral-700/50 text-neutral-400"
198
198
  style={{ paddingLeft: `${depth * 16 + 8}px` }}
199
199
  >
200
200
  <Clock className="w-3 h-3 shrink-0 text-neutral-500" />
201
- <span className="text-xs flex-1 truncate">
201
+ <span className="text-sm flex-1 truncate">
202
202
  {searchQuery ? highlightMatch(displayName, searchQuery) : displayName}
203
203
  </span>
204
- <span className="text-xss text-neutral-500 shrink-0" title={formatFullDate(entry.savedAt)}>
204
+ <span className="text-xs text-neutral-500 shrink-0" title={formatFullDate(entry.savedAt)}>
205
205
  {formatRelativeTime(entry.savedAt)}
206
206
  </span>
207
207
  <IconButton
@@ -248,7 +248,7 @@ function ExpandableNode({
248
248
  <div>
249
249
  <button
250
250
  onClick={() => onToggle(path)}
251
- className="w-full flex items-center gap-2 px-2 py-1.5 text-sm rounded-md transition-colors cursor-pointer text-neutral-400 hover:bg-neutral-700/50 hover:text-neutral-300"
251
+ className="w-full flex items-center gap-2 px-2 py-1.5 text-md rounded-md transition-colors cursor-pointer text-neutral-400 hover:bg-neutral-700/50 hover:text-neutral-300"
252
252
  style={{ paddingLeft: `${depth * 16 + 8}px` }}
253
253
  >
254
254
  <span className="w-4 h-4 flex items-center justify-center shrink-0">
@@ -266,7 +266,7 @@ function ExpandableNode({
266
266
  </span>
267
267
 
268
268
  {snapshotCount > 0 && (
269
- <span className="text-xs text-neutral-500 bg-neutral-700 px-1.5 py-0.5 rounded shrink-0">
269
+ <span className="text-sm text-neutral-500 bg-neutral-700 px-1.5 py-0.5 rounded shrink-0">
270
270
  {snapshotCount}
271
271
  </span>
272
272
  )}
@@ -322,10 +322,10 @@ export function SnapshotTree({
322
322
 
323
323
  if (totalCount === 0) {
324
324
  return (
325
- <div className={cn('text-sm text-neutral-500 py-8 text-center', className)}>
325
+ <div className={cn('text-md text-neutral-500 py-8 text-center', className)}>
326
326
  No snapshots saved yet.
327
327
  <br />
328
- <span className="text-xs">Use the camera button in editors to save snapshots.</span>
328
+ <span className="text-sm">Use the camera button in editors to save snapshots.</span>
329
329
  </div>
330
330
  )
331
331
  }
@@ -377,7 +377,7 @@ export function SnapshotTree({
377
377
  {/* Tree */}
378
378
  <div className="bg-neutral-900 border border-neutral-700 rounded-lg p-2 min-h-[200px] max-h-[60vh] overflow-y-auto">
379
379
  {filteredScopes.length === 0 && searchQuery ? (
380
- <p className="text-xs text-neutral-500 text-center py-4">
380
+ <p className="text-sm text-neutral-500 text-center py-4">
381
381
  No snapshots match &ldquo;{searchQuery}&rdquo;
382
382
  </p>
383
383
  ) : (