@antigenic-oss/paint 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 (158) hide show
  1. package/LICENSE +178 -0
  2. package/NOTICE +4 -0
  3. package/README.md +180 -0
  4. package/bin/paint.js +266 -0
  5. package/next-env.d.ts +6 -0
  6. package/next.config.ts +19 -0
  7. package/package.json +81 -0
  8. package/postcss.config.mjs +8 -0
  9. package/public/dev-editor-inspector.js +1872 -0
  10. package/src/app/api/claude/analyze/route.ts +319 -0
  11. package/src/app/api/claude/apply/route.ts +185 -0
  12. package/src/app/api/claude/pick-folder/route.ts +64 -0
  13. package/src/app/api/claude/scan/route.ts +221 -0
  14. package/src/app/api/claude/status/route.ts +55 -0
  15. package/src/app/api/project/scan/route.ts +634 -0
  16. package/src/app/api/project-scan/css-variables/route.ts +238 -0
  17. package/src/app/api/project-scan/route.ts +40 -0
  18. package/src/app/api/project-scan/tailwind-config/route.ts +172 -0
  19. package/src/app/api/proxy/[[...path]]/route.ts +2400 -0
  20. package/src/app/docs/DocsClient.tsx +322 -0
  21. package/src/app/docs/layout.tsx +7 -0
  22. package/src/app/docs/page.tsx +855 -0
  23. package/src/app/globals.css +176 -0
  24. package/src/app/layout.tsx +19 -0
  25. package/src/app/page.tsx +46 -0
  26. package/src/bridge/api-handlers.ts +885 -0
  27. package/src/bridge/proxy-handler.ts +329 -0
  28. package/src/bridge/server.ts +113 -0
  29. package/src/components/BreakpointTabs.tsx +72 -0
  30. package/src/components/ChangeSummaryModal.tsx +267 -0
  31. package/src/components/ConnectModal.tsx +994 -0
  32. package/src/components/Editor.tsx +90 -0
  33. package/src/components/PageSelector.tsx +208 -0
  34. package/src/components/PreviewFrame.tsx +299 -0
  35. package/src/components/ProjectFolderBanner.tsx +91 -0
  36. package/src/components/ResponsiveToolbar.tsx +222 -0
  37. package/src/components/TargetSelector.tsx +243 -0
  38. package/src/components/TopBar.tsx +315 -0
  39. package/src/components/common/CollapsibleSection.tsx +36 -0
  40. package/src/components/common/ColorPicker.tsx +920 -0
  41. package/src/components/common/EditablePre.tsx +136 -0
  42. package/src/components/common/ErrorBoundary.tsx +65 -0
  43. package/src/components/common/ResizablePanel.tsx +83 -0
  44. package/src/components/common/ScanAnimation.tsx +76 -0
  45. package/src/components/common/ToastContainer.tsx +97 -0
  46. package/src/components/common/UnitInput.tsx +77 -0
  47. package/src/components/common/VariableColorPicker.tsx +622 -0
  48. package/src/components/left-panel/AddElementPanel.tsx +237 -0
  49. package/src/components/left-panel/ComponentsPanel.tsx +609 -0
  50. package/src/components/left-panel/IconSidebar.tsx +99 -0
  51. package/src/components/left-panel/LayerNode.tsx +874 -0
  52. package/src/components/left-panel/LayerSearch.tsx +23 -0
  53. package/src/components/left-panel/LayersPanel.tsx +52 -0
  54. package/src/components/left-panel/LeftPanel.tsx +122 -0
  55. package/src/components/left-panel/PagesPanel.tsx +114 -0
  56. package/src/components/left-panel/icons.tsx +162 -0
  57. package/src/components/left-panel/terminal/ScanOverlay.tsx +66 -0
  58. package/src/components/left-panel/terminal/TerminalPanel.tsx +217 -0
  59. package/src/components/right-panel/ElementLogBox.tsx +248 -0
  60. package/src/components/right-panel/PanelTabs.tsx +83 -0
  61. package/src/components/right-panel/RightPanel.tsx +41 -0
  62. package/src/components/right-panel/changes/AiScanResultPanel.tsx +285 -0
  63. package/src/components/right-panel/changes/ChangeEntry.tsx +59 -0
  64. package/src/components/right-panel/changes/ChangelogActions.tsx +105 -0
  65. package/src/components/right-panel/changes/ChangesPanel.tsx +1474 -0
  66. package/src/components/right-panel/claude/ApplyConfirmModal.tsx +376 -0
  67. package/src/components/right-panel/claude/ClaudeErrorState.tsx +125 -0
  68. package/src/components/right-panel/claude/ClaudeIntegrationPanel.tsx +482 -0
  69. package/src/components/right-panel/claude/ClaudeProgressIndicator.tsx +76 -0
  70. package/src/components/right-panel/claude/DiffCard.tsx +130 -0
  71. package/src/components/right-panel/claude/DiffViewer.tsx +54 -0
  72. package/src/components/right-panel/claude/ProjectRootSelector.tsx +275 -0
  73. package/src/components/right-panel/claude/ResultsSummary.tsx +119 -0
  74. package/src/components/right-panel/claude/SetupFlow.tsx +315 -0
  75. package/src/components/right-panel/console/ConsolePanel.tsx +209 -0
  76. package/src/components/right-panel/design/AppearanceSection.tsx +703 -0
  77. package/src/components/right-panel/design/BackgroundSection.tsx +516 -0
  78. package/src/components/right-panel/design/BorderSection.tsx +161 -0
  79. package/src/components/right-panel/design/CSSRawView.tsx +412 -0
  80. package/src/components/right-panel/design/DesignCSSTabToggle.tsx +51 -0
  81. package/src/components/right-panel/design/DesignPanel.tsx +275 -0
  82. package/src/components/right-panel/design/ElementBreadcrumb.tsx +51 -0
  83. package/src/components/right-panel/design/GradientEditor.tsx +726 -0
  84. package/src/components/right-panel/design/LayoutSection.tsx +1948 -0
  85. package/src/components/right-panel/design/PositionSection.tsx +865 -0
  86. package/src/components/right-panel/design/PropertiesSection.tsx +86 -0
  87. package/src/components/right-panel/design/SVGSection.tsx +361 -0
  88. package/src/components/right-panel/design/ShadowBlurSection.tsx +227 -0
  89. package/src/components/right-panel/design/SizeSection.tsx +183 -0
  90. package/src/components/right-panel/design/TextSection.tsx +719 -0
  91. package/src/components/right-panel/design/icons.tsx +948 -0
  92. package/src/components/right-panel/design/inputs/BoxModelPreview.tsx +467 -0
  93. package/src/components/right-panel/design/inputs/ColorInput.tsx +43 -0
  94. package/src/components/right-panel/design/inputs/CompactInput.tsx +333 -0
  95. package/src/components/right-panel/design/inputs/DraggableLabel.tsx +118 -0
  96. package/src/components/right-panel/design/inputs/IconToggleGroup.tsx +54 -0
  97. package/src/components/right-panel/design/inputs/LinkedInputPair.tsx +174 -0
  98. package/src/components/right-panel/design/inputs/SectionHeader.tsx +79 -0
  99. package/src/components/right-panel/variables/VariablesPanel.tsx +388 -0
  100. package/src/hooks/useBridge.ts +95 -0
  101. package/src/hooks/useChangeTracker.ts +563 -0
  102. package/src/hooks/useClaudeAPI.ts +118 -0
  103. package/src/hooks/useDOMTree.ts +25 -0
  104. package/src/hooks/useKeyboardShortcuts.ts +76 -0
  105. package/src/hooks/usePostMessage.ts +589 -0
  106. package/src/hooks/useProjectScan.ts +204 -0
  107. package/src/hooks/useResizable.ts +20 -0
  108. package/src/hooks/useSelectedElement.ts +51 -0
  109. package/src/hooks/useTargetUrl.ts +81 -0
  110. package/src/inspector/DOMTraverser.ts +71 -0
  111. package/src/inspector/ElementSelector.ts +23 -0
  112. package/src/inspector/HoverHighlighter.ts +54 -0
  113. package/src/inspector/SelectionHighlighter.ts +27 -0
  114. package/src/inspector/StyleExtractor.ts +19 -0
  115. package/src/inspector/inspector.ts +17 -0
  116. package/src/inspector/messaging.ts +30 -0
  117. package/src/lib/apiBase.ts +15 -0
  118. package/src/lib/classifyElement.ts +430 -0
  119. package/src/lib/claude-bin.ts +197 -0
  120. package/src/lib/claude-stream.ts +158 -0
  121. package/src/lib/clientProjectScanner.ts +344 -0
  122. package/src/lib/componentMatcher.ts +156 -0
  123. package/src/lib/constants.ts +573 -0
  124. package/src/lib/cssVariableUtils.ts +409 -0
  125. package/src/lib/diffParser.ts +206 -0
  126. package/src/lib/folderPicker.ts +84 -0
  127. package/src/lib/gradientParser.ts +160 -0
  128. package/src/lib/projectScanner.ts +355 -0
  129. package/src/lib/promptBuilder.ts +402 -0
  130. package/src/lib/shadowParser.ts +124 -0
  131. package/src/lib/tailwindClassParser.ts +248 -0
  132. package/src/lib/textShadowUtils.ts +106 -0
  133. package/src/lib/utils.ts +299 -0
  134. package/src/lib/validatePath.ts +40 -0
  135. package/src/proxy.ts +92 -0
  136. package/src/server/terminal-server.ts +104 -0
  137. package/src/store/changeSlice.ts +288 -0
  138. package/src/store/claudeSlice.ts +222 -0
  139. package/src/store/componentSlice.ts +90 -0
  140. package/src/store/consoleSlice.ts +51 -0
  141. package/src/store/cssVariableSlice.ts +94 -0
  142. package/src/store/elementSlice.ts +78 -0
  143. package/src/store/index.ts +35 -0
  144. package/src/store/terminalSlice.ts +30 -0
  145. package/src/store/treeSlice.ts +69 -0
  146. package/src/store/uiSlice.ts +327 -0
  147. package/src/types/changelog.ts +49 -0
  148. package/src/types/claude.ts +131 -0
  149. package/src/types/component.ts +49 -0
  150. package/src/types/cssVariables.ts +18 -0
  151. package/src/types/element.ts +21 -0
  152. package/src/types/file-system-access.d.ts +27 -0
  153. package/src/types/gradient.ts +12 -0
  154. package/src/types/messages.ts +392 -0
  155. package/src/types/shadow.ts +8 -0
  156. package/src/types/tree.ts +9 -0
  157. package/tsconfig.json +42 -0
  158. package/tsconfig.server.json +12 -0
@@ -0,0 +1,376 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback } from 'react'
4
+ import { useEditorStore } from '@/store'
5
+ import { BREAKPOINT_LABELS } from '@/types/changelog'
6
+ import type { Breakpoint } from '@/types/changelog'
7
+ import { ProjectRootSelector } from './ProjectRootSelector'
8
+
9
+ interface ApplyConfirmModalProps {
10
+ onConfirm: () => void
11
+ onCancel: () => void
12
+ }
13
+
14
+ export function ApplyConfirmModal({
15
+ onConfirm,
16
+ onCancel,
17
+ }: ApplyConfirmModalProps) {
18
+ const targetUrl = useEditorStore((s) => s.targetUrl)
19
+ const portRoots = useEditorStore((s) => s.portRoots)
20
+ const projectRoot = targetUrl ? (portRoots[targetUrl] ?? null) : null
21
+ const styleChanges = useEditorStore((s) => s.styleChanges)
22
+ const parsedDiffs = useEditorStore((s) => s.parsedDiffs)
23
+ const elementSnapshots = useEditorStore((s) => s.elementSnapshots)
24
+
25
+ const [folderConfirmed, setFolderConfirmed] = useState(false)
26
+ const [changingFolder, setChangingFolder] = useState(false)
27
+
28
+ const handleFolderSaved = useCallback(() => {
29
+ setChangingFolder(false)
30
+ setFolderConfirmed(false)
31
+ }, [])
32
+
33
+ // Derive unique pages from element snapshots
34
+ const pages = Array.from(
35
+ new Set(
36
+ Object.values(elementSnapshots)
37
+ .map((snap) => snap.pagePath)
38
+ .filter(Boolean),
39
+ ),
40
+ )
41
+
42
+ // Derive unique breakpoints from style changes
43
+ const breakpoints = Array.from(
44
+ new Set(styleChanges.map((c) => c.breakpoint)),
45
+ ) as Breakpoint[]
46
+
47
+ // Files that will be modified
48
+ const files = parsedDiffs.map((d) => d.filePath)
49
+
50
+ return (
51
+ <div
52
+ className="fixed inset-0 z-[9999] flex items-center justify-center"
53
+ style={{ background: 'rgba(0, 0, 0, 0.6)' }}
54
+ onClick={onCancel}
55
+ >
56
+ <div
57
+ className="w-[380px] max-h-[80vh] overflow-y-auto rounded-lg shadow-2xl"
58
+ style={{
59
+ background: 'var(--bg-primary)',
60
+ border: '1px solid var(--border)',
61
+ }}
62
+ onClick={(e) => e.stopPropagation()}
63
+ >
64
+ {/* Header */}
65
+ <div
66
+ className="flex items-center gap-2 px-4 py-3"
67
+ style={{ borderBottom: '1px solid var(--border)' }}
68
+ >
69
+ <svg
70
+ width="16"
71
+ height="16"
72
+ viewBox="0 0 24 24"
73
+ fill="none"
74
+ stroke="var(--warning)"
75
+ strokeWidth="2"
76
+ strokeLinecap="round"
77
+ strokeLinejoin="round"
78
+ >
79
+ <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
80
+ <line x1="12" y1="9" x2="12" y2="13" />
81
+ <line x1="12" y1="17" x2="12.01" y2="17" />
82
+ </svg>
83
+ <span
84
+ className="text-sm font-semibold"
85
+ style={{ color: 'var(--text-primary)' }}
86
+ >
87
+ Confirm Apply All
88
+ </span>
89
+ </div>
90
+
91
+ {/* Body */}
92
+ <div className="flex flex-col gap-3 px-4 py-3">
93
+ {/* Project folder — must be confirmed */}
94
+ <div
95
+ className="flex flex-col gap-2 px-3 py-2.5 rounded"
96
+ style={{
97
+ background: folderConfirmed
98
+ ? 'rgba(78, 201, 176, 0.06)'
99
+ : 'rgba(251, 191, 36, 0.06)',
100
+ border: `1px solid ${folderConfirmed ? 'var(--success)' : 'var(--warning)'}`,
101
+ }}
102
+ >
103
+ <div className="flex items-center justify-between">
104
+ <span
105
+ className="text-[11px] font-medium uppercase tracking-wide"
106
+ style={{
107
+ color: folderConfirmed ? 'var(--success)' : 'var(--warning)',
108
+ }}
109
+ >
110
+ Target Project Folder
111
+ </span>
112
+ {projectRoot && !changingFolder && (
113
+ <button
114
+ onClick={() => {
115
+ setChangingFolder(true)
116
+ setFolderConfirmed(false)
117
+ }}
118
+ className="text-[10px] px-1.5 py-0.5 rounded transition-colors hover:bg-[var(--bg-hover)]"
119
+ style={{ color: 'var(--accent)' }}
120
+ >
121
+ Change
122
+ </button>
123
+ )}
124
+ </div>
125
+
126
+ {changingFolder && targetUrl ? (
127
+ <ProjectRootSelector
128
+ targetUrl={targetUrl}
129
+ onSaved={handleFolderSaved}
130
+ />
131
+ ) : projectRoot ? (
132
+ <>
133
+ <div
134
+ className="px-2.5 py-1.5 rounded text-xs font-mono truncate"
135
+ style={{
136
+ background: 'var(--bg-tertiary)',
137
+ color: 'var(--text-primary)',
138
+ }}
139
+ title={projectRoot}
140
+ >
141
+ {projectRoot}
142
+ </div>
143
+ <label className="flex items-start gap-2 cursor-pointer select-none">
144
+ <input
145
+ type="checkbox"
146
+ checked={folderConfirmed}
147
+ onChange={(e) => setFolderConfirmed(e.target.checked)}
148
+ className="mt-0.5 accent-[var(--accent)]"
149
+ />
150
+ <span
151
+ className="text-[11px] leading-relaxed"
152
+ style={{ color: 'var(--text-secondary)' }}
153
+ >
154
+ I confirm this is the correct project folder for{' '}
155
+ <span
156
+ className="font-mono"
157
+ style={{ color: 'var(--text-primary)' }}
158
+ >
159
+ {(() => {
160
+ try {
161
+ return new URL(targetUrl!).host
162
+ } catch {
163
+ return targetUrl
164
+ }
165
+ })()}
166
+ </span>
167
+ </span>
168
+ </label>
169
+ </>
170
+ ) : (
171
+ <div className="flex flex-col gap-2">
172
+ <span className="text-[11px]" style={{ color: 'var(--error)' }}>
173
+ No project folder set. Select one before applying.
174
+ </span>
175
+ {targetUrl && (
176
+ <ProjectRootSelector
177
+ targetUrl={targetUrl}
178
+ onSaved={handleFolderSaved}
179
+ />
180
+ )}
181
+ </div>
182
+ )}
183
+ </div>
184
+
185
+ {/* Pages affected */}
186
+ <div className="flex flex-col gap-1">
187
+ <span
188
+ className="text-[11px] font-medium uppercase tracking-wide"
189
+ style={{ color: 'var(--text-muted)' }}
190
+ >
191
+ Pages Affected
192
+ </span>
193
+ <div className="flex flex-col gap-1">
194
+ {pages.length > 0 ? (
195
+ pages.map((page) => (
196
+ <div
197
+ key={page}
198
+ className="flex items-center gap-2 px-2.5 py-1.5 rounded text-xs font-mono"
199
+ style={{
200
+ background: 'var(--bg-tertiary)',
201
+ color: 'var(--text-primary)',
202
+ }}
203
+ >
204
+ <span style={{ color: 'var(--accent)', fontSize: 10 }}>
205
+ &#9679;
206
+ </span>
207
+ {page}
208
+ </div>
209
+ ))
210
+ ) : (
211
+ <div
212
+ className="px-2.5 py-1.5 rounded text-xs"
213
+ style={{
214
+ background: 'var(--bg-tertiary)',
215
+ color: 'var(--text-secondary)',
216
+ }}
217
+ >
218
+ Current page
219
+ </div>
220
+ )}
221
+ </div>
222
+ </div>
223
+
224
+ {/* Items count */}
225
+ <div className="flex flex-col gap-1">
226
+ <span
227
+ className="text-[11px] font-medium uppercase tracking-wide"
228
+ style={{ color: 'var(--text-muted)' }}
229
+ >
230
+ Changes to Apply
231
+ </span>
232
+ <div className="flex items-center gap-3">
233
+ <div
234
+ className="flex items-center gap-2 px-2.5 py-1.5 rounded text-xs"
235
+ style={{ background: 'var(--bg-tertiary)' }}
236
+ >
237
+ <span
238
+ style={{ color: 'var(--accent)' }}
239
+ className="font-semibold"
240
+ >
241
+ {styleChanges.length}
242
+ </span>
243
+ <span style={{ color: 'var(--text-secondary)' }}>
244
+ style change{styleChanges.length !== 1 ? 's' : ''}
245
+ </span>
246
+ </div>
247
+ <div
248
+ className="flex items-center gap-2 px-2.5 py-1.5 rounded text-xs"
249
+ style={{ background: 'var(--bg-tertiary)' }}
250
+ >
251
+ <span
252
+ style={{ color: 'var(--accent)' }}
253
+ className="font-semibold"
254
+ >
255
+ {files.length}
256
+ </span>
257
+ <span style={{ color: 'var(--text-secondary)' }}>
258
+ file{files.length !== 1 ? 's' : ''}
259
+ </span>
260
+ </div>
261
+ </div>
262
+ </div>
263
+
264
+ {/* Devices */}
265
+ <div className="flex flex-col gap-1">
266
+ <span
267
+ className="text-[11px] font-medium uppercase tracking-wide"
268
+ style={{ color: 'var(--text-muted)' }}
269
+ >
270
+ Devices
271
+ </span>
272
+ <div className="flex items-center gap-2">
273
+ {breakpoints.map((bp) => (
274
+ <div
275
+ key={bp}
276
+ className="flex items-center gap-1.5 px-2.5 py-1.5 rounded text-xs"
277
+ style={{
278
+ background: 'var(--bg-tertiary)',
279
+ color: 'var(--text-primary)',
280
+ }}
281
+ >
282
+ {bp === 'mobile' && (
283
+ <svg
284
+ width="12"
285
+ height="12"
286
+ viewBox="0 0 24 24"
287
+ fill="none"
288
+ stroke="currentColor"
289
+ strokeWidth="2"
290
+ strokeLinecap="round"
291
+ strokeLinejoin="round"
292
+ >
293
+ <rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
294
+ <line x1="12" y1="18" x2="12.01" y2="18" />
295
+ </svg>
296
+ )}
297
+ {bp === 'tablet' && (
298
+ <svg
299
+ width="12"
300
+ height="12"
301
+ viewBox="0 0 24 24"
302
+ fill="none"
303
+ stroke="currentColor"
304
+ strokeWidth="2"
305
+ strokeLinecap="round"
306
+ strokeLinejoin="round"
307
+ >
308
+ <rect x="4" y="2" width="16" height="20" rx="2" ry="2" />
309
+ <line x1="12" y1="18" x2="12.01" y2="18" />
310
+ </svg>
311
+ )}
312
+ {bp === 'desktop' && (
313
+ <svg
314
+ width="12"
315
+ height="12"
316
+ viewBox="0 0 24 24"
317
+ fill="none"
318
+ stroke="currentColor"
319
+ strokeWidth="2"
320
+ strokeLinecap="round"
321
+ strokeLinejoin="round"
322
+ >
323
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
324
+ <line x1="8" y1="21" x2="16" y2="21" />
325
+ <line x1="12" y1="17" x2="12" y2="21" />
326
+ </svg>
327
+ )}
328
+ {BREAKPOINT_LABELS[bp]}
329
+ </div>
330
+ ))}
331
+ </div>
332
+ </div>
333
+
334
+ {/* Tip */}
335
+ <div
336
+ className="flex gap-2 px-3 py-2.5 rounded text-[11px] leading-relaxed"
337
+ style={{
338
+ background: 'rgba(251, 191, 36, 0.08)',
339
+ border: '1px solid rgba(251, 191, 36, 0.2)',
340
+ color: 'var(--warning)',
341
+ }}
342
+ >
343
+ <span className="flex-shrink-0 mt-0.5">Tip:</span>
344
+ <span>
345
+ For best results, apply changes one at a time and test each before
346
+ proceeding. This makes it easier to catch issues and revert if
347
+ needed.
348
+ </span>
349
+ </div>
350
+ </div>
351
+
352
+ {/* Footer buttons */}
353
+ <div
354
+ className="flex items-center justify-end gap-2 px-4 py-3"
355
+ style={{ borderTop: '1px solid var(--border)' }}
356
+ >
357
+ <button
358
+ onClick={onCancel}
359
+ className="px-3 py-1.5 rounded text-xs font-medium transition-colors hover:bg-[var(--bg-hover)]"
360
+ style={{ color: 'var(--text-secondary)' }}
361
+ >
362
+ Cancel
363
+ </button>
364
+ <button
365
+ onClick={onConfirm}
366
+ disabled={!folderConfirmed || !projectRoot}
367
+ className="px-4 py-1.5 rounded text-xs font-medium transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
368
+ style={{ background: 'var(--accent)', color: '#fff' }}
369
+ >
370
+ Apply All Changes
371
+ </button>
372
+ </div>
373
+ </div>
374
+ </div>
375
+ )
376
+ }
@@ -0,0 +1,125 @@
1
+ 'use client'
2
+
3
+ import type { ClaudeError, ClaudeErrorCode } from '@/types/claude'
4
+
5
+ interface ClaudeErrorStateProps {
6
+ error: ClaudeError
7
+ onRetry: () => void
8
+ }
9
+
10
+ const ERROR_DETAILS: Record<
11
+ ClaudeErrorCode,
12
+ { title: string; description: string; command?: string }
13
+ > = {
14
+ CLI_NOT_FOUND: {
15
+ title: 'Claude Code CLI Not Found',
16
+ description:
17
+ 'The Claude Code CLI is not installed or not in your PATH. Install it to continue.',
18
+ command: 'npm install -g @anthropic-ai/claude-code',
19
+ },
20
+ AUTH_REQUIRED: {
21
+ title: 'Authentication Required',
22
+ description:
23
+ 'Claude Code CLI needs to be authenticated. Run the command below in your terminal to log in.',
24
+ command: 'claude',
25
+ },
26
+ TIMEOUT: {
27
+ title: 'Analysis Timed Out',
28
+ description:
29
+ 'The analysis took too long to complete. Try with fewer changes or a smaller scope.',
30
+ },
31
+ PARSE_FAILURE: {
32
+ title: 'Parse Failure',
33
+ description:
34
+ 'Could not parse the results returned by Claude Code. The output format may have changed.',
35
+ },
36
+ UNKNOWN: {
37
+ title: 'Unexpected Error',
38
+ description:
39
+ 'An unexpected error occurred while communicating with Claude Code.',
40
+ },
41
+ }
42
+
43
+ export function ClaudeErrorState({ error, onRetry }: ClaudeErrorStateProps) {
44
+ const details = ERROR_DETAILS[error.code] || ERROR_DETAILS.UNKNOWN
45
+
46
+ return (
47
+ <div className="flex flex-col gap-3 p-4">
48
+ {/* Error icon and title */}
49
+ <div className="flex items-start gap-2">
50
+ <div
51
+ className="flex items-center justify-center w-6 h-6 rounded-full flex-shrink-0 text-sm"
52
+ style={{
53
+ background: 'rgba(244, 71, 71, 0.15)',
54
+ color: 'var(--error)',
55
+ }}
56
+ >
57
+ !
58
+ </div>
59
+ <div className="flex flex-col gap-1">
60
+ <div
61
+ className="text-xs font-medium"
62
+ style={{ color: 'var(--error)' }}
63
+ >
64
+ {details.title}
65
+ </div>
66
+ <p
67
+ className="text-[11px] leading-relaxed"
68
+ style={{ color: 'var(--text-secondary)' }}
69
+ >
70
+ {details.description}
71
+ </p>
72
+ </div>
73
+ </div>
74
+
75
+ {/* Raw error message */}
76
+ {error.message && (
77
+ <div
78
+ className="px-3 py-2 rounded text-[11px] font-mono break-words"
79
+ style={{
80
+ background: 'var(--bg-tertiary)',
81
+ color: 'var(--text-muted)',
82
+ border: '1px solid var(--border)',
83
+ }}
84
+ >
85
+ {error.message}
86
+ </div>
87
+ )}
88
+
89
+ {/* Command suggestion */}
90
+ {details.command && (
91
+ <div className="flex flex-col gap-1.5">
92
+ <span
93
+ className="text-[11px]"
94
+ style={{ color: 'var(--text-secondary)' }}
95
+ >
96
+ Run this in your terminal:
97
+ </span>
98
+ <code
99
+ className="block px-3 py-2 rounded text-[11px] font-mono select-all"
100
+ style={{
101
+ background: 'var(--bg-tertiary)',
102
+ color: 'var(--text-primary)',
103
+ border: '1px solid var(--border)',
104
+ }}
105
+ >
106
+ {details.command}
107
+ </code>
108
+ </div>
109
+ )}
110
+
111
+ {/* Retry button */}
112
+ <button
113
+ onClick={onRetry}
114
+ className="w-full py-1.5 px-3 rounded text-xs font-medium transition-colors"
115
+ style={{
116
+ background: 'var(--bg-hover)',
117
+ color: 'var(--text-primary)',
118
+ border: '1px solid var(--border)',
119
+ }}
120
+ >
121
+ Retry
122
+ </button>
123
+ </div>
124
+ )
125
+ }