@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,315 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback, useEffect } from 'react'
|
|
4
|
+
import { useEditorStore } from '@/store'
|
|
5
|
+
import { ProjectRootSelector } from './ProjectRootSelector'
|
|
6
|
+
import { isEditorOnLocalhost } from '@/hooks/usePostMessage'
|
|
7
|
+
import { getApiBase } from '@/lib/apiBase'
|
|
8
|
+
import type { ClaudeStatusResponse } from '@/types/claude'
|
|
9
|
+
|
|
10
|
+
interface SetupFlowProps {
|
|
11
|
+
targetUrl: string
|
|
12
|
+
onComplete: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function SetupFlow({ targetUrl, onComplete }: SetupFlowProps) {
|
|
16
|
+
const cliAvailable = useEditorStore((s) => s.cliAvailable)
|
|
17
|
+
const setCliAvailable = useEditorStore((s) => s.setCliAvailable)
|
|
18
|
+
const portRoots = useEditorStore((s) => s.portRoots)
|
|
19
|
+
const projectRoot = portRoots[targetUrl] ?? null
|
|
20
|
+
|
|
21
|
+
const bridgeStatus = useEditorStore((s) => s.bridgeStatus)
|
|
22
|
+
const isLocal = typeof window !== 'undefined' && isEditorOnLocalhost()
|
|
23
|
+
const hasBridge = bridgeStatus === 'connected'
|
|
24
|
+
const hasServerAccess = isLocal || hasBridge
|
|
25
|
+
|
|
26
|
+
const [checking, setChecking] = useState(false)
|
|
27
|
+
const [cliVersion, setCliVersion] = useState<string | null>(null)
|
|
28
|
+
const [checkError, setCheckError] = useState<string | null>(null)
|
|
29
|
+
|
|
30
|
+
const checkCli = useCallback(async () => {
|
|
31
|
+
setChecking(true)
|
|
32
|
+
setCheckError(null)
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch(`${getApiBase()}/api/claude/status`)
|
|
35
|
+
const data: ClaudeStatusResponse = await res.json()
|
|
36
|
+
setCliAvailable(data.available)
|
|
37
|
+
if (data.available && data.version) {
|
|
38
|
+
setCliVersion(data.version)
|
|
39
|
+
} else {
|
|
40
|
+
setCheckError(data.error || 'Claude CLI not available')
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
setCliAvailable(false)
|
|
44
|
+
setCheckError('Failed to check CLI status')
|
|
45
|
+
} finally {
|
|
46
|
+
setChecking(false)
|
|
47
|
+
}
|
|
48
|
+
}, [setCliAvailable])
|
|
49
|
+
|
|
50
|
+
// Auto-check CLI on mount (when running locally or bridge is connected)
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (hasServerAccess && cliAvailable === null) {
|
|
53
|
+
checkCli()
|
|
54
|
+
}
|
|
55
|
+
}, [hasServerAccess, cliAvailable, checkCli])
|
|
56
|
+
|
|
57
|
+
const handleProjectRootSaved = useCallback(() => {
|
|
58
|
+
onComplete()
|
|
59
|
+
}, [onComplete])
|
|
60
|
+
|
|
61
|
+
// ─── Remote mode without bridge (Vercel) ───
|
|
62
|
+
// Skip CLI check, show project root selector with File System Access API
|
|
63
|
+
if (!hasServerAccess) {
|
|
64
|
+
return (
|
|
65
|
+
<div className="flex flex-col gap-4 p-4">
|
|
66
|
+
<div
|
|
67
|
+
className="text-xs font-medium"
|
|
68
|
+
style={{ color: 'var(--text-primary)' }}
|
|
69
|
+
>
|
|
70
|
+
Claude Code
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{/* Remote mode notice */}
|
|
74
|
+
<div
|
|
75
|
+
className="flex flex-col gap-1.5 px-3 py-2.5 rounded"
|
|
76
|
+
style={{
|
|
77
|
+
background: 'var(--bg-tertiary)',
|
|
78
|
+
border: '1px solid var(--border)',
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<div
|
|
82
|
+
className="text-[11px] font-medium"
|
|
83
|
+
style={{ color: 'var(--text-secondary)' }}
|
|
84
|
+
>
|
|
85
|
+
Running remotely
|
|
86
|
+
</div>
|
|
87
|
+
<p
|
|
88
|
+
className="text-[10px] leading-relaxed"
|
|
89
|
+
style={{ color: 'var(--text-muted)' }}
|
|
90
|
+
>
|
|
91
|
+
Start the bridge server locally for full CLI access:{' '}
|
|
92
|
+
<code
|
|
93
|
+
className="font-mono px-1 rounded"
|
|
94
|
+
style={{ background: 'var(--bg-primary)' }}
|
|
95
|
+
>
|
|
96
|
+
bun run bridge
|
|
97
|
+
</code>
|
|
98
|
+
</p>
|
|
99
|
+
<p
|
|
100
|
+
className="text-[10px] leading-relaxed"
|
|
101
|
+
style={{ color: 'var(--text-muted)' }}
|
|
102
|
+
>
|
|
103
|
+
Or select a project folder below to improve changelog accuracy.
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{/* Project root setup (uses File System Access API on remote) */}
|
|
108
|
+
<div className="flex flex-col gap-2">
|
|
109
|
+
<div
|
|
110
|
+
className="text-[11px] font-medium"
|
|
111
|
+
style={{ color: 'var(--text-primary)' }}
|
|
112
|
+
>
|
|
113
|
+
Project folder{' '}
|
|
114
|
+
<span style={{ color: 'var(--text-muted)', fontWeight: 'normal' }}>
|
|
115
|
+
(optional)
|
|
116
|
+
</span>
|
|
117
|
+
</div>
|
|
118
|
+
<p className="text-[10px]" style={{ color: 'var(--text-muted)' }}>
|
|
119
|
+
Select your project folder to detect framework, components, and CSS
|
|
120
|
+
strategy for smarter changelog export.
|
|
121
|
+
</p>
|
|
122
|
+
<ProjectRootSelector
|
|
123
|
+
targetUrl={targetUrl}
|
|
124
|
+
onSaved={handleProjectRootSaved}
|
|
125
|
+
/>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ─── Local mode ───
|
|
132
|
+
|
|
133
|
+
// Still checking CLI on first load
|
|
134
|
+
if (checking && cliAvailable === null) {
|
|
135
|
+
return (
|
|
136
|
+
<div className="flex flex-col items-center justify-center py-12 gap-3">
|
|
137
|
+
<div
|
|
138
|
+
className="w-5 h-5 border-2 rounded-full animate-spin"
|
|
139
|
+
style={{
|
|
140
|
+
borderColor: 'var(--border)',
|
|
141
|
+
borderTopColor: 'var(--accent)',
|
|
142
|
+
}}
|
|
143
|
+
/>
|
|
144
|
+
<span className="text-xs" style={{ color: 'var(--text-muted)' }}>
|
|
145
|
+
Detecting Claude Code CLI...
|
|
146
|
+
</span>
|
|
147
|
+
</div>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// CLI not found — show concise troubleshooting (not installation wizard)
|
|
152
|
+
if (cliAvailable === false) {
|
|
153
|
+
return (
|
|
154
|
+
<div className="flex flex-col gap-4 p-4">
|
|
155
|
+
<div
|
|
156
|
+
className="text-xs font-medium"
|
|
157
|
+
style={{ color: 'var(--text-primary)' }}
|
|
158
|
+
>
|
|
159
|
+
Claude Code
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div
|
|
163
|
+
className="flex flex-col gap-2 px-3 py-2.5 rounded"
|
|
164
|
+
style={{
|
|
165
|
+
background: 'rgba(244, 71, 71, 0.08)',
|
|
166
|
+
border: '1px solid var(--error)',
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<div
|
|
170
|
+
className="text-[11px] font-medium"
|
|
171
|
+
style={{ color: 'var(--error)' }}
|
|
172
|
+
>
|
|
173
|
+
CLI not detected
|
|
174
|
+
</div>
|
|
175
|
+
<p
|
|
176
|
+
className="text-[11px] leading-relaxed"
|
|
177
|
+
style={{ color: 'var(--text-secondary)' }}
|
|
178
|
+
>
|
|
179
|
+
The Claude Code CLI couldn't be found on this machine. If
|
|
180
|
+
it's already installed, the server process may not have access
|
|
181
|
+
to your shell PATH.
|
|
182
|
+
</p>
|
|
183
|
+
{checkError && (
|
|
184
|
+
<code
|
|
185
|
+
className="block px-2 py-1 rounded text-[10px] font-mono break-all"
|
|
186
|
+
style={{
|
|
187
|
+
background: 'var(--bg-primary)',
|
|
188
|
+
color: 'var(--text-muted)',
|
|
189
|
+
}}
|
|
190
|
+
>
|
|
191
|
+
{checkError}
|
|
192
|
+
</code>
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div
|
|
197
|
+
className="flex flex-col gap-2 px-3 py-2.5 rounded"
|
|
198
|
+
style={{
|
|
199
|
+
background: 'var(--bg-tertiary)',
|
|
200
|
+
border: '1px solid var(--border)',
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
<div
|
|
204
|
+
className="text-[11px] font-medium"
|
|
205
|
+
style={{ color: 'var(--text-primary)' }}
|
|
206
|
+
>
|
|
207
|
+
Troubleshooting
|
|
208
|
+
</div>
|
|
209
|
+
<ul
|
|
210
|
+
className="flex flex-col gap-1.5 text-[10px] list-none m-0 p-0"
|
|
211
|
+
style={{ color: 'var(--text-secondary)' }}
|
|
212
|
+
>
|
|
213
|
+
<li className="flex gap-1.5">
|
|
214
|
+
<span style={{ color: 'var(--text-muted)' }}>•</span>
|
|
215
|
+
<span>
|
|
216
|
+
Run{' '}
|
|
217
|
+
<code
|
|
218
|
+
className="font-mono px-1 rounded"
|
|
219
|
+
style={{ background: 'var(--bg-primary)' }}
|
|
220
|
+
>
|
|
221
|
+
which claude
|
|
222
|
+
</code>{' '}
|
|
223
|
+
in your terminal to verify it's installed
|
|
224
|
+
</span>
|
|
225
|
+
</li>
|
|
226
|
+
<li className="flex gap-1.5">
|
|
227
|
+
<span style={{ color: 'var(--text-muted)' }}>•</span>
|
|
228
|
+
<span>Restart pAInt after installing the CLI</span>
|
|
229
|
+
</li>
|
|
230
|
+
<li className="flex gap-1.5">
|
|
231
|
+
<span style={{ color: 'var(--text-muted)' }}>•</span>
|
|
232
|
+
<span>
|
|
233
|
+
Install with:{' '}
|
|
234
|
+
<code
|
|
235
|
+
className="font-mono px-1 rounded"
|
|
236
|
+
style={{ background: 'var(--bg-primary)' }}
|
|
237
|
+
>
|
|
238
|
+
npm install -g @anthropic-ai/claude-code
|
|
239
|
+
</code>
|
|
240
|
+
</span>
|
|
241
|
+
</li>
|
|
242
|
+
</ul>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<button
|
|
246
|
+
onClick={checkCli}
|
|
247
|
+
disabled={checking}
|
|
248
|
+
className="w-full py-1.5 px-3 rounded text-xs font-medium transition-colors disabled:opacity-50"
|
|
249
|
+
style={{ background: 'var(--accent)', color: '#fff' }}
|
|
250
|
+
>
|
|
251
|
+
{checking ? 'Checking...' : 'Retry Detection'}
|
|
252
|
+
</button>
|
|
253
|
+
</div>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// CLI found — just ask for project root
|
|
258
|
+
return (
|
|
259
|
+
<div className="flex flex-col gap-4 p-4">
|
|
260
|
+
<div
|
|
261
|
+
className="text-xs font-medium"
|
|
262
|
+
style={{ color: 'var(--text-primary)' }}
|
|
263
|
+
>
|
|
264
|
+
Claude Code
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
{/* CLI detected badge */}
|
|
268
|
+
<div
|
|
269
|
+
className="flex items-center gap-2 px-3 py-2 rounded"
|
|
270
|
+
style={{
|
|
271
|
+
background: 'rgba(78, 201, 176, 0.08)',
|
|
272
|
+
border: '1px solid var(--success)',
|
|
273
|
+
}}
|
|
274
|
+
>
|
|
275
|
+
<span style={{ color: 'var(--success)' }}>✓</span>
|
|
276
|
+
<span className="text-[11px]" style={{ color: 'var(--success)' }}>
|
|
277
|
+
CLI detected{cliVersion ? ` — ${cliVersion}` : ''}
|
|
278
|
+
</span>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
{/* Project root setup */}
|
|
282
|
+
<div className="flex flex-col gap-2">
|
|
283
|
+
<div
|
|
284
|
+
className="text-[11px] font-medium"
|
|
285
|
+
style={{ color: 'var(--text-primary)' }}
|
|
286
|
+
>
|
|
287
|
+
Set project root for{' '}
|
|
288
|
+
<span className="font-mono" style={{ color: 'var(--accent)' }}>
|
|
289
|
+
{(() => {
|
|
290
|
+
try {
|
|
291
|
+
return new URL(targetUrl).host
|
|
292
|
+
} catch {
|
|
293
|
+
return targetUrl
|
|
294
|
+
}
|
|
295
|
+
})()}
|
|
296
|
+
</span>
|
|
297
|
+
</div>
|
|
298
|
+
<p className="text-[10px]" style={{ color: 'var(--text-muted)' }}>
|
|
299
|
+
Enter the absolute path to the project Claude will analyze. Run{' '}
|
|
300
|
+
<code
|
|
301
|
+
className="font-mono px-1 rounded"
|
|
302
|
+
style={{ background: 'var(--bg-tertiary)' }}
|
|
303
|
+
>
|
|
304
|
+
pwd
|
|
305
|
+
</code>{' '}
|
|
306
|
+
in your project directory to get it.
|
|
307
|
+
</p>
|
|
308
|
+
<ProjectRootSelector
|
|
309
|
+
targetUrl={targetUrl}
|
|
310
|
+
onSaved={handleProjectRootSaved}
|
|
311
|
+
/>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
)
|
|
315
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useEffect, useCallback } from 'react'
|
|
4
|
+
import { useEditorStore } from '@/store'
|
|
5
|
+
import type { ConsoleLevel } from '@/types/messages'
|
|
6
|
+
|
|
7
|
+
const LEVEL_CONFIG: Record<
|
|
8
|
+
ConsoleLevel,
|
|
9
|
+
{ icon: string; bgTint: string; color: string }
|
|
10
|
+
> = {
|
|
11
|
+
error: {
|
|
12
|
+
icon: '\u2718',
|
|
13
|
+
bgTint: 'rgba(248,113,113,0.1)',
|
|
14
|
+
color: 'var(--error)',
|
|
15
|
+
},
|
|
16
|
+
warn: {
|
|
17
|
+
icon: '\u26A0',
|
|
18
|
+
bgTint: 'rgba(251,191,36,0.1)',
|
|
19
|
+
color: 'var(--warning)',
|
|
20
|
+
},
|
|
21
|
+
info: {
|
|
22
|
+
icon: '\u24D8',
|
|
23
|
+
bgTint: 'rgba(74,158,255,0.08)',
|
|
24
|
+
color: 'var(--accent)',
|
|
25
|
+
},
|
|
26
|
+
log: {
|
|
27
|
+
icon: '\u25CB',
|
|
28
|
+
bgTint: 'transparent',
|
|
29
|
+
color: 'var(--text-secondary)',
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type FilterLevel = 'all' | ConsoleLevel
|
|
34
|
+
|
|
35
|
+
function formatTime(ts: number): string {
|
|
36
|
+
const d = new Date(ts)
|
|
37
|
+
return d.toLocaleTimeString('en-US', {
|
|
38
|
+
hour12: false,
|
|
39
|
+
hour: '2-digit',
|
|
40
|
+
minute: '2-digit',
|
|
41
|
+
second: '2-digit',
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function ConsolePanel() {
|
|
46
|
+
const entries = useEditorStore((s) => s.consoleEntries)
|
|
47
|
+
const clearConsole = useEditorStore((s) => s.clearConsole)
|
|
48
|
+
const [filter, setFilter] = useState<FilterLevel>('all')
|
|
49
|
+
const listRef = useRef<HTMLDivElement>(null)
|
|
50
|
+
const userScrolledUp = useRef(false)
|
|
51
|
+
|
|
52
|
+
const filtered =
|
|
53
|
+
filter === 'all' ? entries : entries.filter((e) => e.level === filter)
|
|
54
|
+
const errorCount = entries.filter((e) => e.level === 'error').length
|
|
55
|
+
|
|
56
|
+
// Auto-scroll to bottom unless user scrolled up
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!userScrolledUp.current && listRef.current) {
|
|
59
|
+
listRef.current.scrollTop = listRef.current.scrollHeight
|
|
60
|
+
}
|
|
61
|
+
}, [filtered.length])
|
|
62
|
+
|
|
63
|
+
const handleScroll = useCallback(() => {
|
|
64
|
+
const el = listRef.current
|
|
65
|
+
if (!el) return
|
|
66
|
+
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 30
|
|
67
|
+
userScrolledUp.current = !atBottom
|
|
68
|
+
}, [])
|
|
69
|
+
|
|
70
|
+
const copyErrors = useCallback(() => {
|
|
71
|
+
const errors = entries.filter((e) => e.level === 'error')
|
|
72
|
+
if (errors.length === 0) return
|
|
73
|
+
const text = errors
|
|
74
|
+
.map((e) => {
|
|
75
|
+
const ts = new Date(e.timestamp).toISOString()
|
|
76
|
+
const loc = e.source
|
|
77
|
+
? ` (${e.source}${e.line != null ? ':' + e.line : ''}${e.column != null ? ':' + e.column : ''})`
|
|
78
|
+
: ''
|
|
79
|
+
return `[${ts}] ERROR${loc}: ${e.args.join(' ')}`
|
|
80
|
+
})
|
|
81
|
+
.join('\n')
|
|
82
|
+
navigator.clipboard.writeText(text)
|
|
83
|
+
}, [entries])
|
|
84
|
+
|
|
85
|
+
const filters: { id: FilterLevel; label: string }[] = [
|
|
86
|
+
{ id: 'all', label: 'All' },
|
|
87
|
+
{ id: 'error', label: 'Errors' },
|
|
88
|
+
{ id: 'warn', label: 'Warnings' },
|
|
89
|
+
{ id: 'info', label: 'Info' },
|
|
90
|
+
{ id: 'log', label: 'Logs' },
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div
|
|
95
|
+
className="flex flex-col h-full"
|
|
96
|
+
style={{ color: 'var(--text-primary)' }}
|
|
97
|
+
>
|
|
98
|
+
{/* Header */}
|
|
99
|
+
<div
|
|
100
|
+
className="flex items-center justify-between px-3 py-1.5 flex-shrink-0"
|
|
101
|
+
style={{ borderBottom: '1px solid var(--border)' }}
|
|
102
|
+
>
|
|
103
|
+
<span className="text-xs" style={{ color: 'var(--text-secondary)' }}>
|
|
104
|
+
{filtered.length} message{filtered.length !== 1 ? 's' : ''}
|
|
105
|
+
</span>
|
|
106
|
+
<div className="flex items-center gap-1.5">
|
|
107
|
+
{errorCount > 0 && (
|
|
108
|
+
<button
|
|
109
|
+
onClick={copyErrors}
|
|
110
|
+
className="px-2 py-0.5 rounded text-[10px] font-medium"
|
|
111
|
+
style={{ background: 'var(--error)', color: '#fff' }}
|
|
112
|
+
>
|
|
113
|
+
Copy Errors ({errorCount})
|
|
114
|
+
</button>
|
|
115
|
+
)}
|
|
116
|
+
<button
|
|
117
|
+
onClick={clearConsole}
|
|
118
|
+
className="px-2 py-0.5 rounded text-[10px] font-medium"
|
|
119
|
+
style={{
|
|
120
|
+
background: 'var(--bg-hover)',
|
|
121
|
+
color: 'var(--text-secondary)',
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
Clear
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
{/* Filter bar */}
|
|
130
|
+
<div
|
|
131
|
+
className="flex items-center gap-1 px-3 py-1.5 flex-shrink-0"
|
|
132
|
+
style={{ borderBottom: '1px solid var(--border)' }}
|
|
133
|
+
>
|
|
134
|
+
{filters.map((f) => (
|
|
135
|
+
<button
|
|
136
|
+
key={f.id}
|
|
137
|
+
onClick={() => setFilter(f.id)}
|
|
138
|
+
className="px-2 py-0.5 rounded-full text-[10px] font-medium transition-colors"
|
|
139
|
+
style={{
|
|
140
|
+
background: filter === f.id ? 'var(--accent)' : 'var(--bg-input)',
|
|
141
|
+
color: filter === f.id ? '#fff' : 'var(--text-secondary)',
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
{f.label}
|
|
145
|
+
</button>
|
|
146
|
+
))}
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
{/* Message list */}
|
|
150
|
+
<div
|
|
151
|
+
ref={listRef}
|
|
152
|
+
onScroll={handleScroll}
|
|
153
|
+
className="flex-1 overflow-y-auto"
|
|
154
|
+
>
|
|
155
|
+
{filtered.length === 0 ? (
|
|
156
|
+
<div className="flex items-center justify-center h-full">
|
|
157
|
+
<span className="text-xs" style={{ color: 'var(--text-muted)' }}>
|
|
158
|
+
No console messages captured
|
|
159
|
+
</span>
|
|
160
|
+
</div>
|
|
161
|
+
) : (
|
|
162
|
+
filtered.map((entry) => {
|
|
163
|
+
const cfg = LEVEL_CONFIG[entry.level]
|
|
164
|
+
return (
|
|
165
|
+
<div
|
|
166
|
+
key={entry.id}
|
|
167
|
+
className="flex items-start gap-2 px-3 py-1 text-[11px]"
|
|
168
|
+
style={{
|
|
169
|
+
background: cfg.bgTint,
|
|
170
|
+
borderBottom: '1px solid var(--border)',
|
|
171
|
+
fontFamily: 'monospace',
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
<span
|
|
175
|
+
style={{
|
|
176
|
+
color: cfg.color,
|
|
177
|
+
flexShrink: 0,
|
|
178
|
+
width: 14,
|
|
179
|
+
textAlign: 'center',
|
|
180
|
+
}}
|
|
181
|
+
>
|
|
182
|
+
{cfg.icon}
|
|
183
|
+
</span>
|
|
184
|
+
<span
|
|
185
|
+
className="flex-1 break-all"
|
|
186
|
+
style={{ color: 'var(--text-primary)' }}
|
|
187
|
+
>
|
|
188
|
+
{entry.args.join(' ')}
|
|
189
|
+
</span>
|
|
190
|
+
<span className="flex-shrink-0 flex flex-col items-end gap-0.5">
|
|
191
|
+
<span style={{ color: 'var(--text-muted)', fontSize: 10 }}>
|
|
192
|
+
{formatTime(entry.timestamp)}
|
|
193
|
+
</span>
|
|
194
|
+
{entry.source && (
|
|
195
|
+
<span style={{ color: 'var(--text-muted)', fontSize: 9 }}>
|
|
196
|
+
{entry.source.split('/').pop()}
|
|
197
|
+
{entry.line != null ? ':' + entry.line : ''}
|
|
198
|
+
{entry.column != null ? ':' + entry.column : ''}
|
|
199
|
+
</span>
|
|
200
|
+
)}
|
|
201
|
+
</span>
|
|
202
|
+
</div>
|
|
203
|
+
)
|
|
204
|
+
})
|
|
205
|
+
)}
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
)
|
|
209
|
+
}
|