@toolr/ui-design 0.1.8 → 0.1.10

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/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/form-colors.ts +16 -16
  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 +10 -9
  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 +24 -30
  39. package/components/ui/ai-action-button.tsx +10 -7
  40. package/components/ui/ai-execution-action-buttons.tsx +13 -5
  41. package/components/ui/badge.tsx +7 -4
  42. package/components/ui/bottom-panel-header.tsx +9 -5
  43. package/components/ui/breadcrumb.tsx +13 -5
  44. package/components/ui/{extension-list-card.tsx → capability-list-card.tsx} +13 -5
  45. package/components/ui/checkbox.tsx +6 -3
  46. package/components/ui/collapsible-section.tsx +38 -29
  47. package/components/ui/confirm-badge.tsx +7 -4
  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 +7 -4
  55. package/components/ui/file-tree.tsx +3 -1
  56. package/components/ui/files-panel.tsx +147 -27
  57. package/components/ui/filter-dropdown.tsx +84 -74
  58. package/components/ui/form-actions.tsx +14 -6
  59. package/components/ui/frontmatter-form-header.tsx +10 -2
  60. package/components/ui/icon-button.tsx +22 -9
  61. package/components/ui/input.tsx +7 -4
  62. package/components/ui/label.tsx +5 -5
  63. package/components/ui/layout-tab-bar.tsx +7 -5
  64. package/components/ui/modal.tsx +18 -4
  65. package/components/ui/nav-card.tsx +6 -3
  66. package/components/ui/navigation-bar.tsx +164 -82
  67. package/components/ui/number-input.tsx +8 -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 +40 -17
  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 +5 -2
  75. package/components/ui/select.tsx +12 -9
  76. package/components/ui/selection-grid.tsx +36 -37
  77. package/components/ui/setting-row.tsx +2 -2
  78. package/components/ui/settings-card.tsx +10 -3
  79. package/components/ui/settings-info-box.tsx +9 -5
  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 +39 -29
  84. package/components/ui/status-card.tsx +9 -1
  85. package/components/ui/tab-bar.tsx +12 -9
  86. package/components/ui/toggle.tsx +13 -7
  87. package/components/ui/tooltip.tsx +9 -1
  88. package/dist/content.js +8 -8
  89. package/dist/diagrams.d.ts +0 -1
  90. package/dist/index.d.ts +427 -184
  91. package/dist/index.js +3098 -1761
  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 +1 -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
@@ -0,0 +1,39 @@
1
+ import { useLayoutEffect, useRef, useState } from 'react'
2
+
3
+ interface PortalPosition {
4
+ top: number
5
+ left: number
6
+ minWidth: number
7
+ }
8
+
9
+ /**
10
+ * Positions a portal-based dropdown menu below its trigger element.
11
+ * Returns a ref for the trigger container and computed position for the portal.
12
+ */
13
+ export function useDropdownPortal(isOpen: boolean, margin = 16) {
14
+ const triggerRef = useRef<HTMLDivElement>(null)
15
+ const menuRef = useRef<HTMLDivElement>(null)
16
+ const [position, setPosition] = useState<PortalPosition>({ top: 0, left: 0, minWidth: 0 })
17
+
18
+ useLayoutEffect(() => {
19
+ if (!isOpen || !triggerRef.current) return
20
+
21
+ const rect = triggerRef.current.getBoundingClientRect()
22
+ setPosition({
23
+ top: rect.bottom + 4,
24
+ left: rect.left,
25
+ minWidth: rect.width,
26
+ })
27
+ }, [isOpen])
28
+
29
+ // Constrain max-height to viewport
30
+ useLayoutEffect(() => {
31
+ if (!isOpen || !menuRef.current) return
32
+ const rect = menuRef.current.getBoundingClientRect()
33
+ menuRef.current.style.maxHeight = `${window.innerHeight - rect.top - margin}px`
34
+ menuRef.current.style.overflowY = 'auto'
35
+ menuRef.current.style.overscrollBehavior = 'contain'
36
+ }, [isOpen, margin, position])
37
+
38
+ return { triggerRef, menuRef, position }
39
+ }
@@ -0,0 +1,10 @@
1
+ import { createContext, useContext } from 'react'
2
+ import type { FormColor } from './form-colors.ts'
3
+
4
+ const AccentColorContext = createContext<FormColor | undefined>(undefined)
5
+
6
+ export const AccentColorProvider = AccentColorContext.Provider
7
+
8
+ export function useAccentColor(): FormColor | undefined {
9
+ return useContext(AccentColorContext)
10
+ }
@@ -1,4 +1,8 @@
1
- export const AI_TOOL_LOGOS = {
1
+ import { useAccentColor } from './accent-context.ts'
2
+ import { cn } from './cn.ts'
3
+ import { FORM_COLORS, type FormColor } from './form-colors.ts'
4
+
5
+ export const CODING_AGENT_LOGOS = {
2
6
  claude: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAMAAABF0y+mAAAAJFBMVEVHcEzZd1fZd1fZd1fZd1fad1jZd1fZd1fZd1fZd1fZd1fZd1deZDooAAAADHRSTlMA//F3mhLfyjpWsiMDGU5mAAABH0lEQVQokXVSWXbEIAzDuw33v28FJCnpTP3BA6+yRGvbLK39a0F9RUvHZ5CIazZwkm+VpCi1nZP1GpJEnq2NdabzU594N0XpzInRhq/7jvm8wsOjFfVmcQG4guTWZKYL8HSiA1XFHDjgHMqCpKfpNPgIXiZVVvSJ9yazOJARxBiYf/ZMoIUxrYGjRHs/di2nbdzDZxIb1maPriold3QlyFJCAonMd08A7/WhkN19PWDolSeiXU7URWPm8S3ewMCuvGpB+kigVXvWZKlgIVycF0Hjl6DIoVRCGKz9pE8W0QKXkgkIEmjzUDuh11TSuVkn348DLHs1Y7/kzTi+ksX8GLn0wBRrdvCQS949C43fstj6FhfM8Unfqqwv3gf3+/kDMJgHC0kwnjEAAAAASUVORK5CYII=',
3
7
  copilot: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHJ4PSI2IiBmaWxsPSIjMWYxZjFmIi8+PHBhdGggZD0iTTE0IDVjLTQuOTcgMC05IDQuMDMtOSA5IDAgMy45NyAyLjU2IDcuMzQgNi4xMiA4LjUzLjQ1LjA4LjYxLS4xOS42MS0uNDN2LTEuNjdjLTIuNDkuNTQtMy4wMS0xLjA2LTMuMDEtMS4wNi0uNDEtMS4wNC0uOTktMS4zMS0uOTktMS4zMS0uODEtLjU2LjA2LS41NC4wNi0uNTQuOS4wNiAxLjM3LjkyIDEuMzcuOTIuOCAxLjM3IDIuMDkuOTggMi42Ljc0LjA4LS41OC4zMS0uOTguNTctMS4yLTEuOTktLjIzLTQuMDgtLjk5LTQuMDgtNC40MiAwLS45OC4zNS0xLjc4LjkyLTIuNDEtLjA5LS4yMy0uNC0xLjE0LjA5LTIuMzcgMCAwIC43NS0uMjQgMi40Ni45Mi43MS0uMiAxLjQ4LS4zIDIuMjQtLjNzMS41My4xIDIuMjQuMzFjMS43MS0xLjE2IDIuNDYtLjkyIDIuNDYtLjkyLjQ5IDEuMjMuMTggMi4xNC4wOSAyLjM3LjU3LjYzLjkyIDEuNDMuOTIgMi40MSAwIDMuNDQtMi4xIDQuMTktNC4wOSA0LjQxLjMyLjI4LjYxLjgyLjYxIDEuNjZ2Mi40NmMwIC4yNC4xNi41MS42MS40M0MxOS40NCAyMS4zNCAyMiAxOC4wMyAyMiAxNGMwLTQuOTctNC4wMy05LTktOVoiIGZpbGw9IiNmZmYiLz48L3N2Zz4=',
4
8
  codex: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAP1BMVEVHcEwAgPgAgPgAgPcAgPcAgPcAgPcAgPcAgPcAgPcAd/dzsvq+2/2Kvvtfpvru+P/Z6/77/v83lvmnzfwbjPhuMd/TAAAACnRSTlMAF0adyun/9k+LLZxzywAAAXZJREFUeAFkUlmWwyAMC03qZITxAtz/rPNiaLpEvwhJCC1vpMe6PYn2bT3Sckf6e9KF55pux/SDvy9K2uiG7YNx7BQAgDdjP677r/PMXOhN2tOXPkSN2byUkis+XV75vBFA7mqugpE0BKY+M0DNuQIoXgbjlFinv1Zk0wxUAppeEin66ebe4Q2o7J6pq4zG0nLEfTPRDhUUNSlu3fKQeAwH0V5PQlPV1kHFfcZcl20GPAnuuWe1DIjx7HPZw+EkEOJ1cDcBtCBCLBGhGVVlimTQ3FxQbHgEgaoLuoVx6FiG+CQ8h4RWUFYVdHYhy8iDsEfI6IEjvTlrEJgxQq7DQ8W0kbgKgiAu85mPQYiiPZo+LdRnUcesGsZAzxVozp2MBa+q5293bx2gs2kgK2hgvb4b4mpsWgggbS9C+hgMcmtFrRT2ck37e9IAajHjjJ9hz9FO0n201+wn7rP/H76MQzjrIZQwI2deTqz5m4OZhR0oycLMhCQNACpMHlEZrAKTAAAAAElFTkSuQmCC',
@@ -6,9 +10,9 @@ export const AI_TOOL_LOGOS = {
6
10
  opencode: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAIKADAAQAAAABAAAAIAAAAACshmLzAAABl0lEQVRYCe1XS2rDMBAd/70xBJoubJqVF83Sm+YooVcIPUDJTUpOUHIa71Ov0ht4Y/yJ6zEYHFnyZBKKNx4IkTSjNw/pzQhrT4tFDROaPmHuNvVMYPITMCkNVFVFhYz6DcMY9SsJ1HUNmqbB1+EAYRiOgqicSZLAx24HHZYsTkmgC46iCF7X627K+vc8j4wnNVAUBQmiCrhlL3kCIjilCerORTwWgTzP4X27hd/zGcRESOxltYLv4xFs2xbzKOcsAiimn9MJUFymeb21LEvIsqwVnDKbxHGNIgkQl8ymrDC5SADj0Mc1NgGjSW5Z1oAAliz6uMbaoes6vG027V3LNOD7PmAMx1gEENhxHHBdVypC9HGNTQCF2P36yWRrfb9qzDsvFcoD6zMBtgaw48nasWqduh0WAaz1IAjAalqtIZRbdbnA83LZPuFU0r6fRQC73+d+398/GP97H+AmGDAUFsgqwLZ7r92yl7yCOI4hTdO7OOCrSZlGfRnJFE+B9v3im9H34Zg8AQpABOTOSQ1wAbnxM4HJT+APfgFr0DSNhC0AAAAASUVORK5CYII=',
7
11
  } as const
8
12
 
9
- export type AiToolKey = 'claude' | 'gemini' | 'codex' | 'copilot' | 'opencode'
13
+ export type CodingAgentKey = 'claude' | 'gemini' | 'codex' | 'copilot' | 'opencode'
10
14
 
11
- export const AI_TOOL_NAMES: Record<AiToolKey, string> = {
15
+ export const CODING_AGENT_NAMES: Record<CodingAgentKey, string> = {
12
16
  claude: 'Claude Code',
13
17
  gemini: 'Gemini CLI',
14
18
  codex: 'Codex CLI',
@@ -16,21 +20,32 @@ export const AI_TOOL_NAMES: Record<AiToolKey, string> = {
16
20
  opencode: 'Opencode',
17
21
  }
18
22
 
19
- export function AiToolIcon({ tool, size, showName, className, style }: {
20
- tool: string
23
+ export function CodingAgentIcon({ agent, size, showName, selected, accentColor, className, style }: {
24
+ agent: string
21
25
  size?: number
22
26
  showName?: boolean
27
+ selected?: boolean
28
+ accentColor?: FormColor
23
29
  className?: string
24
30
  style?: React.CSSProperties
25
31
  }) {
26
- const src = AI_TOOL_LOGOS[tool as keyof typeof AI_TOOL_LOGOS]
32
+ const contextAccent = useAccentColor()
33
+ const src = CODING_AGENT_LOGOS[agent as keyof typeof CODING_AGENT_LOGOS]
27
34
  if (!src) return null
35
+
36
+ const effectiveAccent = accentColor ?? contextAccent ?? 'blue'
37
+ const accentStyles = selected ? FORM_COLORS[effectiveAccent] : undefined
38
+
28
39
  const img = (
29
40
  <img
30
41
  src={src}
31
42
  alt=""
32
43
  {...(size ? { width: size, height: size } : {})}
33
- className={className}
44
+ className={cn(
45
+ selected && 'rounded-md border-2 p-0.5',
46
+ selected && accentStyles?.border,
47
+ className,
48
+ )}
34
49
  style={{ objectFit: 'contain', ...style }}
35
50
  />
36
51
  )
@@ -38,7 +53,7 @@ export function AiToolIcon({ tool, size, showName, className, style }: {
38
53
  return (
39
54
  <span style={{ display: 'inline-flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
40
55
  {img}
41
- <span className="text-xs text-neutral-400">{AI_TOOL_NAMES[tool as AiToolKey] ?? tool}</span>
56
+ <span className="text-xs text-neutral-400">{CODING_AGENT_NAMES[agent as CodingAgentKey] ?? agent}</span>
42
57
  </span>
43
58
  )
44
59
  }
@@ -0,0 +1,37 @@
1
+ import { cn } from './cn.ts'
2
+
3
+ export type CustomIconKey = 'gerrit'
4
+
5
+ export const CUSTOM_ICON_NAMES: Record<CustomIconKey, string> = {
6
+ gerrit: 'Gerrit',
7
+ }
8
+
9
+ const GERRIT_PATH = 'M12.648 2.678l-.245-.266c.004-.004.29-.268.413-.41.121-.146.342-.484.346-.486l.301.195c-.014.016-.234.359-.375.522-.137.165-.428.432-.44.445zm1.577 10.597c-.012-.004-.24-.154-.365-.221-.117-.059-.32-.146-.422-.191l.213-.612-.898-.444-.286.871c-.174.004-.713.053-1.51.389-.959.4-1.688 1.025-1.695 1.029l-.143.125.641.025.02-.016c.006-.006.721-.535 1.119-.705.088-.037.207-.074.33-.105-.209.105-.439.227-.6.32-.199.119-.57.381-.586.393l-.186.129.682.016.018-.01c.012-.008 1.164-.623 1.789-.76l.196-.047c.145-.037.246-.064.422-.064.156 0 .369.021.688.07.398.059.66.158.664.16l.24.094-.322-.436-.012-.008.003-.002zm-8.98-7.298h-.029l-1.006.916v.03c-.016.266.078.52.258.716.182.196.42.309.686.319h.043c.531 0 .965-.413.992-.94.023-.545-.399-1.01-.944-1.041zM5.2 7.808h-.041c-.117-.005-.23-.032-.33-.085.045.008.098.01.148.002.284-.035.481-.291.448-.575-.035-.279-.293-.48-.576-.442-.264.034-.457.267-.446.531-.031-.09-.045-.183-.045-.28l.912-.833c.446.042.79.431.768.882s-.391.8-.842.8H5.2zm9.329-3.725l-.293-.195-.41.265-.395-.285-.301.18.404.291-.416.27.297.18.4-.254.387.28.309-.169-.407-.296M24 19.525c-.213-.209-.418-.416-.629-.627-.48-.488-.957-.984-1.418-1.486-.547-.598-1.082-1.207-1.582-1.844-.225-.283-.441-.58-.646-.881-.254-.387-.469-.795-.668-1.215-.211-.445-.398-.9-.576-1.356.24.21.463.444.664.692.021-.215.041-.43.059-.648l.023-.322c0-.033.012-.066-.008-.096-.016-.029-.033-.051-.053-.075-.121-.149-.264-.282-.406-.413-.189-.181-.387-.36-.584-.533l-.18-.156c-.027-.027-.057-.051-.086-.074-.031-.03-.041-.096-.055-.136l-.154-.479c.607.285 1.109.741 1.578 1.215.004-.219.004-.436 0-.652 0-.114-.002-.229-.008-.346 0-.029.006-.1-.016-.127-.012-.02-.031-.036-.043-.055-.109-.117-.234-.217-.357-.314-.172-.143-.35-.277-.527-.408-.156-.117-.318-.232-.477-.345-.018-.016-.076-.039-.064-.06l.039-.08c.02-.035.014-.045.053-.029l.17.059c.406.156.779.368 1.143.608-.123-.45-.311-.885-.525-1.296-.389-.762-.893-1.468-1.463-2.105-.379-.426-.785-.824-1.219-1.193-.223-.19-.457-.369-.699-.533-.113-.074-.225-.149-.346-.217-.049-.03-.105-.056-.15-.094-.18-.144-.365-.284-.549-.429.238.098.469.21.689.338-.127-.194-.342-.331-.545-.436-.307-.159-.637-.276-.967-.378.113-.12.234-.228.346-.348.113-.12.223-.246.33-.372.127-.15.252-.3.375-.455l-1.156-.726-.48-.303c-.123.246-.277.476-.458.68-.18.203-.391.369-.6.539-.191.156-.379.316-.566.472-.047.04-.092.085-.145.12-.025.021-.096.004-.127.004H10.7c-.297.006-.596.029-.891.068-.058.007-.113.013-.175.008l-.178-.01c-.137-.008-.271-.016-.408-.016-.289-.004-.58 0-.871.025-.244.022-.489.055-.729.112-.238.056-.461.172-.66.312-.193.141-.387.32-.504.53s-.17.456-.213.689c-.135.013-.268.01-.4.016-.141.008-.277.021-.416.039-.27.037-.535.096-.795.18-.496.154-.945.405-1.336.75-.195.181-.377.38-.539.596-.02.025-.037.051-.063.068-.01.009-.027.016-.033.027-.015.027-.033.052-.051.076l-.134.212c-.168.285-.276.595-.383.906l-.045.123.033-.021c-.01.067-.02.135-.025.202l-.008.105v.053l-.066.013c-.135.026-.271.06-.4.101-.12.039-.233.086-.337.15-.105.066-.195.153-.285.239-.318.31-.562.698-.687 1.128-.1.348-.137.735-.059 1.092.021.09.049.188.107.262.074.09.199.126.313.095.131-.036.248-.124.371-.187l.314-.157c.455-.226.93-.446 1.438-.525.035-.005.1.067.131.091.049.037.1.074.148.104.109.069.221.129.334.181.262.114.541.174.818.231.502.105 1.014.171 1.529.18.207.004.416 0 .625-.018.225-.02.451-.049.678-.051.25-.005.496.025.74.055.254.031.51.068.764.105.518.079 1.031.169 1.543.279-.115.18-.227.362-.338.545-.008.013-.072 0-.088 0-.041-.002-.086-.002-.127 0-.074 0-.15.008-.225.019-.211.03-.416.083-.615.156-.49.181-.938.483-1.326.833-.221.195-.43.408-.609.641-.049.064-.094.129-.139.193.105-.023.211-.045.318-.07.061-.016.117-.027.176-.039.021-.008.029-.016.049-.027.219-.203.451-.393.695-.563.111-.08.225-.154.348-.215.166-.083.354-.131.533-.174-.33.18-.678.35-.977.584-.172.139-.328.291-.49.439l.654-.104c.01 0 .014-.004.023-.01l.17-.094c.113-.064.23-.125.346-.186.24-.123.482-.24.732-.34.236-.094.48-.178.732-.225.221-.046.428-.052.648 0 .342.074.67.232.969.414.021.014.035.033.055.014.016-.014.098-.074.09-.092l-.18-.334c-.016-.029-.023-.049-.051-.066l-.131-.077c-.176-.104-.348-.21-.51-.331-.037-.023-.078-.039-.082-.089-.004-.046.014-.093.029-.136.033-.09.08-.169.131-.248.033-.058.072-.111.109-.166.014-.018.01-.021.031-.014l.119.034c.236.077.469.159.711.22.27.068.545.123.82.176l.048.01c-.056-.03-.091-.09-.132-.136-.023-.03-.035-.05-.074-.06l-.105-.02c-.074-.016-.148-.03-.225-.049-.262-.057-.525-.119-.777-.209-.375-.13-.758-.232-1.145-.322-.486-.107-.977-.194-1.465-.275-.25-.041-.498-.074-.75-.109-.246-.034-.496-.07-.746-.087-.539-.034-1.074.087-1.615.081-.365-.004-.734-.055-1.096-.105l-.068-.009c.252-.093.5-.188.75-.285.236-.095.471-.192.705-.289.127-.052.25-.105.373-.157.113-.051.225-.1.328-.17.408-.279.676-.727.975-1.109.273-.361.586-.7.99-.908.129-.066.262-.117.4-.156-.283-.439-.549-.93-.623-1.455.029.012.061.025.094.037.035.012.064.016.072.045l.029.135c.026.091.053.176.086.262.072.191.16.375.26.551.219.396.484.766.766 1.12.539.686 1.145 1.305 1.736 1.941.145.158.275.324.41.492.15.187.303.375.457.561.121.141.234.285.356.426l.094.112c.016.018.037.026.061.036.604.3 1.213.6 1.822.895.434.209.869.42 1.309.623.236.109.471.219.711.32.029.014.066.029.098.039.016.01.029.016.047.023l.016.049c.027.072.051.145.076.217.049.141.098.279.148.416.33.912.695 1.814 1.145 2.676.346.656.715 1.301 1.09 1.939.4.68.811 1.354 1.225 2.025.332.535.666 1.072 1.008 1.605.146.227.289.459.439.688l.063.094.775-1.141.191-.283c.008-.006.082-.102.076-.109l-.156-.24c-.291-.451-.584-.898-.871-1.35l-.828-1.283-.105-.166.656.799 1.115 1.35c.121.146.236.289.354.438.219-.402.439-.801.662-1.201.086-.164.176-.33.266-.492M12.13 1.915c.309-.246.563-.563.757-.906l1.178.743.18.112c-.246.3-.496.604-.775.873-.035.034-.074.069-.107.104-.021.025-.047.051-.07.075l-.033.038c-.023-.007-.045-.016-.068-.02-.146-.041-.289-.08-.436-.111-.292-.066-.589-.117-.886-.152-.191-.021-.385-.04-.578-.058.275-.234.557-.467.838-.698m-1.754 1.37c.232-.096.482-.15.73-.191.49-.082.994-.094 1.489-.04.354.038.701.108 1.041.21l-.141.115-.225-.061c-.141-.029-.281-.06-.422-.082-.246-.037-.497-.06-.749-.065-.461-.008-.926.042-1.371.159-.301.078-.604.188-.879.332-.286.154-.555.346-.78.582-.134.137-.257.289-.359.455-.057.09-.105.18-.15.275-.024.047-.043.096-.061.144l-.029.075c-.004.015-.008.026-.012.038L8.39 5.22l-.082-.011c.234-.719.763-1.286 1.418-1.649.207-.114.426-.218.65-.279m-3.93.374c.266-.381.686-.649 1.139-.743.502-.101 1.016-.119 1.525-.107-.525.131-1.05.326-1.488.652-.34.075-.664.24-.934.459-.09.071-.17.15-.246.236-.035.037-.066.075-.097.116-.014.015-.027.03-.037.049h-.084c.023-.135.055-.27.098-.4.029-.09.064-.18.119-.26m.882.056c-.145.143-.27.303-.391.469-.047.069-.098.153-.18.191-.085.039-.194-.005-.28-.031.224-.279.521-.494.851-.629M2.308 6.585c.08-.16.182-.315.275-.465.016-.027.053-.046.074-.063.049-.034.096-.069.143-.106.309-.225.621-.446.939-.656.166-.107.33-.213.502-.307.182-.101.369-.18.565-.244.347-.112.707-.189 1.068-.244.174-.025.361-.057.531-.015-.33.188-.658.375-.99.558-.342.191-.689.367-1.035.552-.332.18-.66.375-.981.577-.336.206-.67.419-1.002.629-.08.051-.16.105-.24.155.045-.125.09-.251.151-.371m1.009 3.056c-.029.056-.134.042-.187.042-.094 0-.186 0-.279.006-.258.015-.51.06-.758.138-.475.147-.922.375-1.365.604-.1.055-.203.135-.316.162-.061.015-.131-.008-.166-.063-.035-.061-.053-.131-.064-.198-.027-.12-.035-.245-.033-.368 0-.33.076-.66.215-.96.096-.21.221-.401.371-.574.15-.175.326-.351.539-.45.355-.162.775-.213 1.164-.235.072-.005.145-.007.217-.007.1 0 .217-.016.311.019.074.027.121.105.15.173.045.105.074.225.1.336.061.26.123.525.145.791.01.119.016.239.004.359-.009.073-.014.156-.05.223m2.829-1.973c-.194.123-.403.218-.616.298-.215.077-.436.144-.66.181-.097.013-.195.025-.293.021-.101-.003-.193-.036-.293-.067-.195-.063-.393-.13-.57-.233-.064-.039-.135-.084-.174-.15-.029-.052-.029-.116-.023-.174.012-.231.094-.458.203-.66.187-.343.482-.612.84-.775.879-.396 1.865-.029 2.611.49l.08.061c-.154.165-.311.321-.473.477-.199.189-.404.381-.634.531M8.31 5.739c-.475-.203-.926-.458-1.356-.738.449.188.934.3 1.414.37.258.037.521.072.781.08.281.008.564-.021.84-.075.545-.103 1.068-.305 1.566-.551.494-.245.964-.537 1.413-.859.217-.155.43-.315.633-.487.021-.016.174-.161.184-.154l.041.031.537.416c.328.254.658.51.988.762-.906.326-1.826.629-2.752.904-.519.156-1.038.301-1.565.42-.412.098-.834.189-1.256.21-.507.022-1.006-.135-1.47-.33m8.85 3.942c.076.021.145.045.215.067l.094.033c.016.006.031.015.045.02l.021.06c.045.146.09.289.139.432-.15-.127-.301-.254-.451-.379l-.09-.074c-.021-.016-.045-.021-.029-.046l.059-.114m-.671 1.444l.035-.063.027-.046c.012-.018.008-.022.029-.012.129.054.258.111.385.17.24.11.475.23.703.364.107.065.217.135.322.205l.15.105.074.057c.033.027.041.063.057.102.104.282.219.564.338.844.078.189.162.379.248.566-.293-.371-.621-.715-.957-1.045-.346-.346-.705-.671-1.078-.981l-.323-.264m3.746 6.42l-.121.09.008.016.063.094.271.42.904 1.402c.311.48.621.963.932 1.445l.309.48.084.133c.004.004.029.041.029.045-.26.385-.523.77-.783 1.154-.027.037-.051.076-.074.111-.24-.373-.479-.744-.715-1.117-.4-.635-.795-1.277-1.184-1.916-.434-.709-.855-1.418-1.264-2.141-.383-.674-.75-1.361-1.059-2.076-.301-.697-.563-1.408-.811-2.121.486.193.98.367 1.48.521.145.045.289.09.436.127l.063.018c.008 0 .014-.039.018-.049.018-.064.031-.129.045-.195.031-.125.051-.254.074-.381.205.428.436.844.701 1.236.215.314.445.621.686.92.521.656 1.074 1.283 1.643 1.898.463.494.934.984 1.408 1.465l.389.389c.006.006.039.031.037.041l-.031.053-.148.275-.588 1.068c-.18-.219-.361-.436-.541-.658l-1.125-1.361c-.314-.387-.637-.773-.953-1.16l-.186-.225'
10
+
11
+ const CUSTOM_ICON_SVGS: Record<CustomIconKey, { path: string; fill: boolean }> = {
12
+ gerrit: { path: GERRIT_PATH, fill: true },
13
+ }
14
+
15
+ export function CustomIcon({ icon, size = 16, className }: {
16
+ icon: string
17
+ size?: number
18
+ className?: string
19
+ }) {
20
+ const key = icon as CustomIconKey
21
+ const svg = CUSTOM_ICON_SVGS[key]
22
+ if (!svg) return null
23
+
24
+ return (
25
+ <svg
26
+ xmlns="http://www.w3.org/2000/svg"
27
+ width={size}
28
+ height={size}
29
+ viewBox="0 0 24 24"
30
+ fill={svg.fill ? 'currentColor' : 'none'}
31
+ stroke={svg.fill ? 'none' : 'currentColor'}
32
+ className={cn(className)}
33
+ >
34
+ <path d={svg.path} />
35
+ </svg>
36
+ )
37
+ }
@@ -38,22 +38,22 @@ export const ACCENT_TEXT: Record<AccentColor, string> = {
38
38
 
39
39
  export const ACCENT_ICON = ACCENT_TEXT
40
40
 
41
- export const ACCENT_NAV: Record<AccentColor, { bg: string; text: string }> = {
42
- blue: { bg: 'bg-blue-500/10', text: 'text-blue-400' },
43
- green: { bg: 'bg-green-500/10', text: 'text-green-400' },
44
- red: { bg: 'bg-red-500/10', text: 'text-red-400' },
45
- orange: { bg: 'bg-orange-500/10', text: 'text-orange-400' },
46
- cyan: { bg: 'bg-cyan-500/10', text: 'text-cyan-400' },
47
- yellow: { bg: 'bg-yellow-500/10', text: 'text-yellow-400' },
48
- purple: { bg: 'bg-purple-500/10', text: 'text-purple-400' },
49
- indigo: { bg: 'bg-indigo-500/10', text: 'text-indigo-400' },
50
- emerald: { bg: 'bg-emerald-500/10', text: 'text-emerald-400' },
51
- amber: { bg: 'bg-amber-500/10', text: 'text-amber-400' },
52
- violet: { bg: 'bg-violet-500/10', text: 'text-violet-400' },
53
- neutral: { bg: 'bg-neutral-500/10', text: 'text-neutral-400' },
54
- sky: { bg: 'bg-sky-500/10', text: 'text-sky-400' },
55
- pink: { bg: 'bg-pink-500/10', text: 'text-pink-400' },
56
- teal: { bg: 'bg-teal-500/10', text: 'text-teal-400' },
41
+ export const ACCENT_NAV: Record<AccentColor, { bg: string; text: string; border: string }> = {
42
+ blue: { bg: 'bg-blue-500/10', text: 'text-blue-400', border: 'border-blue-500/30' },
43
+ green: { bg: 'bg-green-500/10', text: 'text-green-400', border: 'border-green-500/30' },
44
+ red: { bg: 'bg-red-500/10', text: 'text-red-400', border: 'border-red-500/30' },
45
+ orange: { bg: 'bg-orange-500/10', text: 'text-orange-400', border: 'border-orange-500/30' },
46
+ cyan: { bg: 'bg-cyan-500/10', text: 'text-cyan-400', border: 'border-cyan-500/30' },
47
+ yellow: { bg: 'bg-yellow-500/10', text: 'text-yellow-400', border: 'border-yellow-500/30' },
48
+ purple: { bg: 'bg-purple-500/10', text: 'text-purple-400', border: 'border-purple-500/30' },
49
+ indigo: { bg: 'bg-indigo-500/10', text: 'text-indigo-400', border: 'border-indigo-500/30' },
50
+ emerald: { bg: 'bg-emerald-500/10', text: 'text-emerald-400', border: 'border-emerald-500/30' },
51
+ amber: { bg: 'bg-amber-500/10', text: 'text-amber-400', border: 'border-amber-500/30' },
52
+ violet: { bg: 'bg-violet-500/10', text: 'text-violet-400', border: 'border-violet-500/30' },
53
+ neutral: { bg: 'bg-neutral-500/10', text: 'text-neutral-400', border: 'border-neutral-500/30' },
54
+ sky: { bg: 'bg-sky-500/10', text: 'text-sky-400', border: 'border-sky-500/30' },
55
+ pink: { bg: 'bg-pink-500/10', text: 'text-pink-400', border: 'border-pink-500/30' },
56
+ teal: { bg: 'bg-teal-500/10', text: 'text-teal-400', border: 'border-teal-500/30' },
57
57
  }
58
58
 
59
59
  export const FORM_COLORS: Record<FormColor, FormColorConfig> = {
@@ -0,0 +1,39 @@
1
+ import { iconMap } from '../ui/icon-button.tsx'
2
+ import { CustomIcon } from './custom-icons.tsx'
3
+ import { cn } from './cn.ts'
4
+
5
+ export type GitProviderKey = 'github' | 'gitlab' | 'gerrit'
6
+
7
+ export const GIT_PROVIDER_NAMES: Record<GitProviderKey, string> = {
8
+ github: 'GitHub',
9
+ gitlab: 'GitLab',
10
+ gerrit: 'Gerrit',
11
+ }
12
+
13
+ const GIT_PROVIDER_COLORS: Record<GitProviderKey, string> = {
14
+ github: 'text-neutral-300',
15
+ gitlab: 'text-orange-400',
16
+ gerrit: 'text-green-400',
17
+ }
18
+
19
+ const GithubIcon = iconMap['github']
20
+ const GitlabIcon = iconMap['gitlab']
21
+
22
+ export function GitProviderIcon({ provider, size = 16, className }: {
23
+ provider: string
24
+ size?: number
25
+ className?: string
26
+ }) {
27
+ const key = provider as GitProviderKey
28
+ const color = GIT_PROVIDER_COLORS[key]
29
+ if (!color) return null
30
+
31
+ if (key === 'gerrit') {
32
+ return <CustomIcon icon="gerrit" size={size} className={cn(color, className)} />
33
+ }
34
+
35
+ const Icon = key === 'github' ? GithubIcon : key === 'gitlab' ? GitlabIcon : null
36
+ if (!Icon) return null
37
+
38
+ return <Icon className={cn(color, className)} style={{ width: size, height: size }} />
39
+ }
@@ -2,7 +2,7 @@ import type { FormColor } from './form-colors.ts'
2
2
 
3
3
  /* ── Neutral scale types ──────────────────────────────────── */
4
4
 
5
- export const SCALE_KEYS = ['black', '950', '900', '850', '800', '750', '700', '600', '500', '400', '300', '200'] as const
5
+ export const SCALE_KEYS = ['black', '990', '980', '970', '960', '950', '940', '930', '920', '910', '900', '850', '800', '750', '700', '650', '600', '550', '500', '450', '400', '350', '300', '250', '200', '150', '100', '090', '080', '070', '060', '050', '040', '030', '020', '010'] as const
6
6
  export type ScaleKey = (typeof SCALE_KEYS)[number]
7
7
 
8
8
  /* ── Theme definitions ────────────────────────────────────── */
@@ -13,8 +13,8 @@ export const DARK_THEMES: ThemeId[] = ['dark']
13
13
  export const LIGHT_THEMES: ThemeId[] = ['light']
14
14
 
15
15
  export const BASE_THEMES: Record<ThemeId, { label: string; maxSat: number; lightness: Record<ScaleKey, number> }> = {
16
- dark: { label: 'Dark', maxSat: 22, lightness: { black: 0, '950': 5, '900': 10, '850': 12, '800': 15, '750': 18, '700': 22, '600': 30, '500': 46, '400': 64, '300': 83, '200': 90 } },
17
- light: { label: 'Light', maxSat: 15, lightness: { black: 98, '950': 96, '900': 93, '850': 91, '800': 88, '750': 85, '700': 82, '600': 65, '500': 50, '400': 38, '300': 22, '200': 12 } },
16
+ dark: { label: 'Dark', maxSat: 22, lightness: { black: 0, '990': 2, '980': 4, '970': 6, '960': 8, '950': 10, '940': 10.5, '930': 11, '920': 11.5, '910': 12, '900': 13, '850': 15, '800': 18, '750': 20, '700': 22, '650': 20, '600': 28, '550': 36, '500': 46, '450': 55, '400': 64, '350': 74, '300': 83, '250': 87, '200': 90, '150': 93, '100': 94, '090': 95, '080': 96, '070': 97, '060': 97.5, '050': 98, '040': 98.5, '030': 99, '020': 99.3, '010': 99.6 } },
17
+ light: { label: 'Light', maxSat: 15, lightness: { black: 98, '990': 97, '980': 96, '970': 95, '960': 94, '950': 93, '940': 92.5, '930': 92, '920': 91.5, '910': 91, '900': 90, '850': 88, '800': 85, '750': 83, '700': 82, '650': 74, '600': 65, '550': 56, '500': 50, '450': 44, '400': 38, '350': 30, '300': 22, '250': 17, '200': 12, '150': 10, '100': 8, '090': 7, '080': 6, '070': 5, '060': 4, '050': 3, '040': 2.5, '030': 2, '020': 1.5, '010': 1 } },
18
18
  }
19
19
 
20
20
  /* ── Accent definitions ───────────────────────────────────── */
@@ -44,7 +44,9 @@ export const ACCENT_DEFS: AccentDef[] = [
44
44
  /* ── Color utilities ──────────────────────────────────────── */
45
45
 
46
46
  export function satCurve(l: number): number {
47
- return Math.min(l / 10, 1) * Math.max(0, 1 - l / 115)
47
+ const ramp = Math.min(l / 10, 1)
48
+ if (l <= 50) return ramp
49
+ return ramp * Math.max(0.5, 1 - (l - 50) / 130)
48
50
  }
49
51
 
50
52
  export function hslToHex(h: number, s: number, l: number): string {
@@ -63,10 +65,10 @@ function alphaToHex(alpha: number): string {
63
65
 
64
66
  /* ── Dim (glass surfaces) ────────────────────────────────── */
65
67
 
66
- export const SURFACE_KEYS = ['950', '900', '850', '800', '750'] as const
68
+ export const SURFACE_KEYS = ['990', '980', '970', '960', '950', '940', '930', '920', '910', '900', '850', '800', '750'] as const
67
69
  export type SurfaceKey = (typeof SURFACE_KEYS)[number]
68
70
 
69
- export const DEFAULT_DIMS: Record<SurfaceKey, number> = { '950': 1, '900': 2, '850': 3, '800': 4, '750': 5 }
71
+ export const DEFAULT_DIMS: Record<SurfaceKey, number> = { '990': 1, '980': 2, '970': 3, '960': 4, '950': 5, '940': 6, '930': 7, '920': 8, '910': 9, '900': 10, '850': 15, '800': 20, '750': 25 }
70
72
  export const DEFAULT_OUTLINE = 30
71
73
 
72
74
  /* ── Scale generation ─────────────────────────────────────── */
@@ -74,6 +76,12 @@ export const DEFAULT_OUTLINE = 30
74
76
  export function generateScale(theme: ThemeId, hue: number | null, maxSat: number, dims: Record<SurfaceKey, number> = DEFAULT_DIMS, outline: number = DEFAULT_OUTLINE): Record<ScaleKey, string> {
75
77
  const lightness = BASE_THEMES[theme].lightness
76
78
  const glassBase = hue !== null ? hslToHex(hue, 80, 50) : null
79
+ const accentRgb = glassBase ? [parseInt(glassBase.slice(1, 3), 16), parseInt(glassBase.slice(3, 5), 16), parseInt(glassBase.slice(5, 7), 16)] : null
80
+ // Steps from white (end of scale) — used for alpha stepping
81
+ const stepsFromWhite: Partial<Record<ScaleKey, number>> = {}
82
+ for (let i = SCALE_KEYS.length - 1, step = 1; i >= 0; i--, step++) {
83
+ stepsFromWhite[SCALE_KEYS[i]] = step
84
+ }
77
85
  const result = {} as Record<ScaleKey, string>
78
86
 
79
87
  for (const key of SCALE_KEYS) {
@@ -92,12 +100,32 @@ export function generateScale(theme: ThemeId, hue: number | null, maxSat: number
92
100
  continue
93
101
  }
94
102
 
95
- // Solid color (text, black, fallbacks)
103
+ // Neutral/achromatic: solid grayscale
96
104
  if (hue === null || maxSat === 0) {
97
105
  const g = Math.round(255 * l / 100).toString(16).padStart(2, '0')
98
106
  result[key] = `#${g}${g}${g}`
107
+ } else if (key === 'black') {
108
+ result[key] = '#000000'
109
+ } else if (l <= 50) {
110
+ // Dark-to-mid: accent at increasing opacity
111
+ result[key] = `${glassBase}${alphaToHex(l / 50)}`
99
112
  } else {
100
- result[key] = hslToHex(hue, maxSat * satCurve(l), l)
113
+ // Light half: cross-fade accent fades out, white fades in
114
+ const t = (l - 50) / 50
115
+ const aa = 1 - t // accent alpha: 1→0
116
+ const wa = t // white alpha: 0→1
117
+ const ta = wa + aa * (1 - wa) // composite alpha
118
+ const [ar, ag, ab] = accentRgb!
119
+ const r = Math.round((255 * wa + ar * aa * (1 - wa)) / ta)
120
+ const g = Math.round((255 * wa + ag * aa * (1 - wa)) / ta)
121
+ const b = Math.round((255 * wa + ab * aa * (1 - wa)) / ta)
122
+ const hex = (c: number) => Math.min(255, c).toString(16).padStart(2, '0')
123
+
124
+ // White variants (010–090): 5% opacity step per position
125
+ // 100–500: natural cross-fade alpha for smooth transition
126
+ const keyNum = parseInt(key)
127
+ const alpha = (keyNum > 0 && keyNum <= 90) ? 1 - stepsFromWhite[key]! * 0.05 : ta
128
+ result[key] = `#${hex(r)}${hex(g)}${hex(b)}${alphaToHex(alpha)}`
101
129
  }
102
130
  }
103
131
  return result
@@ -128,8 +156,29 @@ export function applyTheme(themeId: ThemeId, accentHue: number | null, dims: Rec
128
156
  root.style.setProperty(prop, scale[key])
129
157
  }
130
158
  root.style.setProperty('--color-white', light ? '#0a0a0a' : '#ffffff')
131
- root.style.setProperty('--color-neutral-100', light ? '#1a1a1a' : '#f0f0f0')
132
- root.style.setProperty('--color-neutral-50', light ? '#0f0f0f' : '#fafafa')
159
+
160
+ // Text-range neutrals (600→010): white+alpha for dark, black+alpha for light
161
+ // Surfaces/borders (990→650, 700) keep their accent tint from the scale
162
+ const lightness = BASE_THEMES[themeId].lightness
163
+ const TEXT_KEYS: ScaleKey[] = ['600', '550', '500', '450', '400', '350', '300', '250', '200', '150', '100', '090', '080', '070', '060', '050', '040', '030', '020', '010']
164
+ for (const key of TEXT_KEYS) {
165
+ const l = lightness[key]
166
+ const a = light ? (100 - l) / 100 : l / 100
167
+ root.style.setProperty(`--color-neutral-${key}`, `rgba(${light ? '0, 0, 0' : '255, 255, 255'}, ${a.toFixed(2)})`)
168
+ }
169
+
170
+ // Semantic text tokens: white+alpha for dark, black+alpha for light
171
+ const tb = light ? '0, 0, 0' : '255, 255, 255'
172
+ root.style.setProperty('--foreground', `rgba(${tb}, 0.83)`)
173
+ root.style.setProperty('--surface-foreground', `rgba(${tb}, 0.83)`)
174
+ root.style.setProperty('--popover-foreground', `rgba(${tb}, 0.83)`)
175
+ root.style.setProperty('--muted-foreground', `rgba(${tb}, 0.46)`)
176
+ root.style.setProperty('--text-primary', `rgba(${tb}, 0.83)`)
177
+ root.style.setProperty('--text-secondary', `rgba(${tb}, 0.46)`)
178
+ root.style.setProperty('--text-tertiary', `rgba(${tb}, 0.64)`)
179
+ root.style.setProperty('--text-emphasis', light ? '#0a0a0a' : '#ffffff')
180
+ root.style.setProperty('--primary-foreground', light ? '#0a0a0a' : '#ffffff')
181
+ root.style.setProperty('--destructive-foreground', light ? '#0a0a0a' : '#ffffff')
133
182
 
134
183
  // Surface hover: accent glass at 10% for visible hover states
135
184
  const glassBase = accentHue !== null ? hslToHex(accentHue, 80, 50) : null
@@ -1,17 +1,31 @@
1
+ import { ACCENT_DEFS } from './theme-engine.ts'
2
+ import { useAccentColor } from './accent-context.ts'
3
+
1
4
  export type ToolrAppId = 'toolr' | 'configr' | 'reviewr' | 'vibr' | 'learnr' | 'planr' | 'seedr'
2
5
 
3
- export const TOOLR_APPS: Record<ToolrAppId, { name: string; color: string }> = {
4
- toolr: { name: 'Toolr', color: '#60a5fa' },
5
- configr: { name: 'Configr', color: '#a78bfa' },
6
- reviewr: { name: 'Reviewr', color: '#fb923c' },
7
- learnr: { name: 'Learnr', color: '#facc15' },
8
- seedr: { name: 'Seedr', color: '#2dd4bf' },
9
- planr: { name: 'Planr', color: '#f472b6' },
10
- vibr: { name: 'Vibr', color: '#f87171' },
6
+ // Accent color ID per app resolved to hex via ACCENT_DEFS at render time
7
+ export const TOOLR_APPS: Record<ToolrAppId, { name: string; accent: string }> = {
8
+ toolr: { name: 'Toolr', accent: 'blue' }, // #3b82f6
9
+ configr: { name: 'Configr', accent: 'violet' }, // #8b5cf6
10
+ reviewr: { name: 'Reviewr', accent: 'orange' }, // #f97316
11
+ learnr: { name: 'Learnr', accent: 'yellow' }, // #eab308
12
+ seedr: { name: 'Seedr', accent: 'green' }, // #22c55e
13
+ planr: { name: 'Planr', accent: 'pink' }, // #ec4899 (not in ACCENT_DEFS, fallback hex)
14
+ vibr: { name: 'Vibr', accent: 'red' }, // #ef4444
15
+ }
16
+
17
+ const ACCENT_HEX_MAP: Record<string, string> = Object.fromEntries(ACCENT_DEFS.map((a) => [a.id, a.dotColor]))
18
+ // Fallback hex for accents not in ACCENT_DEFS
19
+ ACCENT_HEX_MAP['pink'] = '#ec4899'
20
+
21
+ /** Resolve an accent ID to its hex color */
22
+ export function resolveAccentHex(accent: string): string {
23
+ return ACCENT_HEX_MAP[accent] ?? ACCENT_HEX_MAP['blue']
11
24
  }
12
25
 
13
26
  export function ToolrAppLogo({ app, color: colorOverride, size, className }: { app?: ToolrAppId; color?: string; size?: number; className?: string }) {
14
- const color = colorOverride ?? (app ? TOOLR_APPS[app]?.color : undefined) ?? '#60a5fa'
27
+ const accentColor = useAccentColor()
28
+ const color = colorOverride ?? (app ? resolveAccentHex(TOOLR_APPS[app]?.accent) : undefined) ?? (accentColor ? resolveAccentHex(accentColor) : undefined) ?? ACCENT_HEX_MAP['blue']
15
29
  return (
16
30
  <svg
17
31
  {...(size ? { width: size, height: size } : {})}
@@ -28,6 +28,8 @@ import { cn } from '../../lib/cn.ts'
28
28
  import { Input } from '../../ui/input.tsx'
29
29
  import { ResizableTextarea } from '../../ui/resizable-textarea.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 { useCapturedIssues } from './use-captured-issues.ts'
32
34
  import type { CapturedError, CapturedIssuesApi } from './types.ts'
33
35
 
@@ -40,6 +42,7 @@ export interface CapturedIssuesPanelProps {
40
42
  onDismiss?: () => void
41
43
  /** Called after successful submission */
42
44
  onSubmitSuccess?: (issueId?: string) => void
45
+ accentColor?: FormColor
43
46
  className?: string
44
47
  }
45
48
 
@@ -48,8 +51,11 @@ export function CapturedIssuesPanel({
48
51
  errors,
49
52
  onDismiss,
50
53
  onSubmitSuccess,
54
+ accentColor,
51
55
  className,
52
56
  }: CapturedIssuesPanelProps) {
57
+ const contextAccent = useAccentColor()
58
+ const effectiveColor = accentColor ?? contextAccent ?? 'blue'
53
59
  const {
54
60
  errorCount,
55
61
  warnCount,
@@ -68,9 +74,10 @@ export function CapturedIssuesPanel({
68
74
  const hasErrors = errorCount > 0 || warnCount > 0
69
75
 
70
76
  return (
77
+ <AccentColorProvider value={effectiveColor}>
71
78
  <div className={cn('space-y-6', className)}>
72
79
  {hasErrors ? (
73
- <div className="bg-neutral-900 border border-neutral-700 rounded-lg p-4 space-y-4">
80
+ <div className="bg-neutral-980 border border-neutral-700 rounded-lg p-4 space-y-4">
74
81
  {/* Error Summary */}
75
82
  <div className="flex items-center justify-between">
76
83
  <div className="flex items-center gap-2">
@@ -94,7 +101,7 @@ export function CapturedIssuesPanel({
94
101
  {/* Error List */}
95
102
  <div className="space-y-2">
96
103
  <p className="text-md text-neutral-400">Captured Errors</p>
97
- <div className="max-h-48 overflow-y-auto bg-neutral-950 border border-neutral-700 rounded-lg p-3 space-y-1">
104
+ <div className="max-h-48 overflow-y-auto bg-neutral-990 border border-neutral-700 rounded-lg p-3 space-y-1">
98
105
  {errors.map((error) => (
99
106
  <div key={error.fingerprint} className="text-sm font-mono break-words">
100
107
  <span className={cn('mr-2 shrink-0', error.level === 'warning' ? 'text-yellow-400' : 'text-red-400')}>
@@ -119,7 +126,7 @@ export function CapturedIssuesPanel({
119
126
  onChange={(e) => setDescription(e.target.value)}
120
127
  placeholder="What were you doing when this happened?"
121
128
  rows={3}
122
- className="w-full px-3 py-1.5 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-300 placeholder-neutral-500 focus:outline-none focus:border-blue-500 transition-colors resize-none text-md"
129
+ className="w-full px-3 py-1.5 bg-neutral-960 border border-neutral-600 rounded-lg text-neutral-300 placeholder-neutral-500 focus:outline-none focus:border-blue-500 transition-colors resize-none text-md"
123
130
  />
124
131
  <Input
125
132
  type="text"
@@ -136,6 +143,7 @@ export function CapturedIssuesPanel({
136
143
  onClick={handleDismiss}
137
144
  disabled={isSubmitting}
138
145
  size="sm"
146
+ accentColor="red"
139
147
  tooltip={{ title: 'Dismiss', description: 'Ignore these errors' }}
140
148
  tooltipPosition="top"
141
149
  />
@@ -150,14 +158,14 @@ export function CapturedIssuesPanel({
150
158
  onClick={handleSubmit}
151
159
  disabled={isSubmitting}
152
160
  size="sm"
153
- color="red"
161
+ accentColor="cyan"
154
162
  tooltip={{ title: 'Send Report', description: 'Submit error report to developers' }}
155
163
  tooltipPosition="top"
156
164
  />
157
165
  </div>
158
166
  </div>
159
167
  ) : (
160
- <div className="bg-neutral-900 border border-neutral-700 rounded-lg p-6">
168
+ <div className="bg-neutral-980 border border-neutral-700 rounded-lg p-6">
161
169
  <div className="flex items-center gap-4">
162
170
  <div className="w-10 h-10 bg-green-500/10 rounded-lg flex items-center justify-center">
163
171
  <Check className="w-5 h-5 text-green-400" />
@@ -172,9 +180,9 @@ export function CapturedIssuesPanel({
172
180
 
173
181
  {/* Previously Reported */}
174
182
  {submittedErrors.length > 0 && (
175
- <div className="bg-neutral-900 border border-neutral-700 rounded-lg overflow-hidden">
183
+ <div className="bg-neutral-980 border border-neutral-700 rounded-lg overflow-hidden">
176
184
  <details className="group">
177
- <summary className="flex items-center justify-between p-4 cursor-pointer hover:bg-neutral-800 transition-colors">
185
+ <summary className="flex items-center justify-between p-4 cursor-pointer hover:bg-neutral-960 transition-colors">
178
186
  <div className="flex items-center gap-2">
179
187
  <Check className="w-4 h-4 text-green-400" />
180
188
  <span className="text-md text-neutral-300">Previously Reported</span>
@@ -186,7 +194,7 @@ export function CapturedIssuesPanel({
186
194
  {submittedErrors.map((error) => (
187
195
  <div
188
196
  key={error.fingerprint}
189
- className="flex items-start gap-3 p-3 bg-neutral-950 border border-neutral-700 rounded-lg"
197
+ className="flex items-start gap-3 p-3 bg-neutral-990 border border-neutral-700 rounded-lg"
190
198
  >
191
199
  <Check className="w-4 h-4 text-green-400 mt-0.5 shrink-0" />
192
200
  <div className="flex-1 min-w-0">
@@ -210,5 +218,6 @@ export function CapturedIssuesPanel({
210
218
  </div>
211
219
  )}
212
220
  </div>
221
+ </AccentColorProvider>
213
222
  )
214
223
  }