@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.
- package/LICENSE +178 -0
- package/NOTICE +4 -0
- package/README.md +180 -0
- package/bin/paint.js +266 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +19 -0
- package/package.json +81 -0
- package/postcss.config.mjs +8 -0
- package/public/dev-editor-inspector.js +1872 -0
- package/src/app/api/claude/analyze/route.ts +319 -0
- package/src/app/api/claude/apply/route.ts +185 -0
- package/src/app/api/claude/pick-folder/route.ts +64 -0
- package/src/app/api/claude/scan/route.ts +221 -0
- package/src/app/api/claude/status/route.ts +55 -0
- package/src/app/api/project/scan/route.ts +634 -0
- package/src/app/api/project-scan/css-variables/route.ts +238 -0
- package/src/app/api/project-scan/route.ts +40 -0
- package/src/app/api/project-scan/tailwind-config/route.ts +172 -0
- package/src/app/api/proxy/[[...path]]/route.ts +2400 -0
- package/src/app/docs/DocsClient.tsx +322 -0
- package/src/app/docs/layout.tsx +7 -0
- package/src/app/docs/page.tsx +855 -0
- package/src/app/globals.css +176 -0
- package/src/app/layout.tsx +19 -0
- package/src/app/page.tsx +46 -0
- package/src/bridge/api-handlers.ts +885 -0
- package/src/bridge/proxy-handler.ts +329 -0
- package/src/bridge/server.ts +113 -0
- package/src/components/BreakpointTabs.tsx +72 -0
- package/src/components/ChangeSummaryModal.tsx +267 -0
- package/src/components/ConnectModal.tsx +994 -0
- package/src/components/Editor.tsx +90 -0
- package/src/components/PageSelector.tsx +208 -0
- package/src/components/PreviewFrame.tsx +299 -0
- package/src/components/ProjectFolderBanner.tsx +91 -0
- package/src/components/ResponsiveToolbar.tsx +222 -0
- package/src/components/TargetSelector.tsx +243 -0
- package/src/components/TopBar.tsx +315 -0
- package/src/components/common/CollapsibleSection.tsx +36 -0
- package/src/components/common/ColorPicker.tsx +920 -0
- package/src/components/common/EditablePre.tsx +136 -0
- package/src/components/common/ErrorBoundary.tsx +65 -0
- package/src/components/common/ResizablePanel.tsx +83 -0
- package/src/components/common/ScanAnimation.tsx +76 -0
- package/src/components/common/ToastContainer.tsx +97 -0
- package/src/components/common/UnitInput.tsx +77 -0
- package/src/components/common/VariableColorPicker.tsx +622 -0
- package/src/components/left-panel/AddElementPanel.tsx +237 -0
- package/src/components/left-panel/ComponentsPanel.tsx +609 -0
- package/src/components/left-panel/IconSidebar.tsx +99 -0
- package/src/components/left-panel/LayerNode.tsx +874 -0
- package/src/components/left-panel/LayerSearch.tsx +23 -0
- package/src/components/left-panel/LayersPanel.tsx +52 -0
- package/src/components/left-panel/LeftPanel.tsx +122 -0
- package/src/components/left-panel/PagesPanel.tsx +114 -0
- package/src/components/left-panel/icons.tsx +162 -0
- package/src/components/left-panel/terminal/ScanOverlay.tsx +66 -0
- package/src/components/left-panel/terminal/TerminalPanel.tsx +217 -0
- package/src/components/right-panel/ElementLogBox.tsx +248 -0
- package/src/components/right-panel/PanelTabs.tsx +83 -0
- package/src/components/right-panel/RightPanel.tsx +41 -0
- package/src/components/right-panel/changes/AiScanResultPanel.tsx +285 -0
- package/src/components/right-panel/changes/ChangeEntry.tsx +59 -0
- package/src/components/right-panel/changes/ChangelogActions.tsx +105 -0
- package/src/components/right-panel/changes/ChangesPanel.tsx +1474 -0
- package/src/components/right-panel/claude/ApplyConfirmModal.tsx +376 -0
- package/src/components/right-panel/claude/ClaudeErrorState.tsx +125 -0
- package/src/components/right-panel/claude/ClaudeIntegrationPanel.tsx +482 -0
- package/src/components/right-panel/claude/ClaudeProgressIndicator.tsx +76 -0
- package/src/components/right-panel/claude/DiffCard.tsx +130 -0
- package/src/components/right-panel/claude/DiffViewer.tsx +54 -0
- package/src/components/right-panel/claude/ProjectRootSelector.tsx +275 -0
- package/src/components/right-panel/claude/ResultsSummary.tsx +119 -0
- package/src/components/right-panel/claude/SetupFlow.tsx +315 -0
- package/src/components/right-panel/console/ConsolePanel.tsx +209 -0
- package/src/components/right-panel/design/AppearanceSection.tsx +703 -0
- package/src/components/right-panel/design/BackgroundSection.tsx +516 -0
- package/src/components/right-panel/design/BorderSection.tsx +161 -0
- package/src/components/right-panel/design/CSSRawView.tsx +412 -0
- package/src/components/right-panel/design/DesignCSSTabToggle.tsx +51 -0
- package/src/components/right-panel/design/DesignPanel.tsx +275 -0
- package/src/components/right-panel/design/ElementBreadcrumb.tsx +51 -0
- package/src/components/right-panel/design/GradientEditor.tsx +726 -0
- package/src/components/right-panel/design/LayoutSection.tsx +1948 -0
- package/src/components/right-panel/design/PositionSection.tsx +865 -0
- package/src/components/right-panel/design/PropertiesSection.tsx +86 -0
- package/src/components/right-panel/design/SVGSection.tsx +361 -0
- package/src/components/right-panel/design/ShadowBlurSection.tsx +227 -0
- package/src/components/right-panel/design/SizeSection.tsx +183 -0
- package/src/components/right-panel/design/TextSection.tsx +719 -0
- package/src/components/right-panel/design/icons.tsx +948 -0
- package/src/components/right-panel/design/inputs/BoxModelPreview.tsx +467 -0
- package/src/components/right-panel/design/inputs/ColorInput.tsx +43 -0
- package/src/components/right-panel/design/inputs/CompactInput.tsx +333 -0
- package/src/components/right-panel/design/inputs/DraggableLabel.tsx +118 -0
- package/src/components/right-panel/design/inputs/IconToggleGroup.tsx +54 -0
- package/src/components/right-panel/design/inputs/LinkedInputPair.tsx +174 -0
- package/src/components/right-panel/design/inputs/SectionHeader.tsx +79 -0
- package/src/components/right-panel/variables/VariablesPanel.tsx +388 -0
- package/src/hooks/useBridge.ts +95 -0
- package/src/hooks/useChangeTracker.ts +563 -0
- package/src/hooks/useClaudeAPI.ts +118 -0
- package/src/hooks/useDOMTree.ts +25 -0
- package/src/hooks/useKeyboardShortcuts.ts +76 -0
- package/src/hooks/usePostMessage.ts +589 -0
- package/src/hooks/useProjectScan.ts +204 -0
- package/src/hooks/useResizable.ts +20 -0
- package/src/hooks/useSelectedElement.ts +51 -0
- package/src/hooks/useTargetUrl.ts +81 -0
- package/src/inspector/DOMTraverser.ts +71 -0
- package/src/inspector/ElementSelector.ts +23 -0
- package/src/inspector/HoverHighlighter.ts +54 -0
- package/src/inspector/SelectionHighlighter.ts +27 -0
- package/src/inspector/StyleExtractor.ts +19 -0
- package/src/inspector/inspector.ts +17 -0
- package/src/inspector/messaging.ts +30 -0
- package/src/lib/apiBase.ts +15 -0
- package/src/lib/classifyElement.ts +430 -0
- package/src/lib/claude-bin.ts +197 -0
- package/src/lib/claude-stream.ts +158 -0
- package/src/lib/clientProjectScanner.ts +344 -0
- package/src/lib/componentMatcher.ts +156 -0
- package/src/lib/constants.ts +573 -0
- package/src/lib/cssVariableUtils.ts +409 -0
- package/src/lib/diffParser.ts +206 -0
- package/src/lib/folderPicker.ts +84 -0
- package/src/lib/gradientParser.ts +160 -0
- package/src/lib/projectScanner.ts +355 -0
- package/src/lib/promptBuilder.ts +402 -0
- package/src/lib/shadowParser.ts +124 -0
- package/src/lib/tailwindClassParser.ts +248 -0
- package/src/lib/textShadowUtils.ts +106 -0
- package/src/lib/utils.ts +299 -0
- package/src/lib/validatePath.ts +40 -0
- package/src/proxy.ts +92 -0
- package/src/server/terminal-server.ts +104 -0
- package/src/store/changeSlice.ts +288 -0
- package/src/store/claudeSlice.ts +222 -0
- package/src/store/componentSlice.ts +90 -0
- package/src/store/consoleSlice.ts +51 -0
- package/src/store/cssVariableSlice.ts +94 -0
- package/src/store/elementSlice.ts +78 -0
- package/src/store/index.ts +35 -0
- package/src/store/terminalSlice.ts +30 -0
- package/src/store/treeSlice.ts +69 -0
- package/src/store/uiSlice.ts +327 -0
- package/src/types/changelog.ts +49 -0
- package/src/types/claude.ts +131 -0
- package/src/types/component.ts +49 -0
- package/src/types/cssVariables.ts +18 -0
- package/src/types/element.ts +21 -0
- package/src/types/file-system-access.d.ts +27 -0
- package/src/types/gradient.ts +12 -0
- package/src/types/messages.ts +392 -0
- package/src/types/shadow.ts +8 -0
- package/src/types/tree.ts +9 -0
- package/tsconfig.json +42 -0
- 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
|
+
●
|
|
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
|
+
}
|