@toolr/ui-design 0.1.0

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 (112) hide show
  1. package/README.md +63 -0
  2. package/components/content/info-panel-primitives.tsx +297 -0
  3. package/components/diagrams/diagram-utils.tsx +908 -0
  4. package/components/hooks/use-click-outside.ts +27 -0
  5. package/components/hooks/use-dropdown-max-height.ts +20 -0
  6. package/components/hooks/use-navigation-history.ts +94 -0
  7. package/components/lib/ai-tools.tsx +44 -0
  8. package/components/lib/cn.ts +6 -0
  9. package/components/lib/form-colors.ts +32 -0
  10. package/components/lib/theme-engine.ts +97 -0
  11. package/components/lib/toolr-brand.tsx +31 -0
  12. package/components/sections/ai-tools-paths/index.ts +37 -0
  13. package/components/sections/ai-tools-paths/tools-paths-panel.tsx +212 -0
  14. package/components/sections/ai-tools-paths/types.ts +111 -0
  15. package/components/sections/ai-tools-paths/use-tools-paths.ts +159 -0
  16. package/components/sections/captured-issues/captured-issues-panel.tsx +214 -0
  17. package/components/sections/captured-issues/index.ts +38 -0
  18. package/components/sections/captured-issues/types.ts +113 -0
  19. package/components/sections/captured-issues/use-captured-issues.ts +111 -0
  20. package/components/sections/golden-snapshots/file-diff-viewer.tsx +420 -0
  21. package/components/sections/golden-snapshots/golden-sync-panel.tsx +223 -0
  22. package/components/sections/golden-snapshots/index.ts +145 -0
  23. package/components/sections/golden-snapshots/snapshot-manager.tsx +200 -0
  24. package/components/sections/golden-snapshots/status-overview.tsx +305 -0
  25. package/components/sections/golden-snapshots/types.ts +288 -0
  26. package/components/sections/golden-snapshots/use-golden-sync.ts +477 -0
  27. package/components/sections/golden-snapshots/version-manager.tsx +186 -0
  28. package/components/sections/prompt-editor/file-type-tabbed-prompt-editor.tsx +210 -0
  29. package/components/sections/prompt-editor/index.ts +121 -0
  30. package/components/sections/prompt-editor/simulator-prompt-editor.tsx +276 -0
  31. package/components/sections/prompt-editor/tabbed-prompt-editor.tsx +514 -0
  32. package/components/sections/prompt-editor/types.ts +101 -0
  33. package/components/sections/prompt-editor/use-prompt-editor.ts +131 -0
  34. package/components/sections/report-bug/error-logger.ts +392 -0
  35. package/components/sections/report-bug/index.ts +59 -0
  36. package/components/sections/report-bug/issue-reporter-api.ts +83 -0
  37. package/components/sections/report-bug/report-bug-form.tsx +282 -0
  38. package/components/sections/report-bug/screenshot-uploader.tsx +228 -0
  39. package/components/sections/report-bug/use-report-bug.ts +170 -0
  40. package/components/sections/snapshot-browser/index.ts +53 -0
  41. package/components/sections/snapshot-browser/snapshot-browser-panel.tsx +147 -0
  42. package/components/sections/snapshot-browser/snapshot-tree.tsx +451 -0
  43. package/components/sections/snapshot-browser/types.ts +106 -0
  44. package/components/sections/snapshot-browser/use-snapshot-browser.ts +125 -0
  45. package/components/sections/snippets-editor/index.ts +31 -0
  46. package/components/sections/snippets-editor/snippets-editor.tsx +381 -0
  47. package/components/sections/snippets-editor/types.ts +48 -0
  48. package/components/sections/snippets-editor/use-snippets-editor.ts +217 -0
  49. package/components/ui/action-dialog.tsx +309 -0
  50. package/components/ui/ai-action-button.tsx +137 -0
  51. package/components/ui/ai-execution-action-buttons.tsx +106 -0
  52. package/components/ui/badge.tsx +67 -0
  53. package/components/ui/bottom-panel-header.tsx +240 -0
  54. package/components/ui/breadcrumb.tsx +168 -0
  55. package/components/ui/checkbox.tsx +102 -0
  56. package/components/ui/collapsible-section.tsx +100 -0
  57. package/components/ui/confirm-badge.tsx +71 -0
  58. package/components/ui/detail-section.tsx +67 -0
  59. package/components/ui/detail-view-wrapper.tsx +55 -0
  60. package/components/ui/editor-placeholder-card.tsx +197 -0
  61. package/components/ui/editor-toolbar.tsx +123 -0
  62. package/components/ui/execution-details-panel.tsx +93 -0
  63. package/components/ui/extension-list-card.tsx +105 -0
  64. package/components/ui/file-structure-section.tsx +373 -0
  65. package/components/ui/file-tree.tsx +171 -0
  66. package/components/ui/files-panel.tsx +251 -0
  67. package/components/ui/filter-dropdown.tsx +173 -0
  68. package/components/ui/form-actions.tsx +127 -0
  69. package/components/ui/frontmatter-form-header.tsx +80 -0
  70. package/components/ui/icon-button.tsx +388 -0
  71. package/components/ui/input.tsx +211 -0
  72. package/components/ui/label.tsx +159 -0
  73. package/components/ui/layout-tab-bar.tsx +289 -0
  74. package/components/ui/modal.tsx +194 -0
  75. package/components/ui/nav-card.tsx +81 -0
  76. package/components/ui/navigation-bar.tsx +285 -0
  77. package/components/ui/number-input.tsx +165 -0
  78. package/components/ui/registry-browser.tsx +261 -0
  79. package/components/ui/registry-card.tsx +710 -0
  80. package/components/ui/registry-detail.tsx +224 -0
  81. package/components/ui/resizable-textarea.tsx +290 -0
  82. package/components/ui/scope-badge.tsx +67 -0
  83. package/components/ui/segmented-toggle.tsx +133 -0
  84. package/components/ui/select.tsx +172 -0
  85. package/components/ui/selection-grid.tsx +313 -0
  86. package/components/ui/setting-row.tsx +97 -0
  87. package/components/ui/snapshot-card.tsx +107 -0
  88. package/components/ui/snippets-panel.tsx +161 -0
  89. package/components/ui/sort-dropdown.tsx +109 -0
  90. package/components/ui/status-card.tsx +96 -0
  91. package/components/ui/tab-bar.tsx +340 -0
  92. package/components/ui/toggle.tsx +142 -0
  93. package/components/ui/tooltip.tsx +326 -0
  94. package/dist/content.d.ts +110 -0
  95. package/dist/content.js +195 -0
  96. package/dist/diagrams.d.ts +371 -0
  97. package/dist/diagrams.js +702 -0
  98. package/dist/index.d.ts +2714 -0
  99. package/dist/index.js +11220 -0
  100. package/dist/preset.d.ts +24 -0
  101. package/dist/preset.js +17 -0
  102. package/dist/tokens/tokens/primitives.css +45 -0
  103. package/dist/tokens/tokens/semantic.css +46 -0
  104. package/dist/tokens/tokens/theme.css +11 -0
  105. package/dist/tokens/tokens/tokens.json +65 -0
  106. package/index.ts +123 -0
  107. package/package.json +63 -0
  108. package/tailwind-preset.ts +22 -0
  109. package/tokens/primitives.css +45 -0
  110. package/tokens/semantic.css +46 -0
  111. package/tokens/theme.css +11 -0
  112. package/tokens/tokens.json +65 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * AI Tools Paths — Shared type definitions
3
+ *
4
+ * Part of: Sections > AI Tools Paths
5
+ *
6
+ * These types define the CONTRACT between the UI layer (this package) and
7
+ * the Rust/Tauri backend that each app must implement. Every type here has
8
+ * a 1:1 mapping to a Rust struct returned by Tauri commands.
9
+ *
10
+ * IMPORTANT FOR AI AGENTS:
11
+ * When adding a new field here, the corresponding Rust struct must also be
12
+ * updated (and vice versa). The serde rename attributes in Rust use camelCase
13
+ * to match these TypeScript interfaces.
14
+ *
15
+ * Generic design:
16
+ * - Tools are keyed by string ID (not hardcoded to specific tools)
17
+ * - Configr uses: "claude", "gemini", "copilot", "codex", "opencode"
18
+ * - Other apps can define their own tool sets
19
+ * - The UI renders whatever tools the consumer provides
20
+ */
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Tool configuration
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /** Per-tool configuration state */
27
+ export interface AiToolConfig {
28
+ /** Unique identifier for the tool (e.g. "claude", "gemini") */
29
+ id: string
30
+ /** Display name (e.g. "Claude Code", "Gemini CLI") */
31
+ name: string
32
+ /** Whether the tool is enabled for use */
33
+ enabled: boolean
34
+ /** User-configured or auto-detected binary path */
35
+ binaryPath: string
36
+ /** Whether the tool was found during detection */
37
+ detected: boolean
38
+ /** Path found by auto-detection (may differ from binaryPath if user overrode it) */
39
+ detectedPath?: string
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Detection results
44
+ // ---------------------------------------------------------------------------
45
+
46
+ /** Result of detecting a single tool's binary */
47
+ export interface ToolDetectionResult {
48
+ /** Whether the binary was found */
49
+ detected: boolean
50
+ /** Resolved path to the binary, if found */
51
+ path?: string
52
+ }
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // API adapter interface
56
+ // ---------------------------------------------------------------------------
57
+
58
+ /**
59
+ * ToolsPathsApi — The callback interface that each app must implement.
60
+ *
61
+ * This is the bridge between the shared UI and the app-specific backend.
62
+ * Each function maps to a Tauri command (or any other backend).
63
+ *
64
+ * ┌─────────────────────────────────────────────────────────────────────┐
65
+ * │ RUST BACKEND IMPLEMENTATION GUIDE │
66
+ * │ │
67
+ * │ Each method below corresponds to a Tauri #[tauri::command]. │
68
+ * │ The Rust reference implementation lives in: │
69
+ * │ configr/main/src-tauri/src/commands/tools/ │
70
+ * │ ├── mod.rs — Types + helpers │
71
+ * │ └── detection.rs — Binary detection logic │
72
+ * │ │
73
+ * │ REQUIRED Tauri commands for full functionality: │
74
+ * │ │
75
+ * │ DETECTION: │
76
+ * │ detect_all_tools() → Record<string, ToolDetectionResult> │
77
+ * │ Scans PATH and known install locations for all tool binaries. │
78
+ * │ Returns a map of tool ID → detection result. │
79
+ * │ │
80
+ * │ detect_tool(toolId) → ToolDetectionResult │
81
+ * │ Scans for a single tool binary. Used for per-tool refresh. │
82
+ * │ │
83
+ * │ VALIDATION: │
84
+ * │ validate_binary_path(path) → bool │
85
+ * │ Checks if a given path points to a valid executable. │
86
+ * │ Typically: fs::metadata(path).is_ok() && is_executable(path) │
87
+ * │ │
88
+ * │ DETECTION STRATEGY (Rust backend should implement): │
89
+ * │ 1. Check PATH environment variable (which <tool-binary>) │
90
+ * │ 2. Check common install locations: │
91
+ * │ - /usr/local/bin/<binary> │
92
+ * │ - /opt/homebrew/bin/<binary> │
93
+ * │ - ~/.local/bin/<binary> │
94
+ * │ - ~/.cargo/bin/<binary> (for Rust-based tools) │
95
+ * │ - ~/.npm/bin/<binary> (for npm-based tools) │
96
+ * │ 3. Check tool-specific locations: │
97
+ * │ - Claude: ~/.claude/bin/claude │
98
+ * │ - Copilot: gh extension path │
99
+ * │ 4. Return first valid path found, or detected: false │
100
+ * └─────────────────────────────────────────────────────────────────────┘
101
+ */
102
+ export interface ToolsPathsApi {
103
+ /** Scan all tools and return detection results keyed by tool ID */
104
+ detectAll: () => Promise<Record<string, ToolDetectionResult>>
105
+
106
+ /** Scan a single tool and return its detection result */
107
+ detectTool: (toolId: string) => Promise<ToolDetectionResult>
108
+
109
+ /** Check if a given binary path is valid (exists and is executable) */
110
+ validatePath: (path: string) => Promise<boolean>
111
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * useToolsPaths — Tool detection and configuration state hook
3
+ *
4
+ * Part of: Sections > AI Tools Paths
5
+ *
6
+ * Manages tool detection state, per-tool refresh tracking, and path validation.
7
+ * Used internally by ToolsPathsPanel but can also be used standalone for
8
+ * custom UIs.
9
+ *
10
+ * AI agent notes:
11
+ * - This hook does NOT store tool configs — the consumer owns that state.
12
+ * It only manages detection/scanning ephemeral state.
13
+ * - The hook tracks which tools have been scanned, which are currently
14
+ * refreshing, and whether a full scan has completed.
15
+ * - Path validation is debounced by the consumer (typically on blur or
16
+ * after typing stops).
17
+ */
18
+
19
+ import { useState, useCallback, useRef, useEffect } from 'react'
20
+ import type { AiToolConfig, ToolsPathsApi, ToolDetectionResult } from './types.ts'
21
+
22
+ export interface UseToolsPathsOptions {
23
+ api: ToolsPathsApi
24
+ tools: AiToolConfig[]
25
+ onToolConfigChange: (toolId: string, config: Partial<AiToolConfig>) => void
26
+ }
27
+
28
+ export interface UseToolsPathsReturn {
29
+ /** Whether a full scan is in progress */
30
+ isDetecting: boolean
31
+ /** Whether a full scan has completed at least once */
32
+ hasScanned: boolean
33
+ /** Set of tool IDs currently being individually refreshed */
34
+ refreshingTools: Set<string>
35
+ /** Set of tool IDs that were found during the last scan */
36
+ scannedTools: Set<string>
37
+ /** Run detection for all tools */
38
+ detectAll: () => Promise<void>
39
+ /** Run detection for a single tool */
40
+ detectTool: (toolId: string) => Promise<void>
41
+ /** Validate and update a tool's binary path */
42
+ validateAndUpdatePath: (toolId: string, path: string) => Promise<void>
43
+ /** Toggle a tool's enabled state */
44
+ toggleEnabled: (toolId: string) => void
45
+ }
46
+
47
+ export function useToolsPaths({ api, tools, onToolConfigChange }: UseToolsPathsOptions): UseToolsPathsReturn {
48
+ const [isDetecting, setIsDetecting] = useState(false)
49
+ const [hasScanned, setHasScanned] = useState(false)
50
+ const [refreshingTools, setRefreshingTools] = useState<Set<string>>(new Set())
51
+ const [scannedTools, setScannedTools] = useState<Set<string>>(new Set())
52
+ const wasDetectingRef = useRef(false)
53
+
54
+ // Track when full detection completes
55
+ useEffect(() => {
56
+ if (wasDetectingRef.current && !isDetecting) {
57
+ setHasScanned(true)
58
+ }
59
+ wasDetectingRef.current = isDetecting
60
+ }, [isDetecting])
61
+
62
+ const detectAll = useCallback(async () => {
63
+ setIsDetecting(true)
64
+ try {
65
+ const results = await api.detectAll()
66
+ const detected = new Set<string>()
67
+
68
+ for (const tool of tools) {
69
+ const result = results[tool.id]
70
+ if (result) {
71
+ onToolConfigChange(tool.id, {
72
+ detected: result.detected,
73
+ detectedPath: result.path,
74
+ binaryPath: result.path || tool.binaryPath,
75
+ })
76
+ if (result.detected) {
77
+ detected.add(tool.id)
78
+ }
79
+ }
80
+ }
81
+
82
+ setScannedTools(detected)
83
+ } finally {
84
+ setIsDetecting(false)
85
+ }
86
+ }, [api, tools, onToolConfigChange])
87
+
88
+ const detectTool = useCallback(async (toolId: string) => {
89
+ setRefreshingTools((prev) => new Set(prev).add(toolId))
90
+ try {
91
+ const result: ToolDetectionResult = await api.detectTool(toolId)
92
+ onToolConfigChange(toolId, {
93
+ detected: result.detected,
94
+ detectedPath: result.path,
95
+ binaryPath: result.path || '',
96
+ })
97
+ setScannedTools((prev) => {
98
+ const next = new Set(prev)
99
+ if (result.detected) {
100
+ next.add(toolId)
101
+ } else {
102
+ next.delete(toolId)
103
+ }
104
+ return next
105
+ })
106
+ } finally {
107
+ setRefreshingTools((prev) => {
108
+ const next = new Set(prev)
109
+ next.delete(toolId)
110
+ return next
111
+ })
112
+ }
113
+ }, [api, onToolConfigChange])
114
+
115
+ const validateAndUpdatePath = useCallback(async (toolId: string, path: string) => {
116
+ onToolConfigChange(toolId, { binaryPath: path })
117
+ setScannedTools((prev) => {
118
+ const next = new Set(prev)
119
+ next.delete(toolId)
120
+ return next
121
+ })
122
+ setHasScanned(false)
123
+
124
+ if (path.trim()) {
125
+ try {
126
+ const isValid = await api.validatePath(path.trim())
127
+ onToolConfigChange(toolId, {
128
+ binaryPath: path.trim(),
129
+ detected: isValid,
130
+ detectedPath: isValid ? path.trim() : undefined,
131
+ })
132
+ } catch {
133
+ onToolConfigChange(toolId, {
134
+ binaryPath: path.trim(),
135
+ detected: false,
136
+ detectedPath: undefined,
137
+ })
138
+ }
139
+ }
140
+ }, [api, onToolConfigChange])
141
+
142
+ const toggleEnabled = useCallback((toolId: string) => {
143
+ const tool = tools.find((t) => t.id === toolId)
144
+ if (tool) {
145
+ onToolConfigChange(toolId, { enabled: !tool.enabled })
146
+ }
147
+ }, [tools, onToolConfigChange])
148
+
149
+ return {
150
+ isDetecting,
151
+ hasScanned,
152
+ refreshingTools,
153
+ scannedTools,
154
+ detectAll,
155
+ detectTool,
156
+ validateAndUpdatePath,
157
+ toggleEnabled,
158
+ }
159
+ }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * CapturedIssuesPanel — Self-contained captured issues review and reporting
3
+ *
4
+ * Part of: Sections > Captured Issues
5
+ *
6
+ * Replicates the configr "Settings > Support > Captured Issues" page as a
7
+ * reusable component. Shows captured errors/warnings, lets the user add
8
+ * context and submit a report, or dismiss. Also shows previously reported
9
+ * errors in a collapsible section.
10
+ *
11
+ * Usage:
12
+ * <CapturedIssuesPanel
13
+ * api={capturedIssuesApi}
14
+ * errors={currentErrors}
15
+ * onDismiss={() => logger.clearLogs()}
16
+ * onSubmitSuccess={(id) => toast(`Reported ${id}`)}
17
+ * />
18
+ *
19
+ * AI agent notes:
20
+ * - All backend calls go through the api prop (CapturedIssuesApi interface)
21
+ * - Errors are passed in as props — the component does not capture them itself
22
+ * - Uses ui-design components (Input, ResizableTextarea, IconButton) for consistency
23
+ * - Dark theme with Catppuccin-like colors matching configr
24
+ */
25
+
26
+ import { AlertTriangle, Check, X, Send, ChevronDown, Shield, Loader2 } from 'lucide-react'
27
+ import { cn } from '../../lib/cn.ts'
28
+ import { Input } from '../../ui/input.tsx'
29
+ import { ResizableTextarea } from '../../ui/resizable-textarea.tsx'
30
+ import { IconButton } from '../../ui/icon-button.tsx'
31
+ import { useCapturedIssues } from './use-captured-issues.ts'
32
+ import type { CapturedError, CapturedIssuesApi } from './types.ts'
33
+
34
+ export interface CapturedIssuesPanelProps {
35
+ /** Backend API implementation */
36
+ api: CapturedIssuesApi
37
+ /** Current captured errors to display */
38
+ errors: CapturedError[]
39
+ /** Called after errors are dismissed */
40
+ onDismiss?: () => void
41
+ /** Called after successful submission */
42
+ onSubmitSuccess?: (issueId?: string) => void
43
+ className?: string
44
+ }
45
+
46
+ export function CapturedIssuesPanel({
47
+ api,
48
+ errors,
49
+ onDismiss,
50
+ onSubmitSuccess,
51
+ className,
52
+ }: CapturedIssuesPanelProps) {
53
+ const {
54
+ errorCount,
55
+ warnCount,
56
+ title,
57
+ setTitle,
58
+ description,
59
+ setDescription,
60
+ email,
61
+ setEmail,
62
+ isSubmitting,
63
+ submittedErrors,
64
+ handleSubmit,
65
+ handleDismiss,
66
+ } = useCapturedIssues({ api, errors, onDismiss, onSubmitSuccess })
67
+
68
+ const hasErrors = errorCount > 0 || warnCount > 0
69
+
70
+ return (
71
+ <div className={cn('space-y-6', className)}>
72
+ {hasErrors ? (
73
+ <div className="bg-[#181825] border border-[#313244] rounded-lg p-4 space-y-4">
74
+ {/* Error Summary */}
75
+ <div className="flex items-center justify-between">
76
+ <div className="flex items-center gap-2">
77
+ <AlertTriangle className="w-4 h-4 text-red-400" />
78
+ <span className="text-sm text-[#cdd6f4]">
79
+ {errorCount > 0 && (
80
+ <span className="text-red-400">
81
+ {errorCount} error{errorCount !== 1 ? 's' : ''}
82
+ </span>
83
+ )}
84
+ {errorCount > 0 && warnCount > 0 && <span className="text-[#6c7086]">, </span>}
85
+ {warnCount > 0 && (
86
+ <span className="text-yellow-400">
87
+ {warnCount} warning{warnCount !== 1 ? 's' : ''}
88
+ </span>
89
+ )}
90
+ </span>
91
+ </div>
92
+ </div>
93
+
94
+ {/* Error List */}
95
+ <div className="space-y-2">
96
+ <p className="text-sm text-[#a6adc8]">Captured Errors</p>
97
+ <div className="max-h-48 overflow-y-auto bg-[#11111b] border border-[#313244] rounded-lg p-3 space-y-1">
98
+ {errors.map((error) => (
99
+ <div key={error.fingerprint} className="text-xs font-mono break-words">
100
+ <span className={cn('mr-2 shrink-0', error.level === 'warning' ? 'text-yellow-400' : 'text-red-400')}>
101
+ {error.count > 1 ? `\u00d7${error.count}` : '\u2022'}
102
+ </span>
103
+ <span className="text-[#a6adc8] break-all">{error.message}</span>
104
+ </div>
105
+ ))}
106
+ </div>
107
+ </div>
108
+
109
+ {/* Optional Details */}
110
+ <div className="space-y-3">
111
+ <p className="text-sm text-[#a6adc8]">Add context (optional)</p>
112
+ <Input
113
+ value={title}
114
+ onChange={setTitle}
115
+ placeholder="Brief summary of what you were doing"
116
+ />
117
+ <ResizableTextarea
118
+ value={description}
119
+ onChange={(e) => setDescription(e.target.value)}
120
+ placeholder="What were you doing when this happened?"
121
+ rows={3}
122
+ className="w-full px-3 py-1.5 bg-[#313244] border border-[#45475a] rounded-lg text-[#cdd6f4] placeholder-[#6c7086] focus:outline-none focus:border-blue-500 transition-colors resize-none text-sm"
123
+ />
124
+ <Input
125
+ type="text"
126
+ value={email}
127
+ onChange={setEmail}
128
+ placeholder="Email for follow-up"
129
+ />
130
+ </div>
131
+
132
+ {/* Actions */}
133
+ <div className="flex items-center justify-end gap-2 pt-2">
134
+ <IconButton
135
+ icon={<X className="w-3.5 h-3.5" />}
136
+ onClick={handleDismiss}
137
+ disabled={isSubmitting}
138
+ size="sm"
139
+ tooltip={{ title: 'Dismiss', description: 'Ignore these errors' }}
140
+ tooltipPosition="top"
141
+ />
142
+ <IconButton
143
+ icon={
144
+ isSubmitting ? (
145
+ <Loader2 className="w-3.5 h-3.5 animate-spin" />
146
+ ) : (
147
+ <Send className="w-3.5 h-3.5" />
148
+ )
149
+ }
150
+ onClick={handleSubmit}
151
+ disabled={isSubmitting}
152
+ size="sm"
153
+ color="red"
154
+ tooltip={{ title: 'Send Report', description: 'Submit error report to developers' }}
155
+ tooltipPosition="top"
156
+ />
157
+ </div>
158
+ </div>
159
+ ) : (
160
+ <div className="bg-[#181825] border border-[#313244] rounded-lg p-6">
161
+ <div className="flex items-center gap-4">
162
+ <div className="w-10 h-10 bg-green-500/10 rounded-lg flex items-center justify-center">
163
+ <Check className="w-5 h-5 text-green-400" />
164
+ </div>
165
+ <div>
166
+ <h3 className="text-[#cdd6f4] font-medium">No Issues Captured</h3>
167
+ <p className="text-sm text-[#6c7086]">Everything is running smoothly.</p>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ )}
172
+
173
+ {/* Previously Reported */}
174
+ {submittedErrors.length > 0 && (
175
+ <div className="bg-[#181825] border border-[#313244] rounded-lg overflow-hidden">
176
+ <details className="group">
177
+ <summary className="flex items-center justify-between p-4 cursor-pointer hover:bg-[#1e1e2e] transition-colors">
178
+ <div className="flex items-center gap-2">
179
+ <Check className="w-4 h-4 text-green-400" />
180
+ <span className="text-sm text-[#cdd6f4]">Previously Reported</span>
181
+ <span className="text-xs text-[#6c7086]">({submittedErrors.length})</span>
182
+ </div>
183
+ <ChevronDown className="w-4 h-4 text-[#6c7086] transition-transform group-open:rotate-180" />
184
+ </summary>
185
+ <div className="border-t border-[#313244] p-4 space-y-2">
186
+ {submittedErrors.map((error) => (
187
+ <div
188
+ key={error.fingerprint}
189
+ className="flex items-start gap-3 p-3 bg-[#11111b] border border-[#313244] rounded-lg"
190
+ >
191
+ <Check className="w-4 h-4 text-green-400 mt-0.5 shrink-0" />
192
+ <div className="flex-1 min-w-0">
193
+ <p
194
+ className="text-sm text-[#cdd6f4] font-mono truncate"
195
+ title={error.message}
196
+ >
197
+ {error.message}
198
+ </p>
199
+ <div className="flex items-center gap-3 mt-1 text-xs text-[#6c7086]">
200
+ {error.count > 0 && (
201
+ <span className="text-yellow-400/80">+{error.count} since</span>
202
+ )}
203
+ <span>{new Date(error.submittedAt).toLocaleDateString()}</span>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ ))}
208
+ </div>
209
+ </details>
210
+ </div>
211
+ )}
212
+ </div>
213
+ )
214
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Captured Issues — Section barrel export
3
+ *
4
+ * This section provides a complete, reusable captured issues review and
5
+ * reporting feature. It shows captured errors/warnings, lets the user add
6
+ * context and submit a report, and tracks previously reported errors.
7
+ *
8
+ * File structure:
9
+ * - captured-issues-panel.tsx — Main panel component (drop-in usage)
10
+ * - use-captured-issues.ts — State management hook (used by panel, also standalone)
11
+ * - types.ts — Types and API interface contract
12
+ *
13
+ * Quick start for consuming apps:
14
+ * import { CapturedIssuesPanel, type CapturedIssuesApi } from '@toolr/ui-design'
15
+ *
16
+ * const api: CapturedIssuesApi = {
17
+ * getSystemInfo: () => invoke('get_system_info'),
18
+ * submitIssue: (data) => invoke('submit_issue', data),
19
+ * dismissErrors: () => invoke('dismiss_errors'),
20
+ * getSubmittedErrors: () => invoke('get_submitted_errors'),
21
+ * }
22
+ *
23
+ * <CapturedIssuesPanel
24
+ * api={api}
25
+ * errors={currentErrors}
26
+ * onDismiss={() => logger.clearLogs()}
27
+ * onSubmitSuccess={(id) => toast(`Reported ${id}`)}
28
+ * />
29
+ */
30
+
31
+ // Main panel component
32
+ export { CapturedIssuesPanel, type CapturedIssuesPanelProps } from './captured-issues-panel.tsx'
33
+
34
+ // Hook for custom UIs
35
+ export { useCapturedIssues, type UseCapturedIssuesOptions, type UseCapturedIssuesReturn } from './use-captured-issues.ts'
36
+
37
+ // Types and API interface
38
+ export type { CapturedError, SubmittedError, CapturedIssuesApi } from './types.ts'
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Captured Issues — Shared type definitions
3
+ *
4
+ * Part of: Sections > Captured Issues
5
+ *
6
+ * These types define the CONTRACT between the UI layer (this package) and
7
+ * the backend that each app must implement. Each type maps to a backend
8
+ * data structure (Rust struct, API response, etc.).
9
+ *
10
+ * IMPORTANT FOR AI AGENTS:
11
+ * When adding a new field here, the corresponding backend type must also be
12
+ * updated. Rust backends use serde rename with camelCase to match these
13
+ * TypeScript interfaces.
14
+ */
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Error entries
18
+ // ---------------------------------------------------------------------------
19
+
20
+ /** A captured error or warning entry from the error logger */
21
+ export interface CapturedError {
22
+ /** Unique hash identifying this error (djb2 of message + stack) */
23
+ fingerprint: string
24
+ /** First occurrence message text */
25
+ message: string
26
+ /** Number of times this error has been seen */
27
+ count: number
28
+ /** Severity level */
29
+ level: 'error' | 'warning'
30
+ /** ISO-8601 timestamp of first occurrence */
31
+ firstSeen: string
32
+ /** ISO-8601 timestamp of most recent occurrence */
33
+ lastSeen: string
34
+ }
35
+
36
+ /** A previously reported error that was submitted with an issue */
37
+ export interface SubmittedError {
38
+ /** Unique hash identifying this error */
39
+ fingerprint: string
40
+ /** Error message text */
41
+ message: string
42
+ /** Number of new occurrences since submission */
43
+ count: number
44
+ /** ISO-8601 timestamp of when this error was submitted */
45
+ submittedAt: string
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // API adapter interface
50
+ // ---------------------------------------------------------------------------
51
+
52
+ /**
53
+ * CapturedIssuesApi — The callback interface that each app must implement.
54
+ *
55
+ * This is the bridge between the shared UI and the app-specific backend.
56
+ * Each function maps to a backend command (Tauri command, API endpoint, etc.).
57
+ *
58
+ * ┌─────────────────────────────────────────────────────────────────────┐
59
+ * │ RUST BACKEND IMPLEMENTATION GUIDE │
60
+ * │ │
61
+ * │ Each method below corresponds to a Tauri #[tauri::command]. │
62
+ * │ The Rust reference implementation lives in: │
63
+ * │ configr/main/src-tauri/src/commands/support.rs │
64
+ * │ │
65
+ * │ REQUIRED Tauri commands: │
66
+ * │ │
67
+ * │ get_system_info() → SystemInfo │
68
+ * │ Returns { os, osVersion, appVersion, arch } │
69
+ * │ Used to include environment details in issue reports. │
70
+ * │ │
71
+ * │ submit_issue(title, description, email, errors) → SubmitResult │
72
+ * │ Sends the issue to your issue tracker (Linear, GitHub, etc.) │
73
+ * │ Returns { success: bool, issue_id: Option<String> } │
74
+ * │ │
75
+ * │ dismiss_errors() → () │
76
+ * │ Clears the current error log without submitting. │
77
+ * │ Typically calls error_logger.clear_logs(). │
78
+ * │ │
79
+ * │ get_submitted_errors() → Vec<SubmittedError> │
80
+ * │ Returns previously reported errors from local storage/DB. │
81
+ * │ Used to show the "Previously Reported" section. │
82
+ * │ │
83
+ * │ Tauri adapter example: │
84
+ * │ │
85
+ * │ import { invoke } from '@tauri-apps/api/core' │
86
+ * │ import type { CapturedIssuesApi } from '@toolr/ui-design' │
87
+ * │ │
88
+ * │ const api: CapturedIssuesApi = { │
89
+ * │ getSystemInfo: () => invoke('get_system_info'), │
90
+ * │ submitIssue: (data) => invoke('submit_issue', data), │
91
+ * │ dismissErrors: () => invoke('dismiss_errors'), │
92
+ * │ getSubmittedErrors: () => invoke('get_submitted_errors'), │
93
+ * │ } │
94
+ * └─────────────────────────────────────────────────────────────────────┘
95
+ */
96
+ export interface CapturedIssuesApi {
97
+ /** Get system environment info to include in issue reports */
98
+ getSystemInfo: () => Promise<{ os: string; osVersion: string; appVersion: string; arch: string }>
99
+
100
+ /** Submit an issue with captured errors to the issue tracker */
101
+ submitIssue: (data: {
102
+ title: string
103
+ description: string
104
+ email: string
105
+ errors: CapturedError[]
106
+ }) => Promise<{ success: boolean; issueId?: string }>
107
+
108
+ /** Dismiss/clear current errors without submitting */
109
+ dismissErrors: () => Promise<void>
110
+
111
+ /** Get list of previously submitted errors */
112
+ getSubmittedErrors: () => Promise<SubmittedError[]>
113
+ }