@antigenic-oss/paint 0.2.0 → 0.2.2
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/README.md +32 -17
- package/bin/bridge-server.js +38 -0
- package/bin/paint.js +559 -104
- package/bin/terminal-server.js +105 -0
- package/package.json +7 -8
- package/public/dev-editor-inspector.js +92 -104
- package/src/app/api/claude/apply/route.ts +2 -2
- package/src/app/api/project/scan/route.ts +1 -1
- package/src/app/api/proxy/[[...path]]/route.ts +4 -4
- package/src/app/docs/DocsClient.tsx +1 -1
- package/src/app/docs/page.tsx +0 -1
- package/src/app/page.tsx +1 -1
- package/src/bridge/api-handlers.ts +1 -1
- package/src/bridge/proxy-handler.ts +4 -4
- package/src/bridge/server.ts +135 -39
- package/src/components/ConnectModal.tsx +1 -2
- package/src/components/PreviewFrame.tsx +2 -2
- package/src/components/ResponsiveToolbar.tsx +1 -2
- package/src/components/common/ColorPicker.tsx +7 -9
- package/src/components/common/UnitInput.tsx +1 -1
- package/src/components/common/VariableColorPicker.tsx +0 -1
- package/src/components/left-panel/ComponentsPanel.tsx +3 -3
- package/src/components/left-panel/LayerNode.tsx +1 -1
- package/src/components/left-panel/icons.tsx +1 -1
- package/src/components/left-panel/terminal/TerminalPanel.tsx +2 -2
- package/src/components/right-panel/ElementLogBox.tsx +1 -3
- package/src/components/right-panel/changes/ChangesPanel.tsx +12 -12
- package/src/components/right-panel/claude/ClaudeIntegrationPanel.tsx +2 -2
- package/src/components/right-panel/claude/DiffViewer.tsx +1 -1
- package/src/components/right-panel/claude/ProjectRootSelector.tsx +7 -7
- package/src/components/right-panel/claude/SetupFlow.tsx +1 -1
- package/src/components/right-panel/console/ConsolePanel.tsx +4 -4
- package/src/components/right-panel/design/BackgroundSection.tsx +2 -2
- package/src/components/right-panel/design/GradientEditor.tsx +6 -6
- package/src/components/right-panel/design/LayoutSection.tsx +4 -4
- package/src/components/right-panel/design/PositionSection.tsx +2 -2
- package/src/components/right-panel/design/SVGSection.tsx +2 -3
- package/src/components/right-panel/design/ShadowBlurSection.tsx +5 -5
- package/src/components/right-panel/design/TextSection.tsx +5 -5
- package/src/components/right-panel/design/icons.tsx +1 -1
- package/src/components/right-panel/design/inputs/BoxModelPreview.tsx +2 -2
- package/src/components/right-panel/design/inputs/CompactInput.tsx +2 -2
- package/src/components/right-panel/design/inputs/DraggableLabel.tsx +2 -1
- package/src/components/right-panel/design/inputs/IconToggleGroup.tsx +1 -1
- package/src/components/right-panel/design/inputs/LinkedInputPair.tsx +3 -3
- package/src/components/right-panel/design/inputs/SectionHeader.tsx +2 -1
- package/src/hooks/useDOMTree.ts +0 -1
- package/src/hooks/usePostMessage.ts +4 -3
- package/src/hooks/useTargetUrl.ts +1 -1
- package/src/inspector/DOMTraverser.ts +2 -2
- package/src/inspector/HoverHighlighter.ts +6 -6
- package/src/inspector/SelectionHighlighter.ts +4 -4
- package/src/lib/classifyElement.ts +1 -2
- package/src/lib/claude-bin.ts +1 -1
- package/src/lib/clientProjectScanner.ts +13 -13
- package/src/lib/cssVariableUtils.ts +1 -1
- package/src/lib/folderPicker.ts +4 -1
- package/src/lib/projectScanner.ts +15 -15
- package/src/lib/tailwindClassParser.ts +1 -1
- package/src/lib/textShadowUtils.ts +1 -1
- package/src/lib/utils.ts +4 -4
- package/src/proxy.ts +1 -1
- package/src/store/treeSlice.ts +2 -2
- package/src/server/terminal-server.ts +0 -104
- package/tsconfig.server.json +0 -12
|
@@ -138,13 +138,13 @@ export function ProjectRootSelector({
|
|
|
138
138
|
setValidating(false)
|
|
139
139
|
}
|
|
140
140
|
}, [
|
|
141
|
-
inputValue,
|
|
142
|
-
targetUrl,
|
|
143
|
-
setProjectRoot,
|
|
144
|
-
onSaved,
|
|
145
|
-
triggerScan,
|
|
146
|
-
triggerClientScan,
|
|
147
|
-
directoryHandle,
|
|
141
|
+
inputValue,
|
|
142
|
+
targetUrl,
|
|
143
|
+
setProjectRoot,
|
|
144
|
+
onSaved,
|
|
145
|
+
triggerScan,
|
|
146
|
+
triggerClientScan,
|
|
147
|
+
directoryHandle, scanFeedbackCallbacks
|
|
148
148
|
])
|
|
149
149
|
|
|
150
150
|
const handleKeyDown = useCallback(
|
|
@@ -16,7 +16,7 @@ export function SetupFlow({ targetUrl, onComplete }: SetupFlowProps) {
|
|
|
16
16
|
const cliAvailable = useEditorStore((s) => s.cliAvailable)
|
|
17
17
|
const setCliAvailable = useEditorStore((s) => s.setCliAvailable)
|
|
18
18
|
const portRoots = useEditorStore((s) => s.portRoots)
|
|
19
|
-
const
|
|
19
|
+
const _projectRoot = portRoots[targetUrl] ?? null
|
|
20
20
|
|
|
21
21
|
const bridgeStatus = useEditorStore((s) => s.bridgeStatus)
|
|
22
22
|
const isLocal = typeof window !== 'undefined' && isEditorOnLocalhost()
|
|
@@ -58,7 +58,7 @@ export function ConsolePanel() {
|
|
|
58
58
|
if (!userScrolledUp.current && listRef.current) {
|
|
59
59
|
listRef.current.scrollTop = listRef.current.scrollHeight
|
|
60
60
|
}
|
|
61
|
-
}, [
|
|
61
|
+
}, [])
|
|
62
62
|
|
|
63
63
|
const handleScroll = useCallback(() => {
|
|
64
64
|
const el = listRef.current
|
|
@@ -74,7 +74,7 @@ export function ConsolePanel() {
|
|
|
74
74
|
.map((e) => {
|
|
75
75
|
const ts = new Date(e.timestamp).toISOString()
|
|
76
76
|
const loc = e.source
|
|
77
|
-
? ` (${e.source}${e.line != null ?
|
|
77
|
+
? ` (${e.source}${e.line != null ? `:${e.line}` : ''}${e.column != null ? `:${e.column}` : ''})`
|
|
78
78
|
: ''
|
|
79
79
|
return `[${ts}] ERROR${loc}: ${e.args.join(' ')}`
|
|
80
80
|
})
|
|
@@ -194,8 +194,8 @@ export function ConsolePanel() {
|
|
|
194
194
|
{entry.source && (
|
|
195
195
|
<span style={{ color: 'var(--text-muted)', fontSize: 9 }}>
|
|
196
196
|
{entry.source.split('/').pop()}
|
|
197
|
-
{entry.line != null ?
|
|
198
|
-
{entry.column != null ?
|
|
197
|
+
{entry.line != null ? `:${entry.line}` : ''}
|
|
198
|
+
{entry.column != null ? `:${entry.column}` : ''}
|
|
199
199
|
</span>
|
|
200
200
|
)}
|
|
201
201
|
</span>
|
|
@@ -250,7 +250,7 @@ function ClipDropdown({
|
|
|
250
250
|
export function BackgroundSection() {
|
|
251
251
|
const computedStyles = useEditorStore((state) => state.computedStyles)
|
|
252
252
|
const cssVariableUsages = useEditorStore((state) => state.cssVariableUsages)
|
|
253
|
-
const
|
|
253
|
+
const _selectorPath = useEditorStore((state) => state.selectorPath)
|
|
254
254
|
const { applyChange, resetProperty } = useChangeTracker()
|
|
255
255
|
|
|
256
256
|
const hasChanges = useEditorStore((s) => {
|
|
@@ -310,7 +310,7 @@ export function BackgroundSection() {
|
|
|
310
310
|
if (parsedGradient) {
|
|
311
311
|
setGradientData(parsedGradient)
|
|
312
312
|
}
|
|
313
|
-
}, [
|
|
313
|
+
}, [bgImage, parsedGradient])
|
|
314
314
|
|
|
315
315
|
// --- Layer preview swatch ---
|
|
316
316
|
const layerSwatchBg = useMemo(() => {
|
|
@@ -42,7 +42,7 @@ function parseStopColor(color: string): { hex: string; opacity: number } {
|
|
|
42
42
|
b = +rgbaMatch[3]
|
|
43
43
|
const a = rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1
|
|
44
44
|
const hex =
|
|
45
|
-
|
|
45
|
+
`#${[r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')}`
|
|
46
46
|
return { hex, opacity: Math.round(a * 100) }
|
|
47
47
|
}
|
|
48
48
|
if (color.startsWith('#')) {
|
|
@@ -453,7 +453,7 @@ export function GradientEditor({
|
|
|
453
453
|
const newStops = [...value.stops, newStop].sort(
|
|
454
454
|
(a, b) => a.position - b.position,
|
|
455
455
|
)
|
|
456
|
-
const newIndex = newStops.
|
|
456
|
+
const newIndex = newStops.indexOf(newStop)
|
|
457
457
|
setSelectedStop(newIndex)
|
|
458
458
|
onChange({ ...value, stops: newStops })
|
|
459
459
|
},
|
|
@@ -483,7 +483,7 @@ export function GradientEditor({
|
|
|
483
483
|
(newHex: string) => {
|
|
484
484
|
const clean = newHex.replace(/[^0-9a-fA-F]/g, '').slice(0, 6)
|
|
485
485
|
if (clean.length === 6 || clean.length === 3) {
|
|
486
|
-
const hex =
|
|
486
|
+
const hex = `#${clean}`
|
|
487
487
|
const newColor = buildStopColor(hex, stopOpacity)
|
|
488
488
|
updateStop(selectedStop, { color: newColor })
|
|
489
489
|
}
|
|
@@ -511,14 +511,14 @@ export function GradientEditor({
|
|
|
511
511
|
|
|
512
512
|
const commitAngle = useCallback(() => {
|
|
513
513
|
const n = parseInt(angleInput, 10)
|
|
514
|
-
if (!isNaN(n)) updateAngle(((n % 360) + 360) % 360)
|
|
514
|
+
if (!Number.isNaN(n)) updateAngle(((n % 360) + 360) % 360)
|
|
515
515
|
else setAngleInput(String(value.angle))
|
|
516
516
|
}, [angleInput, value.angle, updateAngle])
|
|
517
517
|
|
|
518
518
|
const [hexInput, setHexInput] = useState(stopHex.replace('#', ''))
|
|
519
519
|
useEffect(() => setHexInput(stopHex.replace('#', '')), [stopHex])
|
|
520
520
|
|
|
521
|
-
const
|
|
521
|
+
const _commitHex = useCallback(() => {
|
|
522
522
|
updateStopHex(hexInput)
|
|
523
523
|
}, [hexInput, updateStopHex])
|
|
524
524
|
|
|
@@ -527,7 +527,7 @@ export function GradientEditor({
|
|
|
527
527
|
|
|
528
528
|
const commitOpacity = useCallback(() => {
|
|
529
529
|
const n = parseInt(opacityInput, 10)
|
|
530
|
-
if (!isNaN(n)) updateStopOpacity(n)
|
|
530
|
+
if (!Number.isNaN(n)) updateStopOpacity(n)
|
|
531
531
|
else setOpacityInput(String(stopOpacity))
|
|
532
532
|
}, [opacityInput, stopOpacity, updateStopOpacity])
|
|
533
533
|
|
|
@@ -132,7 +132,7 @@ function toGridTemplate(count: number): string {
|
|
|
132
132
|
return `repeat(${count}, 1fr)`
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
function
|
|
135
|
+
function _parseGapValues(gap: string): { row: string; col: string } {
|
|
136
136
|
if (!gap || gap === 'normal') return { row: '0px', col: '0px' }
|
|
137
137
|
const parts = gap.trim().split(/\s+/)
|
|
138
138
|
if (parts.length >= 2) return { row: parts[0], col: parts[1] }
|
|
@@ -1137,7 +1137,7 @@ function NumberStepper({
|
|
|
1137
1137
|
value={value}
|
|
1138
1138
|
onChange={(e) => {
|
|
1139
1139
|
const n = parseInt(e.target.value, 10)
|
|
1140
|
-
if (!isNaN(n) && n >= min && n <= max) onChange(n)
|
|
1140
|
+
if (!Number.isNaN(n) && n >= min && n <= max) onChange(n)
|
|
1141
1141
|
}}
|
|
1142
1142
|
className="w-8 h-full text-center text-[11px] bg-transparent border-none outline-none"
|
|
1143
1143
|
style={{ color: 'var(--text-primary)' }}
|
|
@@ -1199,7 +1199,7 @@ function GapInput({
|
|
|
1199
1199
|
const commit = useCallback(
|
|
1200
1200
|
(num: string, u: string) => {
|
|
1201
1201
|
const n = parseFloat(num)
|
|
1202
|
-
if (!isNaN(n)) onChange(property, formatCSSValue(Math.max(0, n), u))
|
|
1202
|
+
if (!Number.isNaN(n)) onChange(property, formatCSSValue(Math.max(0, n), u))
|
|
1203
1203
|
},
|
|
1204
1204
|
[onChange, property],
|
|
1205
1205
|
)
|
|
@@ -1246,7 +1246,7 @@ function GapInput({
|
|
|
1246
1246
|
const next = units[(idx + 1) % units.length]
|
|
1247
1247
|
setUnit(next)
|
|
1248
1248
|
const n = parseFloat(localVal || '0')
|
|
1249
|
-
if (!isNaN(n)) onChange(property, formatCSSValue(Math.max(0, n), next))
|
|
1249
|
+
if (!Number.isNaN(n)) onChange(property, formatCSSValue(Math.max(0, n), next))
|
|
1250
1250
|
}, [units, unit, localVal, onChange, property])
|
|
1251
1251
|
|
|
1252
1252
|
return (
|
|
@@ -259,7 +259,7 @@ function OffsetInput({
|
|
|
259
259
|
onChange(property, 'auto')
|
|
260
260
|
} else {
|
|
261
261
|
const n = parseFloat(num)
|
|
262
|
-
if (!isNaN(n)) onChange(property, formatCSSValue(n, 'px'))
|
|
262
|
+
if (!Number.isNaN(n)) onChange(property, formatCSSValue(n, 'px'))
|
|
263
263
|
}
|
|
264
264
|
},
|
|
265
265
|
[onChange, property],
|
|
@@ -565,7 +565,7 @@ function ZIndexInput({
|
|
|
565
565
|
setIsAutoMode(true)
|
|
566
566
|
} else {
|
|
567
567
|
const n = parseInt(v, 10)
|
|
568
|
-
if (!isNaN(n)) {
|
|
568
|
+
if (!Number.isNaN(n)) {
|
|
569
569
|
onChange('zIndex', String(n))
|
|
570
570
|
setIsAutoMode(false)
|
|
571
571
|
}
|
|
@@ -69,7 +69,6 @@ function SaveAsVariableRow({
|
|
|
69
69
|
return (
|
|
70
70
|
<div className="flex items-center gap-1 pl-1">
|
|
71
71
|
<input
|
|
72
|
-
autoFocus
|
|
73
72
|
value={varName}
|
|
74
73
|
onChange={(e) => setVarName(e.target.value)}
|
|
75
74
|
onKeyDown={handleKeyDown}
|
|
@@ -329,7 +328,7 @@ export function SVGSection() {
|
|
|
329
328
|
value={fillDisplay}
|
|
330
329
|
property="fill"
|
|
331
330
|
onChange={handleColorChange}
|
|
332
|
-
varExpression={cssVariableUsages
|
|
331
|
+
varExpression={cssVariableUsages.fill}
|
|
333
332
|
/>
|
|
334
333
|
<SaveAsVariableRow
|
|
335
334
|
property="fill"
|
|
@@ -346,7 +345,7 @@ export function SVGSection() {
|
|
|
346
345
|
value={strokeDisplay}
|
|
347
346
|
property="stroke"
|
|
348
347
|
onChange={handleColorChange}
|
|
349
|
-
varExpression={cssVariableUsages
|
|
348
|
+
varExpression={cssVariableUsages.stroke}
|
|
350
349
|
/>
|
|
351
350
|
<SaveAsVariableRow
|
|
352
351
|
property="stroke"
|
|
@@ -78,7 +78,7 @@ export function ShadowBlurSection() {
|
|
|
78
78
|
|
|
79
79
|
const handleFilterBlurChange = (_property: string, value: string) => {
|
|
80
80
|
const num = parseFloat(value)
|
|
81
|
-
if (!isNaN(num) && num > 0) {
|
|
81
|
+
if (!Number.isNaN(num) && num > 0) {
|
|
82
82
|
applyChange('filter', `blur(${num}px)`)
|
|
83
83
|
} else {
|
|
84
84
|
applyChange('filter', 'none')
|
|
@@ -150,7 +150,7 @@ export function ShadowBlurSection() {
|
|
|
150
150
|
property={`shadow-${i}-x`}
|
|
151
151
|
onChange={(_p, v) => {
|
|
152
152
|
const num = parseFloat(v)
|
|
153
|
-
if (!isNaN(num)) updateShadow(i, { x: num })
|
|
153
|
+
if (!Number.isNaN(num)) updateShadow(i, { x: num })
|
|
154
154
|
}}
|
|
155
155
|
units={['px']}
|
|
156
156
|
/>
|
|
@@ -160,7 +160,7 @@ export function ShadowBlurSection() {
|
|
|
160
160
|
property={`shadow-${i}-y`}
|
|
161
161
|
onChange={(_p, v) => {
|
|
162
162
|
const num = parseFloat(v)
|
|
163
|
-
if (!isNaN(num)) updateShadow(i, { y: num })
|
|
163
|
+
if (!Number.isNaN(num)) updateShadow(i, { y: num })
|
|
164
164
|
}}
|
|
165
165
|
units={['px']}
|
|
166
166
|
/>
|
|
@@ -170,7 +170,7 @@ export function ShadowBlurSection() {
|
|
|
170
170
|
property={`shadow-${i}-blur`}
|
|
171
171
|
onChange={(_p, v) => {
|
|
172
172
|
const num = parseFloat(v)
|
|
173
|
-
if (!isNaN(num)) updateShadow(i, { blur: Math.max(0, num) })
|
|
173
|
+
if (!Number.isNaN(num)) updateShadow(i, { blur: Math.max(0, num) })
|
|
174
174
|
}}
|
|
175
175
|
units={['px']}
|
|
176
176
|
min={0}
|
|
@@ -181,7 +181,7 @@ export function ShadowBlurSection() {
|
|
|
181
181
|
property={`shadow-${i}-spread`}
|
|
182
182
|
onChange={(_p, v) => {
|
|
183
183
|
const num = parseFloat(v)
|
|
184
|
-
if (!isNaN(num)) updateShadow(i, { spread: num })
|
|
184
|
+
if (!Number.isNaN(num)) updateShadow(i, { spread: num })
|
|
185
185
|
}}
|
|
186
186
|
units={['px']}
|
|
187
187
|
/>
|
|
@@ -100,7 +100,7 @@ const selectStyle = {
|
|
|
100
100
|
color: 'var(--text-primary)',
|
|
101
101
|
} as const
|
|
102
102
|
|
|
103
|
-
const
|
|
103
|
+
const _labelStyle = {
|
|
104
104
|
color: 'var(--text-muted)',
|
|
105
105
|
} as const
|
|
106
106
|
|
|
@@ -391,7 +391,7 @@ export function TextSection() {
|
|
|
391
391
|
value={color}
|
|
392
392
|
property="color"
|
|
393
393
|
onChange={handleChange}
|
|
394
|
-
varExpression={cssVariableUsages
|
|
394
|
+
varExpression={cssVariableUsages.color}
|
|
395
395
|
/>
|
|
396
396
|
</div>
|
|
397
397
|
</div>
|
|
@@ -668,7 +668,7 @@ export function TextSection() {
|
|
|
668
668
|
property={`textShadow-${i}-x`}
|
|
669
669
|
onChange={(_p, v) => {
|
|
670
670
|
const num = parseFloat(v)
|
|
671
|
-
if (!isNaN(num)) updateShadow(i, { x: num })
|
|
671
|
+
if (!Number.isNaN(num)) updateShadow(i, { x: num })
|
|
672
672
|
}}
|
|
673
673
|
units={['px']}
|
|
674
674
|
/>
|
|
@@ -678,7 +678,7 @@ export function TextSection() {
|
|
|
678
678
|
property={`textShadow-${i}-y`}
|
|
679
679
|
onChange={(_p, v) => {
|
|
680
680
|
const num = parseFloat(v)
|
|
681
|
-
if (!isNaN(num)) updateShadow(i, { y: num })
|
|
681
|
+
if (!Number.isNaN(num)) updateShadow(i, { y: num })
|
|
682
682
|
}}
|
|
683
683
|
units={['px']}
|
|
684
684
|
/>
|
|
@@ -688,7 +688,7 @@ export function TextSection() {
|
|
|
688
688
|
property={`textShadow-${i}-blur`}
|
|
689
689
|
onChange={(_p, v) => {
|
|
690
690
|
const num = parseFloat(v)
|
|
691
|
-
if (!isNaN(num)) updateShadow(i, { blur: Math.max(0, num) })
|
|
691
|
+
if (!Number.isNaN(num)) updateShadow(i, { blur: Math.max(0, num) })
|
|
692
692
|
}}
|
|
693
693
|
units={['px']}
|
|
694
694
|
min={0}
|
|
@@ -118,7 +118,7 @@ function ScrubValue({
|
|
|
118
118
|
const commitEdit = useCallback(() => {
|
|
119
119
|
setIsEditing(false)
|
|
120
120
|
const n = parseFloat(editValue)
|
|
121
|
-
if (!isNaN(n)) {
|
|
121
|
+
if (!Number.isNaN(n)) {
|
|
122
122
|
onChange(property, formatCSSValue(Math.max(0, n), unit))
|
|
123
123
|
}
|
|
124
124
|
}, [editValue, onChange, property, unit])
|
|
@@ -209,7 +209,7 @@ export function BoxModelPreview({
|
|
|
209
209
|
|
|
210
210
|
// Dim-display helper for the content dimension text
|
|
211
211
|
const dimText =
|
|
212
|
-
|
|
212
|
+
`${contentW ? `${contentW}` : '–'} × ${contentH ? `${contentH}` : '–'}`
|
|
213
213
|
|
|
214
214
|
return (
|
|
215
215
|
<div className="pt-1.5" style={{ borderTop: '1px solid var(--border)' }}>
|
|
@@ -154,7 +154,7 @@ export function CompactInput({
|
|
|
154
154
|
onChange(property, 'auto')
|
|
155
155
|
} else {
|
|
156
156
|
const n = parseFloat(num)
|
|
157
|
-
if (!isNaN(n)) {
|
|
157
|
+
if (!Number.isNaN(n)) {
|
|
158
158
|
const clamped = clampValue(n)
|
|
159
159
|
onChange(property, formatCSSValue(clamped, u))
|
|
160
160
|
}
|
|
@@ -173,7 +173,7 @@ export function CompactInput({
|
|
|
173
173
|
onChange(property, 'auto')
|
|
174
174
|
} else {
|
|
175
175
|
const num = parseFloat(localValue || '0')
|
|
176
|
-
if (!isNaN(num)) {
|
|
176
|
+
if (!Number.isNaN(num)) {
|
|
177
177
|
const clamped = clampValue(num)
|
|
178
178
|
setLocalValue(String(clamped))
|
|
179
179
|
onChange(property, formatCSSValue(clamped, nextUnit))
|
|
@@ -54,6 +54,7 @@ export function DraggableLabel({
|
|
|
54
54
|
value === 'auto' ||
|
|
55
55
|
value === 'none' ||
|
|
56
56
|
value === 'normal' ||
|
|
57
|
+
Number.
|
|
57
58
|
isNaN(parsed.number)
|
|
58
59
|
)
|
|
59
60
|
return
|
|
@@ -98,7 +99,7 @@ export function DraggableLabel({
|
|
|
98
99
|
value !== 'auto' &&
|
|
99
100
|
value !== 'none' &&
|
|
100
101
|
value !== 'normal' &&
|
|
101
|
-
!isNaN(parsed.number)
|
|
102
|
+
!Number.isNaN(parsed.number)
|
|
102
103
|
|
|
103
104
|
return (
|
|
104
105
|
<span
|
|
@@ -14,7 +14,7 @@ interface LinkedInputPairProps {
|
|
|
14
14
|
units?: string[]
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function
|
|
17
|
+
function _areAllEqual(values: {
|
|
18
18
|
top: string
|
|
19
19
|
right: string
|
|
20
20
|
bottom: string
|
|
@@ -68,12 +68,12 @@ export function LinkedInputPair({
|
|
|
68
68
|
}
|
|
69
69
|
}, [values])
|
|
70
70
|
|
|
71
|
-
const handleLinkedHChange = (
|
|
71
|
+
const handleLinkedHChange = (_property: string, value: string) => {
|
|
72
72
|
onChange(properties.left, value)
|
|
73
73
|
onChange(properties.right, value)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
const handleLinkedVChange = (
|
|
76
|
+
const handleLinkedVChange = (_property: string, value: string) => {
|
|
77
77
|
onChange(properties.top, value)
|
|
78
78
|
onChange(properties.bottom, value)
|
|
79
79
|
}
|
package/src/hooks/useDOMTree.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import React
|
|
3
|
+
import type React from 'react'
|
|
4
|
+
import { useEffect, useCallback } from 'react'
|
|
4
5
|
import { useEditorStore } from '@/store'
|
|
5
6
|
import type {
|
|
6
7
|
InspectorToEditorMessage,
|
|
@@ -114,7 +115,7 @@ function handleMessage(event: MessageEvent) {
|
|
|
114
115
|
sendViaIframe({ type: 'REQUEST_DOM_TREE' })
|
|
115
116
|
sendViaIframe({ type: 'REQUEST_PAGE_LINKS' })
|
|
116
117
|
sendViaIframe({ type: 'REQUEST_CSS_VARIABLES' })
|
|
117
|
-
setTimeout(
|
|
118
|
+
setTimeout(() => {
|
|
118
119
|
sendViaIframe({ type: 'REQUEST_COMPONENTS', payload: {} })
|
|
119
120
|
}, 500)
|
|
120
121
|
|
|
@@ -265,7 +266,7 @@ function handleMessage(event: MessageEvent) {
|
|
|
265
266
|
// Debounced component rescan on DOM changes (2s to avoid
|
|
266
267
|
// excessive scanning during rapid DOM mutations)
|
|
267
268
|
if (componentRescanTimer) clearTimeout(componentRescanTimer)
|
|
268
|
-
componentRescanTimer = setTimeout(
|
|
269
|
+
componentRescanTimer = setTimeout(() => {
|
|
269
270
|
componentRescanTimer = null
|
|
270
271
|
sendViaIframe({ type: 'REQUEST_COMPONENTS', payload: {} })
|
|
271
272
|
}, 2000)
|
|
@@ -60,7 +60,7 @@ export function useTargetUrl() {
|
|
|
60
60
|
)
|
|
61
61
|
return
|
|
62
62
|
|
|
63
|
-
const delay = RECONNECT_BASE_DELAY_MS *
|
|
63
|
+
const delay = RECONNECT_BASE_DELAY_MS * 2 ** retryCountRef.current
|
|
64
64
|
retryTimeoutRef.current = setTimeout(() => {
|
|
65
65
|
retryCountRef.current++
|
|
66
66
|
setConnectionStatus('connecting')
|
|
@@ -21,14 +21,14 @@ export function generateSelectorPath(element: Element): string {
|
|
|
21
21
|
if (current.className && typeof current.className === 'string') {
|
|
22
22
|
const classes = current.className.trim().split(/\s+/).filter(Boolean)
|
|
23
23
|
if (classes.length > 0) {
|
|
24
|
-
selector +=
|
|
24
|
+
selector += `.${classes.join('.')}`
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const parent = current.parentElement
|
|
29
29
|
if (parent) {
|
|
30
30
|
const siblings = Array.from(parent.children).filter(
|
|
31
|
-
(child) => child.tagName === current
|
|
31
|
+
(child) => child.tagName === current?.tagName,
|
|
32
32
|
)
|
|
33
33
|
if (siblings.length > 1) {
|
|
34
34
|
const index = siblings.indexOf(current) + 1
|
|
@@ -16,11 +16,11 @@ export function createHoverHighlighter() {
|
|
|
16
16
|
|
|
17
17
|
function getElementLabel(el: Element): string {
|
|
18
18
|
const tag = el.tagName.toLowerCase()
|
|
19
|
-
if (el.id) return tag
|
|
19
|
+
if (el.id) return `${tag}#${el.id}`
|
|
20
20
|
const cls = el.className
|
|
21
21
|
if (cls && typeof cls === 'string') {
|
|
22
22
|
const first = cls.trim().split(/\s+/)[0]
|
|
23
|
-
if (first) return tag
|
|
23
|
+
if (first) return `${tag}.${first}`
|
|
24
24
|
}
|
|
25
25
|
return tag
|
|
26
26
|
}
|
|
@@ -28,10 +28,10 @@ export function createHoverHighlighter() {
|
|
|
28
28
|
return {
|
|
29
29
|
show(el: Element, rect: DOMRect) {
|
|
30
30
|
overlay.style.display = 'block'
|
|
31
|
-
overlay.style.top = rect.top
|
|
32
|
-
overlay.style.left = rect.left
|
|
33
|
-
overlay.style.width = rect.width
|
|
34
|
-
overlay.style.height = rect.height
|
|
31
|
+
overlay.style.top = `${rect.top}px`
|
|
32
|
+
overlay.style.left = `${rect.left}px`
|
|
33
|
+
overlay.style.width = `${rect.width}px`
|
|
34
|
+
overlay.style.height = `${rect.height}px`
|
|
35
35
|
label.textContent = getElementLabel(el)
|
|
36
36
|
// Flip label below if near top
|
|
37
37
|
if (rect.top < 20) {
|
|
@@ -12,10 +12,10 @@ export function createSelectionHighlighter() {
|
|
|
12
12
|
return {
|
|
13
13
|
show(rect: DOMRect) {
|
|
14
14
|
overlay.style.display = 'block'
|
|
15
|
-
overlay.style.top = rect.top
|
|
16
|
-
overlay.style.left = rect.left
|
|
17
|
-
overlay.style.width = rect.width
|
|
18
|
-
overlay.style.height = rect.height
|
|
15
|
+
overlay.style.top = `${rect.top}px`
|
|
16
|
+
overlay.style.left = `${rect.left}px`
|
|
17
|
+
overlay.style.width = `${rect.width}px`
|
|
18
|
+
overlay.style.height = `${rect.height}px`
|
|
19
19
|
},
|
|
20
20
|
hide() {
|
|
21
21
|
overlay.style.display = 'none'
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
import type {
|
|
10
10
|
FileMap,
|
|
11
|
-
ComponentEntry,
|
|
12
11
|
RouteEntry,
|
|
13
12
|
SourceInfo,
|
|
14
13
|
} from '@/types/claude'
|
|
@@ -328,7 +327,7 @@ export function inferSourcePath(opts: {
|
|
|
328
327
|
const abs = opts.sourceInfo.fileName
|
|
329
328
|
if (opts.projectRoot) {
|
|
330
329
|
const root = opts.projectRoot.replace(/\/$/, '')
|
|
331
|
-
if (abs.startsWith(root
|
|
330
|
+
if (abs.startsWith(`${root}/`)) {
|
|
332
331
|
return abs.substring(root.length + 1)
|
|
333
332
|
}
|
|
334
333
|
}
|
package/src/lib/claude-bin.ts
CHANGED
|
@@ -97,7 +97,7 @@ function filePathToUrlPattern(relativePath: string): string {
|
|
|
97
97
|
const parts = relativePath.split('/')
|
|
98
98
|
const routeParts = parts.slice(1, -1)
|
|
99
99
|
const filtered = routeParts.filter((p) => !p.startsWith('('))
|
|
100
|
-
return
|
|
100
|
+
return `/${filtered.join('/')}`
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
interface Collectors {
|
|
@@ -201,14 +201,14 @@ function detectFramework(
|
|
|
201
201
|
devDeps: Record<string, string>,
|
|
202
202
|
): string | null {
|
|
203
203
|
const all = { ...deps, ...devDeps }
|
|
204
|
-
if (all
|
|
205
|
-
if (all['@remix-run/react'] || all
|
|
206
|
-
if (all
|
|
207
|
-
if (all
|
|
204
|
+
if (all.next) return 'Next.js'
|
|
205
|
+
if (all['@remix-run/react'] || all.remix) return 'Remix'
|
|
206
|
+
if (all.gatsby) return 'Gatsby'
|
|
207
|
+
if (all.astro) return 'Astro'
|
|
208
208
|
if (all['@angular/core']) return 'Angular'
|
|
209
|
-
if (all
|
|
210
|
-
if (all
|
|
211
|
-
if (all
|
|
209
|
+
if (all.vue) return 'Vue'
|
|
210
|
+
if (all.svelte) return 'Svelte'
|
|
211
|
+
if (all.react) return 'React'
|
|
212
212
|
return null
|
|
213
213
|
}
|
|
214
214
|
|
|
@@ -220,13 +220,13 @@ function detectCssStrategy(
|
|
|
220
220
|
): string[] {
|
|
221
221
|
const all = { ...deps, ...devDeps }
|
|
222
222
|
const strategies: string[] = []
|
|
223
|
-
if (all
|
|
223
|
+
if (all.tailwindcss) strategies.push('Tailwind')
|
|
224
224
|
if (hasCssModules) strategies.push('CSS Modules')
|
|
225
225
|
if (all['styled-components']) strategies.push('styled-components')
|
|
226
226
|
if (all['@emotion/react'] || all['@emotion/styled'])
|
|
227
227
|
strategies.push('Emotion')
|
|
228
|
-
if (all
|
|
229
|
-
if (all
|
|
228
|
+
if (all.sass || all['node-sass']) strategies.push('Sass')
|
|
229
|
+
if (all.less) strategies.push('Less')
|
|
230
230
|
if (all['@vanilla-extract/css']) strategies.push('Vanilla Extract')
|
|
231
231
|
if (strategies.length === 0 && cssFiles.length > 0) strategies.push('CSS')
|
|
232
232
|
return strategies
|
|
@@ -270,7 +270,7 @@ export async function scanProjectClient(
|
|
|
270
270
|
|
|
271
271
|
for (const dir of candidateDirs) {
|
|
272
272
|
const alreadyCovered = scannedRoots.some((root) =>
|
|
273
|
-
dir.startsWith(root
|
|
273
|
+
dir.startsWith(`${root}/`),
|
|
274
274
|
)
|
|
275
275
|
if (alreadyCovered) continue
|
|
276
276
|
|
|
@@ -312,7 +312,7 @@ export async function scanProjectClient(
|
|
|
312
312
|
entry.kind === 'directory' &&
|
|
313
313
|
ASSET_DIR_NAMES.has(name.toLowerCase())
|
|
314
314
|
) {
|
|
315
|
-
collectors.assetDirs.add(
|
|
315
|
+
collectors.assetDirs.add(`public/${name}`)
|
|
316
316
|
}
|
|
317
317
|
if (entry.kind === 'file') {
|
|
318
318
|
collectors.assetDirs.add('public')
|
package/src/lib/folderPicker.ts
CHANGED
|
@@ -57,7 +57,10 @@ export async function pickFolder(): Promise<FolderPickResult> {
|
|
|
57
57
|
|
|
58
58
|
async function pickFolderClient(): Promise<FolderPickResult> {
|
|
59
59
|
try {
|
|
60
|
-
|
|
60
|
+
if (!window.showDirectoryPicker) {
|
|
61
|
+
return { type: 'error', message: 'Folder picker is not supported' }
|
|
62
|
+
}
|
|
63
|
+
const handle = await window.showDirectoryPicker({ mode: 'read' })
|
|
61
64
|
return { type: 'handle', handle, name: handle.name }
|
|
62
65
|
} catch (err) {
|
|
63
66
|
if (err instanceof DOMException && err.name === 'AbortError') {
|