@toolr/ui-design 0.1.8 → 0.1.9

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 (100) hide show
  1. package/ai-manifest.json +35 -20
  2. package/components/composites/dashboard-list-item.tsx +172 -0
  3. package/components/composites/dashboard-panel.tsx +218 -0
  4. package/components/content/info-panel-primitives.tsx +9 -8
  5. package/components/diagrams/diagram-utils.tsx +2 -1
  6. package/components/hooks/use-dropdown-portal.ts +39 -0
  7. package/components/lib/accent-context.ts +10 -0
  8. package/components/lib/{ai-tools.tsx → coding-agents.tsx} +23 -8
  9. package/components/lib/custom-icons.tsx +37 -0
  10. package/components/lib/git-providers.tsx +39 -0
  11. package/components/lib/theme-engine.ts +59 -10
  12. package/components/lib/toolr-brand.tsx +23 -9
  13. package/components/sections/captured-issues/captured-issues-panel.tsx +17 -8
  14. package/components/sections/{ai-tools-paths/tools-paths-panel.tsx → coding-agent-paths/agent-paths-panel.tsx} +70 -62
  15. package/components/sections/coding-agent-paths/index.ts +37 -0
  16. package/components/sections/{ai-tools-paths → coding-agent-paths}/types.ts +28 -28
  17. package/components/sections/coding-agent-paths/use-agent-paths.ts +159 -0
  18. package/components/sections/golden-snapshots/file-diff-viewer.tsx +10 -9
  19. package/components/sections/golden-snapshots/golden-sync-panel.tsx +12 -3
  20. package/components/sections/golden-snapshots/snapshot-manager.tsx +9 -7
  21. package/components/sections/golden-snapshots/status-overview.tsx +8 -8
  22. package/components/sections/golden-snapshots/version-manager.tsx +6 -6
  23. package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +3 -3
  24. package/components/sections/prompt-editor/index.ts +1 -1
  25. package/components/sections/prompt-editor/simulator-prompt-editor.tsx +13 -5
  26. package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +18 -10
  27. package/components/sections/prompt-editor/types.ts +2 -2
  28. package/components/sections/report-bug/report-bug-form.tsx +12 -4
  29. package/components/sections/report-bug/screenshot-uploader.tsx +11 -3
  30. package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +12 -4
  31. package/components/sections/snapshot-browser/snapshot-tree.tsx +5 -4
  32. package/components/sections/snapshot-browser/types.ts +1 -1
  33. package/components/sections/snippets-editor/snippets-editor.tsx +16 -9
  34. package/components/settings/SettingsHeader.tsx +2 -2
  35. package/components/settings/SettingsPanel.tsx +11 -3
  36. package/components/settings/SettingsTreeNav.tsx +15 -9
  37. package/components/ui/action-dialog.tsx +24 -30
  38. package/components/ui/ai-action-button.tsx +10 -7
  39. package/components/ui/ai-execution-action-buttons.tsx +13 -5
  40. package/components/ui/badge.tsx +7 -4
  41. package/components/ui/bottom-panel-header.tsx +9 -5
  42. package/components/ui/breadcrumb.tsx +9 -1
  43. package/components/ui/{extension-list-card.tsx → capability-list-card.tsx} +13 -5
  44. package/components/ui/checkbox.tsx +6 -3
  45. package/components/ui/collapsible-section.tsx +38 -29
  46. package/components/ui/confirm-badge.tsx +7 -4
  47. package/components/ui/cookie-consent.tsx +13 -7
  48. package/components/ui/detail-section.tsx +24 -16
  49. package/components/ui/detail-view-wrapper.tsx +30 -22
  50. package/components/ui/editor-placeholder-card.tsx +28 -24
  51. package/components/ui/editor-toolbar.tsx +7 -4
  52. package/components/ui/execution-details-panel.tsx +10 -5
  53. package/components/ui/file-structure-section.tsx +3 -3
  54. package/components/ui/file-tree.tsx +3 -1
  55. package/components/ui/files-panel.tsx +147 -27
  56. package/components/ui/filter-dropdown.tsx +84 -74
  57. package/components/ui/form-actions.tsx +14 -6
  58. package/components/ui/frontmatter-form-header.tsx +10 -2
  59. package/components/ui/icon-button.tsx +22 -9
  60. package/components/ui/input.tsx +7 -4
  61. package/components/ui/label.tsx +5 -5
  62. package/components/ui/layout-tab-bar.tsx +7 -5
  63. package/components/ui/modal.tsx +18 -4
  64. package/components/ui/nav-card.tsx +6 -3
  65. package/components/ui/navigation-bar.tsx +37 -9
  66. package/components/ui/number-input.tsx +8 -4
  67. package/components/ui/project-explorer.tsx +666 -0
  68. package/components/ui/registry-browser.tsx +12 -1
  69. package/components/ui/registry-card.tsx +49 -42
  70. package/components/ui/registry-detail.tsx +34 -11
  71. package/components/ui/resizable-textarea.tsx +18 -11
  72. package/components/ui/scope-badge.tsx +18 -11
  73. package/components/ui/segmented-toggle.tsx +5 -2
  74. package/components/ui/select.tsx +12 -9
  75. package/components/ui/selection-grid.tsx +36 -37
  76. package/components/ui/setting-row.tsx +2 -2
  77. package/components/ui/settings-card.tsx +10 -3
  78. package/components/ui/settings-info-box.tsx +9 -5
  79. package/components/ui/settings-section-title.tsx +14 -2
  80. package/components/ui/snapshot-card.tsx +10 -2
  81. package/components/ui/snippets-panel.tsx +4 -2
  82. package/components/ui/sort-dropdown.tsx +39 -29
  83. package/components/ui/status-card.tsx +9 -1
  84. package/components/ui/tab-bar.tsx +12 -9
  85. package/components/ui/toggle.tsx +13 -7
  86. package/components/ui/tooltip.tsx +9 -1
  87. package/dist/content.js +8 -8
  88. package/dist/diagrams.d.ts +0 -1
  89. package/dist/index.d.ts +421 -182
  90. package/dist/index.js +2984 -1691
  91. package/dist/tokens/primitives.css +28 -6
  92. package/dist/tokens/semantic.css +15 -15
  93. package/dist/tokens/theme.css +23 -0
  94. package/index.ts +25 -11
  95. package/package.json +1 -1
  96. package/tokens/primitives.css +28 -6
  97. package/tokens/semantic.css +15 -15
  98. package/tokens/theme.css +23 -0
  99. package/components/sections/ai-tools-paths/index.ts +0 -37
  100. package/components/sections/ai-tools-paths/use-tools-paths.ts +0 -159
@@ -28,6 +28,8 @@ import { Trash2, HelpCircle } from 'lucide-react'
28
28
  import { cn } from '../../lib/cn.ts'
29
29
  import { Select } from '../../ui/select.tsx'
30
30
  import { IconButton } from '../../ui/icon-button.tsx'
31
+ import { useAccentColor, AccentColorProvider } from '../../lib/accent-context.ts'
32
+ import type { FormColor } from '../../lib/form-colors.ts'
31
33
  import { SnapshotTree } from './snapshot-tree.tsx'
32
34
  import { useSnapshotBrowser } from './use-snapshot-browser.ts'
33
35
  import type { SnapshotScope, SnapshotBrowserApi } from './types.ts'
@@ -43,6 +45,7 @@ export interface SnapshotBrowserPanelProps {
43
45
  snapshotLimit: number
44
46
  onSnapshotLimitChange: (limit: number) => void
45
47
  onClearAll?: () => void
48
+ accentColor?: FormColor
46
49
  className?: string
47
50
  }
48
51
 
@@ -52,8 +55,11 @@ export function SnapshotBrowserPanel({
52
55
  snapshotLimit,
53
56
  onSnapshotLimitChange,
54
57
  onClearAll,
58
+ accentColor,
55
59
  className,
56
60
  }: SnapshotBrowserPanelProps) {
61
+ const contextAccent = useAccentColor()
62
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
57
63
  const {
58
64
  searchQuery,
59
65
  setSearchQuery,
@@ -69,9 +75,10 @@ export function SnapshotBrowserPanel({
69
75
  } = useSnapshotBrowser({ scopes, api })
70
76
 
71
77
  return (
78
+ <AccentColorProvider value={effectiveColor}>
72
79
  <div className={cn('space-y-6', className)}>
73
80
  {/* Snapshot Limit */}
74
- <div className="bg-neutral-900 border border-neutral-700 rounded-lg p-4">
81
+ <div className="bg-neutral-980 border border-neutral-700 rounded-lg p-4">
75
82
  <div className="flex items-center justify-between">
76
83
  <div>
77
84
  <label className="text-neutral-300">Snapshot Limit</label>
@@ -88,7 +95,7 @@ export function SnapshotBrowserPanel({
88
95
  </div>
89
96
 
90
97
  {/* Snapshot Browser */}
91
- <div className="bg-neutral-900 border border-neutral-700 rounded-lg p-4">
98
+ <div className="bg-neutral-980 border border-neutral-700 rounded-lg p-4">
92
99
  <div className="flex items-center justify-between mb-3">
93
100
  <div>
94
101
  <label className="text-neutral-300">Browse Snapshots</label>
@@ -103,7 +110,7 @@ export function SnapshotBrowserPanel({
103
110
  onClick={() => onClearAll?.()}
104
111
  disabled={totalSnapshotCount === 0 || !onClearAll}
105
112
  size="sm"
106
- color="red"
113
+ accentColor="red"
107
114
  tooltip={{
108
115
  title: 'Clear all snapshots',
109
116
  description: 'Delete all saved snapshots',
@@ -126,7 +133,7 @@ export function SnapshotBrowserPanel({
126
133
  </div>
127
134
 
128
135
  {/* Help */}
129
- <div className="bg-neutral-900/50 border border-neutral-700 rounded-lg p-4">
136
+ <div className="bg-neutral-980/50 border border-neutral-700 rounded-lg p-4">
130
137
  <div className="flex items-start gap-3">
131
138
  <HelpCircle className="w-4 h-4 text-neutral-500 mt-0.5 shrink-0" />
132
139
  <div className="text-md text-neutral-500 space-y-2">
@@ -143,5 +150,6 @@ export function SnapshotBrowserPanel({
143
150
  </div>
144
151
  </div>
145
152
  </div>
153
+ </AccentColorProvider>
146
154
  )
147
155
  }
@@ -211,7 +211,7 @@ function SnapshotEntryRow({
211
211
  onDelete(scopeId, categoryId, itemId, entry.id)
212
212
  }}
213
213
  size="sm"
214
- color="red"
214
+ accentColor="red"
215
215
  className={cn('transition-opacity', isDeleting ? 'opacity-50' : 'opacity-0 group-hover:opacity-100')}
216
216
  disabled={isDeleting}
217
217
  tooltip={{ title: 'Delete', description: 'Delete this snapshot' }}
@@ -322,6 +322,7 @@ export function SnapshotTree({
322
322
 
323
323
  if (totalCount === 0) {
324
324
  return (
325
+ // py-8: empty state needs extra vertical breathing room beyond the p-6 scale max
325
326
  <div className={cn('text-md text-neutral-500 py-8 text-center', className)}>
326
327
  No snapshots saved yet.
327
328
  <br />
@@ -352,7 +353,7 @@ export function SnapshotTree({
352
353
  <IconButton
353
354
  icon={<X className="w-3.5 h-3.5" />}
354
355
  onClick={() => onSearchChange('')}
355
- color="neutral"
356
+ accentColor="neutral"
356
357
  size="xss"
357
358
  className="absolute right-2 top-1/2 -translate-y-1/2"
358
359
  tooltip={{ title: 'Clear', description: 'Clear search' }}
@@ -365,7 +366,7 @@ export function SnapshotTree({
365
366
  icon={allExpanded ? <ChevronsDownUp className="w-3.5 h-3.5" /> : <ChevronsUpDown className="w-3.5 h-3.5" />}
366
367
  onClick={allExpanded ? onCollapseAll : onExpandAll}
367
368
  size="sm"
368
- color="neutral"
369
+ accentColor="neutral"
369
370
  tooltip={{
370
371
  title: allExpanded ? 'Collapse All' : 'Expand All',
371
372
  description: allExpanded ? 'Collapse all sections' : 'Expand all sections',
@@ -375,7 +376,7 @@ export function SnapshotTree({
375
376
  </div>
376
377
 
377
378
  {/* Tree */}
378
- <div className="bg-neutral-900 border border-neutral-700 rounded-lg p-2 min-h-[200px] max-h-[60vh] overflow-y-auto">
379
+ <div className="bg-neutral-980 border border-neutral-700 rounded-lg p-2 min-h-[200px] max-h-[60vh] overflow-y-auto">
379
380
  {filteredScopes.length === 0 && searchQuery ? (
380
381
  <p className="text-sm text-neutral-500 text-center py-4">
381
382
  No snapshots match &ldquo;{searchQuery}&rdquo;
@@ -49,7 +49,7 @@ export interface SnapshotCategory {
49
49
  items: SnapshotItem[]
50
50
  }
51
51
 
52
- /** A top-level scope containing categories (e.g. "Settings", "Extensions") */
52
+ /** A top-level scope containing categories (e.g. "Settings", "Capabilities") */
53
53
  export interface SnapshotScope {
54
54
  id: string
55
55
  name: string
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Part of: Sections > Snippets Editor
5
5
  *
6
- * Replicates the configr "Settings > Extensions > [type] > Snippets" page
6
+ * Replicates the configr "Settings > Capabilities > [type] > Snippets" page
7
7
  * as a reusable, self-contained component. Left column shows the snippet
8
8
  * list with search; right column shows the editor form.
9
9
  *
@@ -25,10 +25,12 @@
25
25
  import { useResizableSidebar } from '../../hooks/use-resizable-sidebar.ts'
26
26
  import { Plus, X, Braces, Trash2, RotateCcw, Save } from 'lucide-react'
27
27
  import { ACCENT_TEXT, type AccentColor } from '../../lib/form-colors.ts'
28
+ import type { FormColor } from '../../lib/form-colors.ts'
28
29
  import { cn } from '../../lib/cn.ts'
29
30
  import { Input } from '../../ui/input.tsx'
30
31
  import { ResizableTextarea } from '../../ui/resizable-textarea.tsx'
31
32
  import { IconButton } from '../../ui/icon-button.tsx'
33
+ import { useAccentColor, AccentColorProvider } from '../../lib/accent-context.ts'
32
34
  import { useSnippetsEditor } from './use-snippets-editor.ts'
33
35
  import type { SnippetData, SnippetsEditorApi } from './types.ts'
34
36
 
@@ -40,7 +42,7 @@ export interface SnippetsEditorProps {
40
42
  /** Section description, e.g. "Define snippets to reuse in skills prompts..." */
41
43
  description?: string
42
44
  className?: string
43
- accentColor?: string
45
+ accentColor?: FormColor
44
46
  }
45
47
 
46
48
  const ACCENT_DIVIDER_HOVER: Record<string, string> = {
@@ -90,8 +92,10 @@ export function SnippetsEditor({
90
92
  title = 'Snippets',
91
93
  description = 'Define reusable snippets for prompts using {{SNIPPET_NAME}} syntax.',
92
94
  className,
93
- accentColor = 'blue',
95
+ accentColor: accentColorProp,
94
96
  }: SnippetsEditorProps) {
97
+ const contextAccent = useAccentColor()
98
+ const accentColor = accentColorProp ?? contextAccent ?? 'blue'
95
99
  const {
96
100
  selectedName,
97
101
  selectSnippet,
@@ -119,7 +123,8 @@ export function SnippetsEditor({
119
123
  )
120
124
 
121
125
  return (
122
- <div className={cn('flex flex-col bg-neutral-900 border border-neutral-700 rounded-lg overflow-hidden', className)}>
126
+ <AccentColorProvider value={accentColor}>
127
+ <div className={cn('flex flex-col bg-neutral-980 border border-neutral-700 rounded-lg overflow-hidden', className)}>
123
128
  {/* Header */}
124
129
  <div className="flex items-center justify-between px-4 py-3 border-b border-neutral-700 bg-purple-500/5">
125
130
  <div className="flex items-center gap-2">
@@ -151,7 +156,7 @@ export function SnippetsEditor({
151
156
  icon={<Plus className="w-3.5 h-3.5" />}
152
157
  onClick={startAdd}
153
158
  size="xs"
154
- color="blue"
159
+ accentColor="cyan"
155
160
  tooltip={{ title: 'Add Snippet', description: 'Create a new snippet' }}
156
161
  disabled={isAdding}
157
162
  />
@@ -160,6 +165,7 @@ export function SnippetsEditor({
160
165
  {/* Snippet list */}
161
166
  <div className="flex-1 overflow-y-auto">
162
167
  {filteredSnippets.length === 0 && !isAdding && (
168
+ // py-10: empty state needs extra vertical breathing room beyond the p-6 scale max
163
169
  <div className="text-center py-10 px-4">
164
170
  <Braces className="w-8 h-8 mx-auto text-purple-400/40 mb-3" />
165
171
  <p className="text-sm text-neutral-500 mb-1">
@@ -222,6 +228,7 @@ export function SnippetsEditor({
222
228
  </div>
223
229
  </div>
224
230
  </div>
231
+ </AccentColorProvider>
225
232
  )
226
233
  }
227
234
 
@@ -243,8 +250,8 @@ function SnippetListItem({ snippet, selected, onSelect, onDelete, accentColor }:
243
250
  className={cn(
244
251
  'group flex items-start gap-2 px-3 py-2.5 cursor-pointer transition-colors border-l-2',
245
252
  selected
246
- ? `bg-neutral-850 ${ACCENT_BORDER[accentColor] ?? ACCENT_BORDER.blue}`
247
- : 'border-l-transparent hover:bg-neutral-850/50',
253
+ ? `bg-neutral-970 ${ACCENT_BORDER[accentColor] ?? ACCENT_BORDER.blue}`
254
+ : 'border-l-transparent hover:bg-neutral-970/50',
248
255
  )}
249
256
  onClick={onSelect}
250
257
  >
@@ -366,7 +373,7 @@ function SnippetForm({
366
373
  icon={<Trash2 className="w-3.5 h-3.5" />}
367
374
  onClick={onDelete}
368
375
  size="sm"
369
- color="red"
376
+ accentColor="red"
370
377
  disabled={isSaving}
371
378
  tooltip={{ title: 'Delete', description: 'Remove this snippet' }}
372
379
  />
@@ -387,7 +394,7 @@ function SnippetForm({
387
394
  icon={<RotateCcw className="w-3.5 h-3.5" />}
388
395
  onClick={onReset}
389
396
  size="sm"
390
- color="neutral"
397
+ accentColor="orange"
391
398
  disabled={isSaving}
392
399
  tooltip={{ title: 'Reset', description: 'Revert to saved values' }}
393
400
  />
@@ -49,7 +49,7 @@ export function SettingsHeader({
49
49
 
50
50
  return (
51
51
  <div
52
- className={`bg-neutral-950 border border-neutral-700 border-l-2 ${styles.border} rounded-lg p-4 flex items-center gap-4`}
52
+ className={`bg-neutral-990 border border-neutral-700 border-l-2 ${styles.border} rounded-lg p-4 flex items-center gap-4`}
53
53
  >
54
54
  <div className={`flex-shrink-0 w-8 h-8 rounded-lg ${styles.iconBg} flex items-center justify-center`}>
55
55
  {icon ?? <Info className={`w-4 h-4 ${styles.iconColor}`} />}
@@ -62,7 +62,7 @@ export function SettingsHeader({
62
62
  icon={<RotateCcw className="w-4 h-4" />}
63
63
  onClick={onReset}
64
64
  size="sm"
65
- color="orange"
65
+ accentColor="orange"
66
66
  tooltip={
67
67
  resetTooltip ?? {
68
68
  title: 'Reset to Defaults',
@@ -1,20 +1,28 @@
1
1
  import type { ReactNode } from 'react'
2
2
  import { SettingsTreeNav } from './SettingsTreeNav.tsx'
3
3
  import type { SettingsTreeNode } from './settings-tree-utils.ts'
4
+ import type { FormColor } from '../lib/form-colors.ts'
5
+ import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
4
6
 
5
7
  export interface SettingsPanelProps {
6
8
  tree: SettingsTreeNode[]
7
9
  selectedPath: string
8
10
  onSelectPath: (path: string) => void
9
11
  children: ReactNode
12
+ accentColor?: FormColor
10
13
  }
11
14
 
12
- export function SettingsPanel({ tree, selectedPath, onSelectPath, children }: SettingsPanelProps) {
15
+ export function SettingsPanel({ tree, selectedPath, onSelectPath, children, accentColor }: SettingsPanelProps) {
16
+ const contextAccent = useAccentColor()
17
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
18
+
13
19
  return (
14
20
  <div className="flex-1 flex overflow-hidden">
15
- <SettingsTreeNav tree={tree} selectedPath={selectedPath} onSelectPath={onSelectPath} />
21
+ <SettingsTreeNav tree={tree} selectedPath={selectedPath} onSelectPath={onSelectPath} accentColor={effectiveColor} />
16
22
  <div className="flex-1 overflow-y-auto">
17
- {children}
23
+ <AccentColorProvider value={effectiveColor}>
24
+ {children}
25
+ </AccentColorProvider>
18
26
  </div>
19
27
  </div>
20
28
  )
@@ -9,12 +9,16 @@ import {
9
9
  getParentPaths,
10
10
  filterTree,
11
11
  } from './settings-tree-utils.ts'
12
+ import type { FormColor } from '../lib/form-colors.ts'
13
+ import { useAccentColor } from '../lib/accent-context.ts'
12
14
 
13
15
  export interface SettingsTreeNavProps {
14
16
  tree: SettingsTreeNode[]
15
17
  selectedPath: string
16
18
  onSelectPath: (path: string) => void
17
- accentColor?: string
19
+ accentColor?: FormColor
20
+ className?: string
21
+ searchPlaceholder?: string
18
22
  }
19
23
 
20
24
  const ACCENT_SELECTED: Record<string, string> = {
@@ -90,7 +94,7 @@ function TreeNode({
90
94
  className={`
91
95
  w-full flex items-center gap-2 px-2 py-1.5 text-md rounded-md transition-colors cursor-pointer
92
96
  ${isSelected ? ACCENT_SELECTED[accentColor] ?? ACCENT_SELECTED.blue : isInSelectedPath ? 'text-neutral-300' : 'text-neutral-400'}
93
- ${!isSelected && 'hover:bg-neutral-800 hover:text-neutral-200'}
97
+ ${!isSelected && 'hover:bg-neutral-960 hover:text-neutral-200'}
94
98
  `}
95
99
  style={{ paddingLeft: `${depth * 12 + 8}px` }}
96
100
  >
@@ -135,7 +139,9 @@ function TreeNode({
135
139
  )
136
140
  }
137
141
 
138
- export function SettingsTreeNav({ tree, selectedPath, onSelectPath, accentColor = 'blue' }: SettingsTreeNavProps) {
142
+ export function SettingsTreeNav({ tree, selectedPath, onSelectPath, accentColor, className, searchPlaceholder }: SettingsTreeNavProps) {
143
+ const contextAccent = useAccentColor()
144
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
139
145
  const [expandedPaths, setExpandedPaths] = useState<Set<string>>(() => {
140
146
  const expanded = new Set<string>()
141
147
  if (selectedPath) {
@@ -203,12 +209,12 @@ export function SettingsTreeNav({ tree, selectedPath, onSelectPath, accentColor
203
209
  }
204
210
 
205
211
  return (
206
- <nav className="w-56 flex-shrink-0 bg-neutral-900 border-r border-neutral-800 flex flex-col overflow-hidden">
207
- <div className="p-2 border-b border-neutral-800">
212
+ <nav className={className ?? "w-56 flex-shrink-0 bg-neutral-980 border-r border-neutral-960 flex flex-col overflow-hidden"}>
213
+ <div className="p-2 border-b border-neutral-960">
208
214
  <div className="flex items-center gap-1">
209
215
  <Input
210
216
  type="search"
211
- placeholder="Search..."
217
+ placeholder={searchPlaceholder ?? "Search..."}
212
218
  value={searchQuery}
213
219
  onChange={setSearchQuery}
214
220
  size="sm"
@@ -220,7 +226,7 @@ export function SettingsTreeNav({ tree, selectedPath, onSelectPath, accentColor
220
226
  icon={<ChevronsUpDown className="w-3.5 h-3.5" />}
221
227
  onClick={handleExpandAll}
222
228
  size="sm"
223
- color="neutral"
229
+ accentColor="neutral"
224
230
  tooltipPosition="left"
225
231
  tooltip={{
226
232
  title: 'Expand All',
@@ -233,7 +239,7 @@ export function SettingsTreeNav({ tree, selectedPath, onSelectPath, accentColor
233
239
  icon={<ChevronsDownUp className="w-3.5 h-3.5" />}
234
240
  onClick={handleCollapseAll}
235
241
  size="sm"
236
- color="neutral"
242
+ accentColor="neutral"
237
243
  tooltipPosition="left"
238
244
  tooltip={{
239
245
  title: 'Collapse All',
@@ -261,7 +267,7 @@ export function SettingsTreeNav({ tree, selectedPath, onSelectPath, accentColor
261
267
  onToggleExpand={handleToggleExpand}
262
268
  depth={0}
263
269
  searchQuery={searchQuery}
264
- accentColor={accentColor}
270
+ accentColor={effectiveColor}
265
271
  />
266
272
  ))
267
273
  )}
@@ -21,6 +21,8 @@ import { SelectionGrid, type SelectionCardItem, type CodingToolPresetConfig } fr
21
21
  import { ExecutionDetailsPanel } from './execution-details-panel.tsx'
22
22
  import type { DetailRow } from './detail-section.tsx'
23
23
  import { cn } from '../lib/cn.ts'
24
+ import type { FormColor } from '../lib/form-colors.ts'
25
+ import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
24
26
 
25
27
  export interface ActionDialogProps {
26
28
  /** Dialog title */
@@ -49,11 +51,11 @@ export interface ActionDialogProps {
49
51
  statusText?: string
50
52
 
51
53
  // ── Selection section (optional) ─────────────────────────────────────────
52
- /** Label above the selection grid (e.g. "AI Tool:") */
54
+ /** Label above the selection grid (e.g. "Coding Agent:") */
53
55
  selectionLabel?: string
54
56
  /** Custom selection items - renders SelectionGrid when provided */
55
57
  items?: SelectionCardItem[]
56
- /** Built-in AI tool presets. Ignored when `items` is provided. */
58
+ /** Built-in coding agent presets. Ignored when `items` is provided. */
57
59
  presets?: CodingToolPresetConfig[]
58
60
  /** Currently selected item IDs */
59
61
  selectedIds?: string[]
@@ -95,6 +97,8 @@ export interface ActionDialogProps {
95
97
  children?: React.ReactNode
96
98
  /** Additional className */
97
99
  className?: string
100
+ /** Accent color for themed sub-components */
101
+ accentColor?: FormColor
98
102
  }
99
103
 
100
104
  export function ActionDialog({
@@ -130,7 +134,10 @@ export function ActionDialog({
130
134
  executionWarning,
131
135
  children,
132
136
  className,
137
+ accentColor: accentColorProp,
133
138
  }: ActionDialogProps) {
139
+ const contextAccent = useAccentColor()
140
+ const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
134
141
  const dialogRef = useRef<HTMLDivElement>(null)
135
142
  const titleId = useId()
136
143
 
@@ -141,18 +148,8 @@ export function ActionDialog({
141
148
  const hasScenarios = scenarios && scenarios.length > 0 && selectedScenarioIds && onSelectScenarios
142
149
  const hasExecutionDetails = executionDetails.length > 0 || onAllowDirectEditsChange || executionWarning
143
150
 
144
- const allScenariosSelected = hasScenarios && selectedScenarioIds!.length === scenarios!.filter(s => !s.disabled).length
145
-
146
- function handleSelectAllScenarios() {
147
- if (!scenarios || !onSelectScenarios) return
148
- if (allScenariosSelected) {
149
- onSelectScenarios([])
150
- } else {
151
- onSelectScenarios(scenarios.filter(s => !s.disabled).map(s => s.id))
152
- }
153
- }
154
-
155
151
  return createPortal(
152
+ <AccentColorProvider value={effectiveColor}>
156
153
  <div className="fixed inset-0 z-50 flex items-center justify-center">
157
154
  <div className="absolute inset-0 bg-[var(--dialog-backdrop)]" onClick={onCancel} aria-hidden="true" />
158
155
  <div
@@ -161,13 +158,13 @@ export function ActionDialog({
161
158
  aria-modal="true"
162
159
  aria-labelledby={titleId}
163
160
  className={cn(
164
- 'relative bg-neutral-950 border border-neutral-700 rounded-xl shadow-lg w-full max-w-[800px] mx-4 flex flex-col',
161
+ 'relative bg-neutral-990 border border-neutral-700 rounded-lg shadow-lg w-full max-w-[800px] mx-4 flex flex-col',
165
162
  'max-h-[80vh]',
166
163
  className,
167
164
  )}
168
165
  >
169
166
  {/* Header */}
170
- <div className="flex items-center gap-3 px-4 py-3 border-b border-neutral-700 flex-shrink-0">
167
+ <div className="flex items-center gap-3 px-4 py-3 flex-shrink-0">
171
168
  {Icon && (
172
169
  <Icon
173
170
  className="w-4 h-4 flex-shrink-0"
@@ -187,7 +184,7 @@ export function ActionDialog({
187
184
  <IconButton
188
185
  icon="settings"
189
186
  size="sm"
190
- color="neutral"
187
+ accentColor="neutral"
191
188
  onClick={onSettings}
192
189
  tooltip={{ description: 'Open settings' }}
193
190
  />
@@ -200,7 +197,7 @@ export function ActionDialog({
200
197
  {hasSelection && (
201
198
  <div>
202
199
  {selectionLabel && (
203
- <div className="text-sm text-neutral-500 mb-2">{selectionLabel}</div>
200
+ <div className="font-medium text-neutral-400 text-md mb-2">{selectionLabel}</div>
204
201
  )}
205
202
  <SelectionGrid
206
203
  items={items}
@@ -217,16 +214,7 @@ export function ActionDialog({
217
214
  {/* Built-in SelectionGrid for scenarios */}
218
215
  {hasScenarios && (
219
216
  <div>
220
- <div className="flex items-center justify-between mb-2">
221
- <span className="text-sm text-neutral-500">{scenarioLabel}</span>
222
- <button
223
- type="button"
224
- onClick={handleSelectAllScenarios}
225
- className="text-sm text-blue-400 hover:text-blue-300 transition-colors cursor-pointer"
226
- >
227
- {allScenariosSelected ? 'Deselect All' : 'Select All'}
228
- </button>
229
- </div>
217
+ <div className="font-medium text-neutral-400 text-md mb-2">{scenarioLabel}</div>
230
218
  <SelectionGrid
231
219
  items={scenarios}
232
220
  selectedIds={selectedScenarioIds}
@@ -260,14 +248,20 @@ export function ActionDialog({
260
248
  onCancel={onCancel}
261
249
  cancelTooltip="Close this dialog"
262
250
  onConfirm={onSubmit}
263
- confirmTooltip={submitLabel}
251
+ confirmTooltip={
252
+ hasScenarios && selectedScenarioIds!.length === 0
253
+ ? 'Select at least one scenario to run'
254
+ : submitLabel
255
+ }
264
256
  confirmIcon={submitIcon}
265
257
  confirmColor={submitColor as 'blue'}
266
- confirmDisabled={submitDisabled}
258
+ confirmDisabled={submitDisabled || (hasScenarios && selectedScenarioIds!.length === 0)}
259
+ border={false}
267
260
  />
268
261
  </div>
269
262
  </div>
270
- </div>,
263
+ </div>
264
+ </AccentColorProvider>,
271
265
  document.body,
272
266
  )
273
267
  }
@@ -1,5 +1,6 @@
1
1
  import { useMemo } from 'react'
2
2
  import { IconButton, type IconButtonProps, type IconName } from './icon-button.tsx'
3
+ import { useAccentColor } from '../lib/accent-context.ts'
3
4
 
4
5
  export type AiActionStatus = 'idle' | 'running' | 'completed'
5
6
  export type AiCompletionResult = 'success' | 'partial' | 'error'
@@ -32,16 +33,16 @@ export interface AiActionButtonProps {
32
33
  disabledReason?: string
33
34
 
34
35
  /** Color in default state */
35
- color?: IconButtonProps['color']
36
+ accentColor?: IconButtonProps['accentColor']
36
37
  /** Color when completed successfully (defaults to 'green') */
37
- completedColor?: IconButtonProps['color']
38
+ completedColor?: IconButtonProps['accentColor']
38
39
 
39
40
  size?: IconButtonProps['size']
40
41
  className?: string
41
42
  testId?: string
42
43
  }
43
44
 
44
- const COMPLETION_COLORS: Record<AiCompletionResult, IconButtonProps['color']> = {
45
+ const COMPLETION_COLORS: Record<AiCompletionResult, IconButtonProps['accentColor']> = {
45
46
  success: 'green',
46
47
  partial: 'amber',
47
48
  error: 'red',
@@ -65,12 +66,14 @@ export function AiActionButton({
65
66
  onClick,
66
67
  disabled: forceDisabled = false,
67
68
  disabledReason,
68
- color = 'neutral',
69
+ accentColor,
69
70
  completedColor = 'green',
70
71
  size = 'sm',
71
72
  className,
72
73
  testId,
73
74
  }: AiActionButtonProps) {
75
+ const contextAccent = useAccentColor()
76
+ const effectiveAccent = accentColor ?? contextAccent ?? 'purple'
74
77
  const isRunning = status === 'running'
75
78
  const isCompleted = status === 'completed'
76
79
 
@@ -90,8 +93,8 @@ export function AiActionButton({
90
93
  ? completedColor
91
94
  : COMPLETION_COLORS[completionResult]
92
95
  }
93
- return color
94
- }, [isCompleted, completionResult, completedColor, color])
96
+ return effectiveAccent
97
+ }, [isCompleted, completionResult, completedColor, effectiveAccent])
95
98
 
96
99
  const resolvedTooltip = useMemo(() => {
97
100
  if (forceDisabled && disabledReason) {
@@ -122,7 +125,7 @@ export function AiActionButton({
122
125
  return (
123
126
  <IconButton
124
127
  icon={resolvedIcon}
125
- color={resolvedColor}
128
+ accentColor={resolvedColor}
126
129
  size={size}
127
130
  disabled={isDisabled}
128
131
  onClick={isDisabled ? () => {} : onClick}
@@ -1,4 +1,6 @@
1
1
  import { IconButton, type IconName } from './icon-button.tsx'
2
+ import type { FormColor } from '../lib/form-colors.ts'
3
+ import { useAccentColor } from '../lib/accent-context.ts'
2
4
 
3
5
  export type ExecutionStatus = 'running' | 'success' | 'partial' | 'error'
4
6
 
@@ -17,6 +19,8 @@ export interface AiExecutionActionButtonsProps {
17
19
  status?: ExecutionStatus
18
20
  /** Prefix for test IDs (e.g., 'scan-modal' results in 'scan-modal-minimize-btn') */
19
21
  testIdPrefix?: string
22
+ /** Accent color for themed sub-components */
23
+ accentColor?: FormColor
20
24
  }
21
25
 
22
26
  export function AiExecutionActionButtons({
@@ -27,8 +31,12 @@ export function AiExecutionActionButtons({
27
31
  onClose,
28
32
  status = 'success',
29
33
  testIdPrefix,
34
+ accentColor,
30
35
  }: AiExecutionActionButtonsProps) {
31
- const getDoneButtonConfig = (): { icon: IconName; color: 'neutral' | 'blue' | 'red' | 'amber'; tooltip: { title?: string; description: string } } => {
36
+ const contextAccent = useAccentColor()
37
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
38
+
39
+ const getDoneButtonConfig = (): { icon: IconName; color: FormColor; tooltip: { title?: string; description: string } } => {
32
40
  if (!allDone) {
33
41
  return {
34
42
  icon: 'check-circle',
@@ -52,7 +60,7 @@ export function AiExecutionActionButtons({
52
60
  default:
53
61
  return {
54
62
  icon: 'check-circle',
55
- color: 'blue',
63
+ color: effectiveColor,
56
64
  tooltip: { description: 'All scans completed successfully' },
57
65
  }
58
66
  }
@@ -69,7 +77,7 @@ export function AiExecutionActionButtons({
69
77
  icon="minus"
70
78
  onClick={onMinimize}
71
79
  size="sm"
72
- color="neutral"
80
+ accentColor="neutral"
73
81
  tooltip={{ description: 'Hide modal. Process will continue in background.' }}
74
82
  tooltipPosition="top"
75
83
  testId={testIdPrefix ? `${testIdPrefix}-minimize-btn` : undefined}
@@ -81,7 +89,7 @@ export function AiExecutionActionButtons({
81
89
  icon="stop-circle"
82
90
  onClick={onCancel}
83
91
  size="sm"
84
- color="red"
92
+ accentColor="red"
85
93
  tooltip={{ description: 'Stop and kill the running process' }}
86
94
  tooltipPosition="top"
87
95
  testId={testIdPrefix ? `${testIdPrefix}-cancel-btn` : undefined}
@@ -96,7 +104,7 @@ export function AiExecutionActionButtons({
96
104
  onClick={onClose}
97
105
  disabled={isRunning && !allDone}
98
106
  size="sm"
99
- color={doneConfig.color}
107
+ accentColor={doneConfig.color}
100
108
  tooltip={doneConfig.tooltip}
101
109
  tooltipPosition="top"
102
110
  testId={testIdPrefix ? `${testIdPrefix}-close-btn` : undefined}
@@ -15,13 +15,14 @@
15
15
 
16
16
  import { memo } from 'react'
17
17
  import { FORM_COLORS, type AccentColor } from '../lib/form-colors.ts'
18
+ import { useAccentColor } from '../lib/accent-context.ts'
18
19
  import { cn } from '../lib/cn.ts'
19
20
 
20
21
  export type BadgeColor = AccentColor
21
22
 
22
23
  export interface BadgeProps {
23
24
  value: number | string
24
- color?: BadgeColor
25
+ accentColor?: BadgeColor
25
26
  size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
26
27
  className?: string
27
28
  testId?: string
@@ -37,11 +38,13 @@ const sizeClasses = {
37
38
 
38
39
  export const Badge = memo(function Badge({
39
40
  value,
40
- color = 'neutral',
41
+ accentColor,
41
42
  size = 'sm',
42
43
  className,
43
44
  testId,
44
45
  }: BadgeProps) {
46
+ const contextAccent = useAccentColor()
47
+ const effectiveColor = accentColor ?? contextAccent ?? 'neutral'
45
48
  const display = typeof value === 'number' && value > 99 ? '99+' : value
46
49
 
47
50
  return (
@@ -49,8 +52,8 @@ export const Badge = memo(function Badge({
49
52
  data-testid={testId}
50
53
  className={cn(
51
54
  'inline-flex items-center justify-center border rounded-full font-medium leading-none tabular-nums',
52
- FORM_COLORS[color].border,
53
- FORM_COLORS[color].accent,
55
+ FORM_COLORS[effectiveColor].border,
56
+ FORM_COLORS[effectiveColor].accent,
54
57
  sizeClasses[size],
55
58
  className,
56
59
  )}