@toolr/ui-design 0.1.7 → 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 (101) 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/hooks/use-modal-behavior.ts +32 -3
  8. package/components/lib/accent-context.ts +10 -0
  9. package/components/lib/{ai-tools.tsx → coding-agents.tsx} +23 -8
  10. package/components/lib/custom-icons.tsx +37 -0
  11. package/components/lib/git-providers.tsx +39 -0
  12. package/components/lib/theme-engine.ts +59 -10
  13. package/components/lib/toolr-brand.tsx +23 -9
  14. package/components/sections/captured-issues/captured-issues-panel.tsx +17 -8
  15. package/components/sections/{ai-tools-paths/tools-paths-panel.tsx → coding-agent-paths/agent-paths-panel.tsx} +70 -62
  16. package/components/sections/coding-agent-paths/index.ts +37 -0
  17. package/components/sections/{ai-tools-paths → coding-agent-paths}/types.ts +28 -28
  18. package/components/sections/coding-agent-paths/use-agent-paths.ts +159 -0
  19. package/components/sections/golden-snapshots/file-diff-viewer.tsx +11 -10
  20. package/components/sections/golden-snapshots/golden-sync-panel.tsx +12 -3
  21. package/components/sections/golden-snapshots/snapshot-manager.tsx +9 -7
  22. package/components/sections/golden-snapshots/status-overview.tsx +8 -8
  23. package/components/sections/golden-snapshots/version-manager.tsx +6 -6
  24. package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +3 -3
  25. package/components/sections/prompt-editor/index.ts +1 -1
  26. package/components/sections/prompt-editor/simulator-prompt-editor.tsx +13 -5
  27. package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +18 -10
  28. package/components/sections/prompt-editor/types.ts +2 -2
  29. package/components/sections/report-bug/report-bug-form.tsx +12 -4
  30. package/components/sections/report-bug/screenshot-uploader.tsx +11 -3
  31. package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +12 -4
  32. package/components/sections/snapshot-browser/snapshot-tree.tsx +5 -4
  33. package/components/sections/snapshot-browser/types.ts +1 -1
  34. package/components/sections/snippets-editor/snippets-editor.tsx +16 -9
  35. package/components/settings/SettingsHeader.tsx +2 -2
  36. package/components/settings/SettingsPanel.tsx +11 -3
  37. package/components/settings/SettingsTreeNav.tsx +15 -9
  38. package/components/ui/action-dialog.tsx +37 -35
  39. package/components/ui/ai-action-button.tsx +12 -11
  40. package/components/ui/ai-execution-action-buttons.tsx +13 -5
  41. package/components/ui/badge.tsx +17 -6
  42. package/components/ui/bottom-panel-header.tsx +9 -5
  43. package/components/ui/breadcrumb.tsx +14 -6
  44. package/components/ui/{extension-list-card.tsx → capability-list-card.tsx} +14 -6
  45. package/components/ui/checkbox.tsx +23 -14
  46. package/components/ui/collapsible-section.tsx +38 -28
  47. package/components/ui/confirm-badge.tsx +17 -6
  48. package/components/ui/cookie-consent.tsx +13 -7
  49. package/components/ui/detail-section.tsx +24 -16
  50. package/components/ui/detail-view-wrapper.tsx +30 -22
  51. package/components/ui/editor-placeholder-card.tsx +28 -24
  52. package/components/ui/editor-toolbar.tsx +7 -4
  53. package/components/ui/execution-details-panel.tsx +10 -5
  54. package/components/ui/file-structure-section.tsx +3 -3
  55. package/components/ui/file-tree.tsx +7 -5
  56. package/components/ui/files-panel.tsx +147 -27
  57. package/components/ui/filter-dropdown.tsx +88 -75
  58. package/components/ui/form-actions.tsx +21 -11
  59. package/components/ui/frontmatter-form-header.tsx +10 -2
  60. package/components/ui/icon-button.tsx +27 -14
  61. package/components/ui/input.tsx +15 -7
  62. package/components/ui/label.tsx +9 -5
  63. package/components/ui/layout-tab-bar.tsx +11 -9
  64. package/components/ui/modal.tsx +26 -8
  65. package/components/ui/nav-card.tsx +7 -4
  66. package/components/ui/navigation-bar.tsx +40 -12
  67. package/components/ui/number-input.tsx +14 -4
  68. package/components/ui/project-explorer.tsx +666 -0
  69. package/components/ui/registry-browser.tsx +12 -1
  70. package/components/ui/registry-card.tsx +49 -42
  71. package/components/ui/registry-detail.tsx +34 -11
  72. package/components/ui/resizable-textarea.tsx +18 -11
  73. package/components/ui/scope-badge.tsx +18 -11
  74. package/components/ui/segmented-toggle.tsx +7 -2
  75. package/components/ui/select.tsx +17 -11
  76. package/components/ui/selection-grid.tsx +40 -37
  77. package/components/ui/setting-row.tsx +6 -4
  78. package/components/ui/settings-card.tsx +12 -5
  79. package/components/ui/settings-info-box.tsx +9 -6
  80. package/components/ui/settings-section-title.tsx +14 -2
  81. package/components/ui/snapshot-card.tsx +10 -2
  82. package/components/ui/snippets-panel.tsx +4 -2
  83. package/components/ui/sort-dropdown.tsx +45 -32
  84. package/components/ui/status-card.tsx +9 -1
  85. package/components/ui/tab-bar.tsx +26 -13
  86. package/components/ui/toggle.tsx +31 -17
  87. package/components/ui/tooltip.tsx +14 -6
  88. package/dist/content.js +8 -8
  89. package/dist/diagrams.d.ts +0 -1
  90. package/dist/index.d.ts +431 -186
  91. package/dist/index.js +3119 -1724
  92. package/dist/tokens/primitives.css +28 -6
  93. package/dist/tokens/semantic.css +15 -15
  94. package/dist/tokens/theme.css +23 -0
  95. package/index.ts +25 -11
  96. package/package.json +9 -1
  97. package/tokens/primitives.css +28 -6
  98. package/tokens/semantic.css +15 -15
  99. package/tokens/theme.css +23 -0
  100. package/components/sections/ai-tools-paths/index.ts +0 -37
  101. package/components/sections/ai-tools-paths/use-tools-paths.ts +0 -159
@@ -4,6 +4,8 @@ import { DebounceBorderOverlay } from './debounce-border-overlay.tsx'
4
4
  import { IconButton } from './icon-button.tsx'
5
5
  import { Input } from './input.tsx'
6
6
  import { RegistryCard, type RegistryCardProps } from './registry-card.tsx'
7
+ import type { FormColor } from '../lib/form-colors.ts'
8
+ import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
7
9
 
8
10
  const PAGE_SIZE = 60
9
11
 
@@ -50,6 +52,8 @@ export interface RegistryBrowserProps {
50
52
 
51
53
  // Grid items
52
54
  items: RegistryCardProps[]
55
+ /** Accent color for themed sub-components */
56
+ accentColor?: FormColor
53
57
  }
54
58
 
55
59
  export function RegistryBrowser({
@@ -76,7 +80,10 @@ export function RegistryBrowser({
76
80
  onScrollChange,
77
81
  maxWidth = 'max-w-[1440px]',
78
82
  items,
83
+ accentColor: accentColorProp,
79
84
  }: RegistryBrowserProps) {
85
+ const contextAccent = useAccentColor()
86
+ const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
80
87
  // Incremental rendering: render a page at a time, load more on scroll
81
88
  const totalCount = items.length
82
89
  const needsPaging = totalCount > PAGE_SIZE
@@ -152,6 +159,7 @@ export function RegistryBrowser({
152
159
  const hasMore = visibleCount < totalCount
153
160
 
154
161
  return (
162
+ <AccentColorProvider value={effectiveColor}>
155
163
  <div ref={scrollRef} className="h-full overflow-y-auto p-4 pb-8" onScroll={handleScroll}>
156
164
  {/* Toolbar */}
157
165
  <div className="sticky top-0 flex items-center gap-2 pb-4">
@@ -184,7 +192,7 @@ export function RegistryBrowser({
184
192
  icon={<ArrowRight className="w-3.5 h-3.5" />}
185
193
  onClick={onSearchSubmit}
186
194
  size="sm"
187
- color="blue"
195
+ accentColor="neutral"
188
196
  tooltip={{ description: 'Search' }}
189
197
  />
190
198
  )}
@@ -199,6 +207,7 @@ export function RegistryBrowser({
199
207
  icon={<RefreshCw className="w-3.5 h-3.5" />}
200
208
  onClick={onRefresh}
201
209
  size="sm"
210
+ accentColor="neutral"
202
211
  tooltip={refreshTooltip ?? { description: 'Refresh registry data' }}
203
212
  />
204
213
  )}
@@ -210,6 +219,7 @@ export function RegistryBrowser({
210
219
  {/* Content */}
211
220
  <div className={`${maxWidth} mx-auto relative z-10`}>
212
221
  {isLoading ? (
222
+ /* py-12: empty/loading/error states need extra vertical breathing room beyond the p-6 scale max */
213
223
  <div className="flex items-center justify-center py-12 text-neutral-500">
214
224
  <Loader2 className="w-5 h-5 animate-spin mr-2" />
215
225
  {loadingMessage}
@@ -243,5 +253,6 @@ export function RegistryBrowser({
243
253
  )}
244
254
  </div>
245
255
  </div>
256
+ </AccentColorProvider>
246
257
  )
247
258
  }
@@ -5,14 +5,16 @@ import {
5
5
  } from 'lucide-react'
6
6
  import { Tooltip } from './tooltip.tsx'
7
7
  import { IconButton, type IconName } from './icon-button.tsx'
8
+ import type { FormColor } from '../lib/form-colors.ts'
9
+ import { useAccentColor, AccentColorProvider } from '../lib/accent-context.ts'
8
10
  import { Label, type LabelColor, smartCapitalize } from './label.tsx'
9
- import { AiToolIcon, AI_TOOL_NAMES, type AiToolKey } from '../lib/ai-tools.tsx'
10
- export { AiToolIcon, AI_TOOL_NAMES, type AiToolKey }
11
+ import { CodingAgentIcon, CODING_AGENT_NAMES, type CodingAgentKey } from '../lib/coding-agents.tsx'
12
+ export { CodingAgentIcon, CODING_AGENT_NAMES, type CodingAgentKey }
11
13
 
12
14
  // ── Constants ─────────────────────────────────────────────────────────────────
13
15
 
14
16
  export type RegistryItemType = 'skill' | 'hook' | 'agent' | 'command' | 'plugin' | 'mcp' | 'settings'
15
- export type ExtensionSource = 'project' | 'plugin' | 'user' | 'local'
17
+ export type CapabilitySource = 'project' | 'plugin' | 'user' | 'local'
16
18
 
17
19
  const TYPE_ICONS: Record<string, typeof Sparkles> = {
18
20
  skill: Sparkles,
@@ -98,7 +100,7 @@ function formatCount(n?: number): string {
98
100
  // ── Scope helpers ─────────────────────────────────────────────────────────────
99
101
 
100
102
  function computeSeedrScopes(
101
- installedScopes: ReadonlySet<ExtensionSource> | undefined,
103
+ installedScopes: ReadonlySet<CapabilitySource> | undefined,
102
104
  isUserScope: boolean,
103
105
  ) {
104
106
  const isInstalledInViewScope = (() => {
@@ -107,7 +109,7 @@ function computeSeedrScopes(
107
109
  return installedScopes.has('project') || installedScopes.has('local')
108
110
  })()
109
111
 
110
- const removeScope: ExtensionSource | null = (() => {
112
+ const removeScope: CapabilitySource | null = (() => {
111
113
  if (!installedScopes || installedScopes.size === 0) return null
112
114
  if (isUserScope && installedScopes.has('user')) return 'user'
113
115
  if (!isUserScope) {
@@ -150,7 +152,7 @@ function CategoryBadge({ category, onFilter }: { category: string; onFilter?: (c
150
152
  return (
151
153
  <Label
152
154
  text={smartCapitalize(category)}
153
- color={getCategoryLabelColor(category)}
155
+ accentColor={getCategoryLabelColor(category)}
154
156
  icon="tag"
155
157
  tooltip={{ description: onFilter ? `${smartCapitalize(category)} \u00b7 Click to filter` : smartCapitalize(category) }}
156
158
  size="sm"
@@ -198,7 +200,7 @@ function RegistryCardBadgeRow({ sourceType, pluginType, category, onFilterCatego
198
200
  {sourceType && SOURCE_TYPE_BADGES[sourceType] && (
199
201
  <Label
200
202
  text={sourceType}
201
- color={SOURCE_TYPE_BADGES[sourceType].color}
203
+ accentColor={SOURCE_TYPE_BADGES[sourceType].color}
202
204
  icon={SOURCE_TYPE_BADGES[sourceType].icon}
203
205
  tooltip={{ description: `Source: ${sourceType}` }}
204
206
  size="sm"
@@ -208,7 +210,7 @@ function RegistryCardBadgeRow({ sourceType, pluginType, category, onFilterCatego
208
210
  {pluginType && PLUGIN_TYPE_BADGES[pluginType] && (
209
211
  <Label
210
212
  text={pluginType}
211
- color={PLUGIN_TYPE_BADGES[pluginType].color}
213
+ accentColor={PLUGIN_TYPE_BADGES[pluginType].color}
212
214
  icon={PLUGIN_TYPE_BADGES[pluginType].icon}
213
215
  tooltip={{ description: `Plugin type: ${pluginType}` }}
214
216
  size="sm"
@@ -219,20 +221,20 @@ function RegistryCardBadgeRow({ sourceType, pluginType, category, onFilterCatego
219
221
  {isInstalled && !isDisabled && (
220
222
  <Label
221
223
  text="Added"
222
- color="green"
224
+ accentColor="green"
223
225
  icon={scopeIcons.length > 0 ? scopeIcons : 'check'}
224
226
  tooltip={{ description: `Installed in: ${scopes.join(', ')}` }}
225
227
  size="sm"
226
228
  />
227
229
  )}
228
230
  {isDisabled && (
229
- <Label text="Disabled" color="red" icon="x" tooltip={{ description: 'Installed but disabled' }} size="sm" />
231
+ <Label text="Disabled" accentColor="red" icon="x" tooltip={{ description: 'Installed but disabled' }} size="sm" />
230
232
  )}
231
233
  {status?.updateAvailable && (
232
- <Label text="Update" color="amber" icon="download" tooltip={{ description: 'Update available' }} size="sm" />
234
+ <Label text="Update" accentColor="amber" icon="download" tooltip={{ description: 'Update available' }} size="sm" />
233
235
  )}
234
236
  {status?.modified && (
235
- <Label text="Modified" color="violet" icon="pencil" tooltip={{ description: 'Locally modified' }} size="sm" />
237
+ <Label text="Modified" accentColor="violet" icon="pencil" tooltip={{ description: 'Locally modified' }} size="sm" />
236
238
  )}
237
239
  </div>
238
240
  )
@@ -256,6 +258,7 @@ interface RegistryCardBase {
256
258
  isInstalling?: boolean
257
259
  updatedAt?: string
258
260
  onDateClick?: () => void
261
+ accentColor?: FormColor
259
262
  }
260
263
 
261
264
  interface SeedrVariant extends RegistryCardBase {
@@ -264,14 +267,14 @@ interface SeedrVariant extends RegistryCardBase {
264
267
  sourceType: string
265
268
  pluginType?: string
266
269
  wrapper?: string
267
- installedScopes?: ReadonlySet<ExtensionSource>
270
+ installedScopes?: ReadonlySet<CapabilitySource>
268
271
  viewScope?: 'user' | 'project'
269
272
  status?: SeedrItemStatus
270
273
  compatibility: string[]
271
274
  packageCounts?: Record<string, number>
272
275
  isDisabled?: boolean
273
276
  onActivate?: () => void
274
- onRemove?: (scope: ExtensionSource) => void
277
+ onRemove?: (scope: CapabilitySource) => void
275
278
  onFilterSource?: () => void
276
279
  onFilterPluginType?: (pluginType: string) => void
277
280
  /** Render scope confirmation modal */
@@ -284,7 +287,7 @@ interface ClaudePluginsVariant extends RegistryCardBase {
284
287
  category?: string
285
288
  stars?: number
286
289
  downloads?: number
287
- installedScopes?: ReadonlySet<ExtensionSource>
290
+ installedScopes?: ReadonlySet<CapabilitySource>
288
291
  viewScope?: 'user' | 'project'
289
292
  onRemove?: () => void
290
293
  onSortBy?: (field: string) => void
@@ -298,7 +301,7 @@ interface ClaudePluginsVariant extends RegistryCardBase {
298
301
  interface AitmplVariant extends RegistryCardBase {
299
302
  variant: 'aitmpl'
300
303
  category: string
301
- installedScopes?: ReadonlySet<ExtensionSource>
304
+ installedScopes?: ReadonlySet<CapabilitySource>
302
305
  viewScope?: 'user' | 'project'
303
306
  onRemove?: () => void
304
307
  onFilterCategory?: (category: string) => void
@@ -310,7 +313,7 @@ interface SkillsShVariant extends RegistryCardBase {
310
313
  variant: 'skills-sh'
311
314
  author: string
312
315
  installs?: number
313
- installedScopes?: ReadonlySet<ExtensionSource>
316
+ installedScopes?: ReadonlySet<CapabilitySource>
314
317
  viewScope?: 'user' | 'project'
315
318
  onRemove?: () => void
316
319
  /** All tool keys to show in bottom row */
@@ -324,7 +327,9 @@ export type RegistryCardProps = SeedrVariant | ClaudePluginsVariant | AitmplVari
324
327
  // ── Main Component ────────────────────────────────────────────────────────────
325
328
 
326
329
  export function RegistryCard(props: RegistryCardProps) {
327
- const { onClick, flash, name, type, description, errorMessage, isInstalling, updatedAt, onDateClick } = props
330
+ const { onClick, flash, name, type, description, errorMessage, isInstalling, updatedAt, onDateClick, accentColor: accentColorProp } = props
331
+ const contextAccent = useAccentColor()
332
+ const effectiveColor = accentColorProp ?? contextAccent ?? 'blue'
328
333
  const [showScopeConfirm, setShowScopeConfirm] = useState(false)
329
334
 
330
335
  const TypeIcon = TYPE_ICONS[type] || TYPE_ICONS.extension
@@ -363,7 +368,7 @@ export function RegistryCard(props: RegistryCardProps) {
363
368
  icon={<Power className="w-3.5 h-3.5" />}
364
369
  onClick={() => props.onActivate?.()}
365
370
  size="sm"
366
- color="green"
371
+ accentColor="green"
367
372
  tooltip={{ description: `Activate ${name}` }}
368
373
  />
369
374
  ) : isInstalledInViewScope && removeScope ? (
@@ -371,7 +376,7 @@ export function RegistryCard(props: RegistryCardProps) {
371
376
  icon={<Trash2 className="w-3.5 h-3.5" />}
372
377
  onClick={() => props.onRemove?.(removeScope)}
373
378
  size="sm"
374
- color="red"
379
+ accentColor="red"
375
380
  tooltip={{ description: `Remove ${name} from ${removeScope}` }}
376
381
  />
377
382
  ) : installedElsewhere ? (
@@ -379,7 +384,7 @@ export function RegistryCard(props: RegistryCardProps) {
379
384
  icon={<FolderPlus className="w-3.5 h-3.5" />}
380
385
  onClick={() => setShowScopeConfirm(true)}
381
386
  size="sm"
382
- color="blue"
387
+ accentColor="blue"
383
388
  tooltip={{ description: ALREADY_AT_USER }}
384
389
  />
385
390
  ) : (
@@ -387,14 +392,14 @@ export function RegistryCard(props: RegistryCardProps) {
387
392
  icon={<Plus className="w-3.5 h-3.5" />}
388
393
  onClick={props.onInstall}
389
394
  size="sm"
390
- color="green"
395
+ accentColor="green"
391
396
  tooltip={{ description: `Add ${name}` }}
392
397
  />
393
398
  )
394
399
 
395
- bottomLogos = props.compatibility.filter((t) => t in AI_TOOL_NAMES).map((tool) => (
396
- <Tooltip key={tool} content={{ description: `Compatible with ${AI_TOOL_NAMES[tool as AiToolKey] ?? tool}` }} position="top">
397
- <AiToolIcon tool={tool} size={16} />
400
+ bottomLogos = props.compatibility.filter((t) => t in CODING_AGENT_NAMES).map((tool) => (
401
+ <Tooltip key={tool} content={{ description: `Compatible with ${CODING_AGENT_NAMES[tool as CodingAgentKey] ?? tool}` }} position="top">
402
+ <CodingAgentIcon agent={tool} size={16} />
398
403
  </Tooltip>
399
404
  ))
400
405
 
@@ -434,7 +439,7 @@ export function RegistryCard(props: RegistryCardProps) {
434
439
  icon={<Trash2 className="w-3.5 h-3.5" />}
435
440
  onClick={() => props.onRemove?.()}
436
441
  size="sm"
437
- color="red"
442
+ accentColor="red"
438
443
  tooltip={{ description: `Remove ${name} from ${removeScope}` }}
439
444
  />
440
445
  ) : installedElsewhere ? (
@@ -442,7 +447,7 @@ export function RegistryCard(props: RegistryCardProps) {
442
447
  icon={<FolderPlus className="w-3.5 h-3.5" />}
443
448
  onClick={() => setShowScopeConfirm(true)}
444
449
  size="sm"
445
- color="blue"
450
+ accentColor="blue"
446
451
  tooltip={{ description: ALREADY_AT_USER }}
447
452
  />
448
453
  ) : (
@@ -450,15 +455,15 @@ export function RegistryCard(props: RegistryCardProps) {
450
455
  icon={<Plus className="w-3.5 h-3.5" />}
451
456
  onClick={props.onInstall}
452
457
  size="sm"
453
- color="green"
458
+ accentColor="green"
454
459
  tooltip={{ description: `Add ${name}` }}
455
460
  />
456
461
  )
457
462
 
458
463
  const compatibleTools = props.compatibleTools ?? []
459
464
  bottomLogos = compatibleTools.map((tool) => (
460
- <Tooltip key={tool} content={{ description: `Compatible with ${AI_TOOL_NAMES[tool as AiToolKey] ?? tool}` }} position="top">
461
- <AiToolIcon tool={tool} size={16} />
465
+ <Tooltip key={tool} content={{ description: `Compatible with ${CODING_AGENT_NAMES[tool as CodingAgentKey] ?? tool}` }} position="top">
466
+ <CodingAgentIcon agent={tool} size={16} />
462
467
  </Tooltip>
463
468
  ))
464
469
  bottomStats = [
@@ -502,7 +507,7 @@ export function RegistryCard(props: RegistryCardProps) {
502
507
  icon={<Trash2 className="w-3.5 h-3.5" />}
503
508
  onClick={() => props.onRemove?.()}
504
509
  size="sm"
505
- color="red"
510
+ accentColor="red"
506
511
  tooltip={{ description: `Remove ${name} from ${removeScope}` }}
507
512
  />
508
513
  ) : installedElsewhere ? (
@@ -510,7 +515,7 @@ export function RegistryCard(props: RegistryCardProps) {
510
515
  icon={<FolderPlus className="w-3.5 h-3.5" />}
511
516
  onClick={() => setShowScopeConfirm(true)}
512
517
  size="sm"
513
- color="blue"
518
+ accentColor="blue"
514
519
  tooltip={{ description: ALREADY_AT_USER }}
515
520
  />
516
521
  ) : (
@@ -518,14 +523,14 @@ export function RegistryCard(props: RegistryCardProps) {
518
523
  icon={<Plus className="w-3.5 h-3.5" />}
519
524
  onClick={props.onInstall}
520
525
  size="sm"
521
- color="green"
526
+ accentColor="green"
522
527
  tooltip={{ description: `Add ${name}` }}
523
528
  />
524
529
  )
525
530
 
526
531
  bottomLogos = [(
527
532
  <Tooltip key="claude" content={{ description: 'Compatible with Claude Code' }} position="top">
528
- <AiToolIcon tool="claude" size={16} />
533
+ <CodingAgentIcon agent="claude" size={16} />
529
534
  </Tooltip>
530
535
  )]
531
536
  } else if (props.variant === 'skills-sh') {
@@ -547,7 +552,7 @@ export function RegistryCard(props: RegistryCardProps) {
547
552
  icon={<Trash2 className="w-3.5 h-3.5" />}
548
553
  onClick={() => props.onRemove?.()}
549
554
  size="sm"
550
- color="red"
555
+ accentColor="red"
551
556
  tooltip={{ description: `Remove ${name} from ${removeScope}` }}
552
557
  />
553
558
  ) : installedElsewhere ? (
@@ -555,7 +560,7 @@ export function RegistryCard(props: RegistryCardProps) {
555
560
  icon={<FolderPlus className="w-3.5 h-3.5" />}
556
561
  onClick={() => setShowScopeConfirm(true)}
557
562
  size="sm"
558
- color="blue"
563
+ accentColor="blue"
559
564
  tooltip={{ description: ALREADY_AT_USER }}
560
565
  />
561
566
  ) : (
@@ -563,15 +568,15 @@ export function RegistryCard(props: RegistryCardProps) {
563
568
  icon={<Plus className="w-3.5 h-3.5" />}
564
569
  onClick={props.onInstall}
565
570
  size="sm"
566
- color="green"
571
+ accentColor="green"
567
572
  tooltip={{ description: `Add ${name}` }}
568
573
  />
569
574
  )
570
575
 
571
576
  const allTools = props.allToolKeys ?? []
572
577
  bottomLogos = allTools.map((tool) => (
573
- <Tooltip key={tool} content={{ description: `Compatible with ${AI_TOOL_NAMES[tool as AiToolKey] ?? tool}` }} position="top">
574
- <AiToolIcon tool={tool} size={14} />
578
+ <Tooltip key={tool} content={{ description: `Compatible with ${CODING_AGENT_NAMES[tool as CodingAgentKey] ?? tool}` }} position="top">
579
+ <CodingAgentIcon agent={tool} size={14} />
575
580
  </Tooltip>
576
581
  ))
577
582
  if (props.installs != null && props.installs > 0) {
@@ -597,10 +602,11 @@ export function RegistryCard(props: RegistryCardProps) {
597
602
  : 'border-neutral-700'
598
603
 
599
604
  return (
605
+ <AccentColorProvider value={effectiveColor}>
600
606
  <div
601
607
  role="button"
602
608
  tabIndex={0}
603
- className={`group text-left w-full cursor-pointer border rounded-lg p-3 transition-all duration-500 hover:border-neutral-600 flex flex-col bg-neutral-800/50 hover:bg-neutral-800 ${borderClass}`}
609
+ className={`group text-left w-full cursor-pointer border rounded-lg p-3 transition-all duration-500 hover:border-neutral-600 flex flex-col bg-neutral-960/50 hover:bg-neutral-960 ${borderClass}`}
604
610
  onClick={onClick}
605
611
  onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick() } }}
606
612
  >
@@ -614,7 +620,7 @@ export function RegistryCard(props: RegistryCardProps) {
614
620
  <Loader2 className="w-4 h-4 text-blue-400 animate-spin" />
615
621
  ) : (
616
622
  <>
617
- <Tooltip content={{ description: `${typeLabel} extension` }} position="top">
623
+ <Tooltip content={{ description: `${typeLabel} capability` }} position="top">
618
624
  <TypeIcon className={`w-4 h-4 group-hover:opacity-0 transition-opacity ${typeIconColor}`} />
619
625
  </Tooltip>
620
626
  {installButton && (
@@ -687,7 +693,7 @@ export function RegistryCard(props: RegistryCardProps) {
687
693
  return (
688
694
  <div onClick={(e) => e.stopPropagation()}>
689
695
  <div className="fixed inset-0 bg-[var(--background)]/50 z-50 flex items-center justify-center" onClick={() => setShowScopeConfirm(false)}>
690
- <div className="bg-neutral-800 border border-neutral-700 rounded-lg p-4 max-w-sm" onClick={(e) => e.stopPropagation()}>
696
+ <div className="bg-neutral-960 border border-neutral-700 rounded-lg p-4 max-w-sm" onClick={(e) => e.stopPropagation()}>
691
697
  <h3 className="text-md font-medium text-neutral-200 mb-2">{ALREADY_AT_USER}</h3>
692
698
  <p className="text-sm text-neutral-400 mb-4">
693
699
  <strong className="text-neutral-300">{name}</strong> is already installed at user level and available to all projects. Do you also want to install it at project level?
@@ -702,5 +708,6 @@ export function RegistryCard(props: RegistryCardProps) {
702
708
  )
703
709
  })()}
704
710
  </div>
711
+ </AccentColorProvider>
705
712
  )
706
713
  }
@@ -2,8 +2,10 @@ import { type ReactNode, useState, useRef, useEffect, useCallback } from 'react'
2
2
  import { ChevronsUpDown, ChevronsDownUp } from 'lucide-react'
3
3
  import { IconButton } from './icon-button.tsx'
4
4
  import { Label, type LabelProps } from './label.tsx'
5
- import { AiToolIcon, AI_TOOL_NAMES, type AiToolKey } from '../lib/ai-tools.tsx'
5
+ import { CodingAgentIcon, CODING_AGENT_NAMES, type CodingAgentKey } from '../lib/coding-agents.tsx'
6
6
  import { FileStructureSection, type FileStructureSectionProps } from './file-structure-section.tsx'
7
+ import { type AccentColor } from '../lib/form-colors.ts'
8
+ import { AccentColorProvider, useAccentColor } from '../lib/accent-context.ts'
7
9
  import type { LucideIcon } from 'lucide-react'
8
10
 
9
11
  export interface RegistryDetailProps {
@@ -35,6 +37,12 @@ export interface RegistryDetailProps {
35
37
  // Above header (e.g. badge row)
36
38
  aboveHeader?: ReactNode
37
39
 
40
+ // Below header (e.g. status messages)
41
+ belowHeader?: ReactNode
42
+
43
+ // Accent color for internal buttons and file structure
44
+ accentColor?: AccentColor
45
+
38
46
  // Max width class
39
47
  maxWidth?: string
40
48
 
@@ -42,7 +50,7 @@ export interface RegistryDetailProps {
42
50
  children?: ReactNode
43
51
  }
44
52
 
45
- const MARKDOWN_CLASSES = 'text-md text-neutral-400 leading-relaxed [&_strong]:text-neutral-200 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:bg-neutral-700/40 [&_code]:border [&_code]:border-neutral-500/40 [&_code]:text-neutral-200 [&_code]:font-mono [&_code]:text-sm [&_h1]:text-lg [&_h1]:font-semibold [&_h1]:text-neutral-200 [&_h1]:mb-2 [&_h2]:text-base [&_h2]:font-semibold [&_h2]:text-neutral-200 [&_h2]:mb-2 [&_h3]:text-md [&_h3]:font-medium [&_h3]:text-neutral-200 [&_h3]:mb-1 [&_ul]:list-disc [&_ul]:pl-4 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:mb-1 [&_p]:mb-2 [&_pre]:bg-neutral-900 [&_pre]:rounded [&_pre]:p-3 [&_pre]:overflow-x-auto [&_pre]:text-sm'
53
+ const MARKDOWN_CLASSES = 'text-md text-neutral-400 leading-relaxed [&_strong]:text-neutral-200 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:bg-neutral-700/40 [&_code]:border [&_code]:border-neutral-500/40 [&_code]:text-neutral-200 [&_code]:font-mono [&_code]:text-sm [&_h1]:text-lg [&_h1]:font-semibold [&_h1]:text-neutral-200 [&_h1]:mb-2 [&_h2]:text-base [&_h2]:font-semibold [&_h2]:text-neutral-200 [&_h2]:mb-2 [&_h3]:text-md [&_h3]:font-medium [&_h3]:text-neutral-200 [&_h3]:mb-1 [&_ul]:list-disc [&_ul]:pl-4 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:mb-1 [&_p]:mb-2 [&_pre]:bg-neutral-980 [&_pre]:rounded [&_pre]:p-3 [&_pre]:overflow-x-auto [&_pre]:text-sm'
46
54
 
47
55
  // ── CollapsibleSection ────────────────────────────────────────────────────────
48
56
 
@@ -103,7 +111,7 @@ function CollapsibleTextSection({ children, header }: { children: string; header
103
111
  </div>
104
112
 
105
113
  {showCollapsed && (
106
- <div className="absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-neutral-800 to-transparent pointer-events-none" />
114
+ <div className="absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-neutral-960 to-transparent pointer-events-none" />
107
115
  )}
108
116
  </div>
109
117
  </div>
@@ -112,17 +120,17 @@ function CollapsibleTextSection({ children, header }: { children: string; header
112
120
 
113
121
  // ── CompatibleWithSection ─────────────────────────────────────────────────────
114
122
 
115
- function CompatibleWithSection({ tools }: { tools: string[] }) {
116
- if (tools.length === 0) return null
123
+ function CompatibleWithSection({ agents }: { agents: string[] }) {
124
+ if (agents.length === 0) return null
117
125
 
118
126
  return (
119
127
  <div>
120
128
  <h3 className="text-sm font-medium text-neutral-500 uppercase tracking-wider mb-3">Compatible with</h3>
121
129
  <div className="flex items-start gap-3">
122
- {tools.map((tool) => (
123
- <div key={tool} className="flex flex-col items-center gap-1">
124
- <AiToolIcon tool={tool} size={18} />
125
- <span className="text-xs text-neutral-400">{AI_TOOL_NAMES[tool as AiToolKey] ?? tool}</span>
130
+ {agents.map((agent) => (
131
+ <div key={agent} className="flex flex-col items-center gap-1">
132
+ <CodingAgentIcon agent={agent} size={18} />
133
+ <span className="text-xs text-neutral-400">{CODING_AGENT_NAMES[agent as CodingAgentKey] ?? agent}</span>
126
134
  </div>
127
135
  ))}
128
136
  </div>
@@ -149,10 +157,15 @@ export function RegistryDetail({
149
157
  fileStructureVariant,
150
158
  onFetchContent,
151
159
  aboveHeader,
160
+ belowHeader,
161
+ accentColor,
152
162
  maxWidth = 'max-w-[1440px]',
153
163
  children,
154
164
  }: RegistryDetailProps) {
155
- return (
165
+ const contextAccent = useAccentColor()
166
+ const resolvedAccent = accentColor ?? contextAccent
167
+
168
+ const content = (
156
169
  <div className="h-full overflow-y-auto p-6">
157
170
  <div className={`${maxWidth} mx-auto space-y-6`}>
158
171
  {/* Above header badges */}
@@ -178,6 +191,9 @@ export function RegistryDetail({
178
191
  )}
179
192
  </div>
180
193
 
194
+ {/* Below header (status messages, etc.) */}
195
+ {belowHeader}
196
+
181
197
  {/* Description */}
182
198
  {description && (
183
199
  <div>
@@ -206,7 +222,7 @@ export function RegistryDetail({
206
222
 
207
223
  {/* Compatible with */}
208
224
  {compatibleTools && compatibleTools.length > 0 && (
209
- <CompatibleWithSection tools={compatibleTools} />
225
+ <CompatibleWithSection agents={compatibleTools} />
210
226
  )}
211
227
 
212
228
  {/* File structure */}
@@ -216,6 +232,7 @@ export function RegistryDetail({
216
232
  files={files}
217
233
  rootName={rootName}
218
234
  onFetchContent={onFetchContent}
235
+ accentColor={resolvedAccent}
219
236
  />
220
237
  )}
221
238
 
@@ -224,4 +241,10 @@ export function RegistryDetail({
224
241
  </div>
225
242
  </div>
226
243
  )
244
+
245
+ if (resolvedAccent) {
246
+ return <AccentColorProvider value={resolvedAccent}>{content}</AccentColorProvider>
247
+ }
248
+
249
+ return content
227
250
  }
@@ -1,9 +1,10 @@
1
1
  import { useCallback, useRef, useState } from 'react'
2
2
  import Editor from '@monaco-editor/react'
3
3
  import { FORM_COLORS, type FormColor } from '../lib/form-colors.ts'
4
+ import { useAccentColor } from '../lib/accent-context.ts'
4
5
 
5
6
  const variantClasses = {
6
- filled: 'bg-neutral-800 border text-neutral-200 placeholder-neutral-500',
7
+ filled: 'bg-neutral-960 border text-neutral-200 placeholder-neutral-500',
7
8
  outline: 'bg-transparent border text-neutral-200 placeholder-neutral-500',
8
9
  }
9
10
 
@@ -11,7 +12,7 @@ interface ResizableTextareaBaseProps {
11
12
  wrapperClassName?: string
12
13
  resizable?: boolean
13
14
  variant?: 'filled' | 'outline'
14
- color?: FormColor
15
+ accentColor?: FormColor
15
16
  }
16
17
 
17
18
  interface ResizableTextareaFieldProps
@@ -72,8 +73,8 @@ export function ResizableTextarea(props: ResizableTextareaProps) {
72
73
  return <ResizableChildren {...props} />
73
74
  }
74
75
 
75
- const { wrapperClassName, resizable = true, variant, color, ...rest } = props
76
- return <ResizableField wrapperClassName={wrapperClassName} resizable={resizable} variant={variant} color={color} {...rest} />
76
+ const { wrapperClassName, resizable = true, variant, accentColor, ...rest } = props
77
+ return <ResizableField wrapperClassName={wrapperClassName} resizable={resizable} variant={variant} accentColor={accentColor} {...rest} />
77
78
  }
78
79
 
79
80
  // ---------------------------------------------------------------------------
@@ -119,7 +120,7 @@ const MONACO_THEME_PREFIX = 'resizable-textarea'
119
120
  const registeredThemes = new Set<string>()
120
121
 
121
122
  const wrapperVariantClasses = {
122
- filled: 'bg-neutral-800 border rounded-lg overflow-hidden',
123
+ filled: 'bg-neutral-960 border rounded-lg overflow-hidden',
123
124
  outline: 'bg-transparent border rounded-lg overflow-hidden',
124
125
  }
125
126
 
@@ -129,17 +130,19 @@ function ResizableCode({
129
130
  language = 'plaintext',
130
131
  readOnly = false,
131
132
  variant = 'outline',
132
- color = 'blue',
133
+ accentColor,
133
134
  resizable = true,
134
135
  wrapperClassName,
135
136
  minHeight = 80,
136
137
  onHeightChange,
137
138
  }: ResizableTextareaCodeProps) {
139
+ const contextAccent = useAccentColor()
140
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
138
141
  const { height, onResizeStart } = useResize(minHeight, onHeightChange)
139
142
 
140
143
  return (
141
144
  <div
142
- className={`relative ${wrapperVariantClasses[variant]} ${FORM_COLORS[color].border} ${wrapperClassName ?? ''}`}
145
+ className={`relative ${wrapperVariantClasses[variant]} ${FORM_COLORS[effectiveColor].border} ${wrapperClassName ?? ''}`}
143
146
  data-resizable-wrapper
144
147
  style={{ height: height ?? minHeight }}
145
148
  >
@@ -208,10 +211,12 @@ function ResizableChildren({
208
211
  wrapperClassName,
209
212
  resizable = true,
210
213
  variant = 'outline',
211
- color = 'blue',
214
+ accentColor,
212
215
  minHeight = 40,
213
216
  onHeightChange,
214
217
  }: ResizableTextareaChildrenProps) {
218
+ const contextAccent = useAccentColor()
219
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
215
220
  const { height, onResizeStart } = useResize(minHeight, onHeightChange)
216
221
 
217
222
  if (!resizable) {
@@ -220,7 +225,7 @@ function ResizableChildren({
220
225
 
221
226
  return (
222
227
  <div
223
- className={`relative ${wrapperVariantClasses[variant]} ${FORM_COLORS[color].border} ${wrapperClassName ?? ''}`}
228
+ className={`relative ${wrapperVariantClasses[variant]} ${FORM_COLORS[effectiveColor].border} ${wrapperClassName ?? ''}`}
224
229
  data-resizable-wrapper
225
230
  style={height != null ? { height } : undefined}
226
231
  >
@@ -243,12 +248,14 @@ function ResizableField({
243
248
  wrapperClassName,
244
249
  resizable = true,
245
250
  variant = 'outline',
246
- color = 'blue',
251
+ accentColor,
247
252
  ...props
248
253
  }: ResizableTextareaBaseProps & Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'color'>) {
254
+ const contextAccent = useAccentColor()
255
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
249
256
  const { height, onResizeStart } = useResize(40)
250
257
 
251
- const className = `w-full rounded-lg focus:outline-none transition-colors ${variantClasses[variant]} ${FORM_COLORS[color].border} ${FORM_COLORS[color].focus} ${props.className ?? ''}`
258
+ const className = `w-full rounded-lg focus:outline-none transition-colors ${variantClasses[variant]} ${FORM_COLORS[effectiveColor].border} ${FORM_COLORS[effectiveColor].focus} ${props.className ?? ''}`
252
259
 
253
260
  if (!resizable) {
254
261
  return <textarea {...props} className={className} style={{ resize: 'none', ...props.style }} />