zelune 1.5.2 → 1.5.5
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/CHANGELOG.md +38 -0
- package/bin/cli.mjs +17 -6
- package/index.html +9 -2
- package/package.json +2 -1
- package/plugin.json +1 -1
- package/plugins/viteReactNativeFallbackPlugin.ts +528 -0
- package/plugins/viteVirtualScreensPlugin.ts +139 -8
- package/src/canvas/CanvasApp.tsx +186 -45
- package/src/canvas/FlowArrowsLayer.tsx +7 -7
- package/src/canvas/InspectorPanel.tsx +58 -49
- package/src/canvas/ScreenArtboard.tsx +107 -32
- package/src/main.tsx +71 -1
- package/src/runtime/MockNavigationContext.tsx +1460 -40
- package/src/runtime/reactNativeShim.ts +141 -0
- package/src/screens/ProductDetailScreen.tsx +32 -59
- package/src/server/astEngine.ts +10 -1
- package/src/styles.css +182 -21
- package/vite.config.ts +136 -67
|
@@ -11,7 +11,7 @@ interface FlowArrowsLayerProps {
|
|
|
11
11
|
|
|
12
12
|
const ARTBOARD_HEADER_OFFSET = 38;
|
|
13
13
|
|
|
14
|
-
export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = ({
|
|
14
|
+
export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = React.memo(({
|
|
15
15
|
screens,
|
|
16
16
|
layout,
|
|
17
17
|
activeScreenId,
|
|
@@ -146,7 +146,7 @@ export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = ({
|
|
|
146
146
|
cx={startX}
|
|
147
147
|
cy={startY}
|
|
148
148
|
r={4}
|
|
149
|
-
fill="
|
|
149
|
+
fill="var(--studio-surface-1)"
|
|
150
150
|
stroke={strokeColor}
|
|
151
151
|
strokeWidth={2}
|
|
152
152
|
/>
|
|
@@ -172,15 +172,15 @@ export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = ({
|
|
|
172
172
|
gap: 4,
|
|
173
173
|
padding: '3px 9px',
|
|
174
174
|
borderRadius: 999,
|
|
175
|
-
backgroundColor:
|
|
176
|
-
border: `1px solid ${isHighlighted ? '
|
|
177
|
-
color: isHighlighted ? '
|
|
175
|
+
backgroundColor: 'var(--studio-surface-1)',
|
|
176
|
+
border: `1px solid ${isHighlighted ? 'var(--studio-accent-emerald)' : 'var(--studio-badge-indigo-border)'}`,
|
|
177
|
+
color: isHighlighted ? 'var(--studio-badge-emerald-text)' : 'var(--studio-badge-indigo-text)',
|
|
178
178
|
fontSize: 10,
|
|
179
179
|
fontWeight: 600,
|
|
180
180
|
fontFamily: 'JetBrains Mono, monospace',
|
|
181
181
|
whiteSpace: 'nowrap',
|
|
182
182
|
cursor: 'pointer',
|
|
183
|
-
boxShadow: '0 4px 12px rgba(0,0,0,0.
|
|
183
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
|
|
184
184
|
margin: '0 auto',
|
|
185
185
|
}}
|
|
186
186
|
>
|
|
@@ -194,4 +194,4 @@ export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = ({
|
|
|
194
194
|
})}
|
|
195
195
|
</svg>
|
|
196
196
|
);
|
|
197
|
-
};
|
|
197
|
+
});
|
|
@@ -197,7 +197,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
197
197
|
display: 'flex',
|
|
198
198
|
alignItems: 'center',
|
|
199
199
|
justifyContent: 'space-between',
|
|
200
|
-
color: '
|
|
200
|
+
color: 'var(--studio-text-primary)',
|
|
201
201
|
fontWeight: 600,
|
|
202
202
|
fontSize: 13.5,
|
|
203
203
|
letterSpacing: '-0.01em',
|
|
@@ -238,11 +238,11 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
238
238
|
lineHeight: 1.6,
|
|
239
239
|
}}
|
|
240
240
|
>
|
|
241
|
-
Select any node on a <code style={{ color: '#
|
|
241
|
+
Select any node on a <code style={{ color: '#0284C7', fontFamily: 'var(--font-mono)' }}>393 × 852</code>{' '}
|
|
242
242
|
artboard to inspect and live-patch its{' '}
|
|
243
|
-
<strong style={{ color: '
|
|
244
|
-
<strong style={{ color: '
|
|
245
|
-
<code style={{ color: '#
|
|
243
|
+
<strong style={{ color: 'var(--studio-text-primary)' }}>StyleSheet.create</strong> rules or{' '}
|
|
244
|
+
<strong style={{ color: 'var(--studio-text-primary)' }}>static copy</strong> directly in the{' '}
|
|
245
|
+
<code style={{ color: '#6366F1', fontFamily: 'var(--font-mono)' }}>.tsx</code> AST.
|
|
246
246
|
</div>
|
|
247
247
|
|
|
248
248
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 4 }}>
|
|
@@ -258,15 +258,15 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
258
258
|
DESIGN & SYNC GOVERNANCE
|
|
259
259
|
</div>
|
|
260
260
|
<div style={{ fontSize: 12, color: 'var(--studio-text-secondary)', lineHeight: 1.5 }}>
|
|
261
|
-
• <strong style={{ color: '
|
|
261
|
+
• <strong style={{ color: 'var(--studio-text-primary)' }}>Tier 1 (Surgical AST)</strong>: Instant write-back to colors, typography, spacing, and copy.
|
|
262
262
|
</div>
|
|
263
263
|
<div style={{ fontSize: 12, color: 'var(--studio-text-secondary)', lineHeight: 1.5 }}>
|
|
264
|
-
• <strong style={{ color: '
|
|
264
|
+
• <strong style={{ color: 'var(--studio-text-primary)' }}>Tier 2 (Antigravity Bridge)</strong>: Dispatch structural refactors with exact file & AST path coordinates.
|
|
265
265
|
</div>
|
|
266
266
|
<div style={{ fontSize: 12, color: 'var(--studio-text-secondary)', lineHeight: 1.5 }}>
|
|
267
|
-
• <strong style={{ color: '
|
|
268
|
-
<code style={{ fontFamily: 'var(--font-mono)', color: '#
|
|
269
|
-
<code style={{ fontFamily: 'var(--font-mono)', color: '#
|
|
267
|
+
• <strong style={{ color: 'var(--studio-text-primary)' }}>Impeccable & Taste Guardrails</strong>: Governed by{' '}
|
|
268
|
+
<code style={{ fontFamily: 'var(--font-mono)', color: '#0284C7' }}>PRODUCT.md</code> &{' '}
|
|
269
|
+
<code style={{ fontFamily: 'var(--font-mono)', color: '#0284C7' }}>DESIGN.md</code>.
|
|
270
270
|
</div>
|
|
271
271
|
</div>
|
|
272
272
|
</div>
|
|
@@ -280,7 +280,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
280
280
|
paddingTop: 12,
|
|
281
281
|
}}
|
|
282
282
|
>
|
|
283
|
-
MCP Server: <span style={{ color: '
|
|
283
|
+
MCP Server: <span style={{ color: 'var(--studio-accent-emerald)' }}>Zelune Ready (mcp/server.ts)</span>
|
|
284
284
|
</div>
|
|
285
285
|
</div>
|
|
286
286
|
) : (
|
|
@@ -310,9 +310,9 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
310
310
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
311
311
|
<span
|
|
312
312
|
style={{
|
|
313
|
-
backgroundColor: '
|
|
314
|
-
border: '1px solid
|
|
315
|
-
color: '
|
|
313
|
+
backgroundColor: 'var(--studio-badge-indigo-bg)',
|
|
314
|
+
border: '1px solid var(--studio-badge-indigo-border)',
|
|
315
|
+
color: 'var(--studio-badge-indigo-text)',
|
|
316
316
|
fontSize: 11.5,
|
|
317
317
|
fontWeight: 700,
|
|
318
318
|
fontFamily: 'var(--font-mono)',
|
|
@@ -325,10 +325,11 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
325
325
|
{inspected.styleSheetKey && (
|
|
326
326
|
<span
|
|
327
327
|
style={{
|
|
328
|
-
backgroundColor: '
|
|
329
|
-
border: '1px solid
|
|
330
|
-
color: '
|
|
328
|
+
backgroundColor: 'var(--studio-badge-emerald-bg)',
|
|
329
|
+
border: '1px solid var(--studio-badge-emerald-border)',
|
|
330
|
+
color: 'var(--studio-badge-emerald-text)',
|
|
331
331
|
fontSize: 11,
|
|
332
|
+
fontWeight: 600,
|
|
332
333
|
fontFamily: 'var(--font-mono)',
|
|
333
334
|
padding: '1px 6px',
|
|
334
335
|
borderRadius: 4,
|
|
@@ -396,11 +397,11 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
396
397
|
<div
|
|
397
398
|
style={{
|
|
398
399
|
padding: '6px 16px',
|
|
399
|
-
backgroundColor: '
|
|
400
|
-
borderBottom: '1px solid
|
|
400
|
+
backgroundColor: 'var(--studio-badge-indigo-bg)',
|
|
401
|
+
borderBottom: '1px solid var(--studio-badge-indigo-border)',
|
|
401
402
|
fontSize: 11,
|
|
402
403
|
fontFamily: 'var(--font-mono)',
|
|
403
|
-
color: '
|
|
404
|
+
color: 'var(--studio-badge-indigo-text)',
|
|
404
405
|
display: 'flex',
|
|
405
406
|
alignItems: 'center',
|
|
406
407
|
justifyContent: 'space-between',
|
|
@@ -413,7 +414,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
413
414
|
? 'Inspecting AST node...'
|
|
414
415
|
: statusMessage}
|
|
415
416
|
</span>
|
|
416
|
-
<span style={{ color: '
|
|
417
|
+
<span style={{ color: 'var(--studio-accent-emerald)', fontWeight: 600 }}>● AST Sync</span>
|
|
417
418
|
</div>
|
|
418
419
|
)}
|
|
419
420
|
|
|
@@ -423,16 +424,24 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
423
424
|
style={{
|
|
424
425
|
margin: '12px 14px 0',
|
|
425
426
|
padding: '9px 12px',
|
|
426
|
-
backgroundColor: '
|
|
427
|
-
border: '1px solid
|
|
427
|
+
backgroundColor: 'var(--studio-badge-emerald-bg)',
|
|
428
|
+
border: '1px solid var(--studio-badge-emerald-border)',
|
|
428
429
|
borderRadius: 8,
|
|
429
430
|
display: 'flex',
|
|
430
431
|
alignItems: 'center',
|
|
431
432
|
justifyContent: 'space-between',
|
|
432
433
|
}}
|
|
433
434
|
>
|
|
434
|
-
<div
|
|
435
|
-
|
|
435
|
+
<div
|
|
436
|
+
style={{
|
|
437
|
+
display: 'flex',
|
|
438
|
+
alignItems: 'center',
|
|
439
|
+
gap: 8,
|
|
440
|
+
fontSize: 12,
|
|
441
|
+
color: 'var(--studio-badge-emerald-text)',
|
|
442
|
+
}}
|
|
443
|
+
>
|
|
444
|
+
<Navigation size={13} color="var(--studio-accent-emerald)" />
|
|
436
445
|
<span>
|
|
437
446
|
Flow → <strong>{inspected.navigationTarget}</strong>
|
|
438
447
|
</span>
|
|
@@ -485,11 +494,11 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
485
494
|
border: '1px solid var(--studio-border-subtle)',
|
|
486
495
|
borderRadius: 7,
|
|
487
496
|
fontSize: 11.5,
|
|
488
|
-
color: '
|
|
497
|
+
color: 'var(--studio-text-secondary)',
|
|
489
498
|
lineHeight: 1.5,
|
|
490
499
|
}}
|
|
491
500
|
>
|
|
492
|
-
Contains dynamic JSX expression (<code style={{ color: '#
|
|
501
|
+
Contains dynamic JSX expression (<code style={{ color: '#0284C7' }}>{'{...}'}</code>). Protected from static overwrite—use <strong>Ask Antigravity</strong> below to refactor expression logic.
|
|
493
502
|
</div>
|
|
494
503
|
) : (
|
|
495
504
|
<textarea
|
|
@@ -505,7 +514,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
505
514
|
borderRadius: 7,
|
|
506
515
|
paddingLeft: 10,
|
|
507
516
|
paddingRight: 10,
|
|
508
|
-
color: '
|
|
517
|
+
color: 'var(--studio-text-primary)',
|
|
509
518
|
fontSize: 12.5,
|
|
510
519
|
resize: 'vertical',
|
|
511
520
|
fontFamily: 'var(--font-sans)',
|
|
@@ -535,7 +544,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
535
544
|
|
|
536
545
|
{/* Background Color */}
|
|
537
546
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
|
538
|
-
<label style={{ fontSize: 11.5, color: '
|
|
547
|
+
<label style={{ fontSize: 11.5, color: 'var(--studio-text-secondary)' }}>Background Color</label>
|
|
539
548
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
540
549
|
<input
|
|
541
550
|
type="color"
|
|
@@ -569,7 +578,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
569
578
|
borderRadius: 6,
|
|
570
579
|
paddingLeft: 10,
|
|
571
580
|
paddingRight: 10,
|
|
572
|
-
color: '
|
|
581
|
+
color: 'var(--studio-text-primary)',
|
|
573
582
|
fontSize: 12,
|
|
574
583
|
fontFamily: 'var(--font-mono)',
|
|
575
584
|
}}
|
|
@@ -591,7 +600,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
591
600
|
border:
|
|
592
601
|
localStyles.backgroundColor === hex
|
|
593
602
|
? '2px solid #38BDF8'
|
|
594
|
-
: '1px solid
|
|
603
|
+
: '1px solid var(--studio-border-strong)',
|
|
595
604
|
cursor: 'pointer',
|
|
596
605
|
}}
|
|
597
606
|
/>
|
|
@@ -601,7 +610,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
601
610
|
|
|
602
611
|
{/* Text Color */}
|
|
603
612
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
|
604
|
-
<label style={{ fontSize: 11.5, color: '
|
|
613
|
+
<label style={{ fontSize: 11.5, color: 'var(--studio-text-secondary)' }}>Text Color</label>
|
|
605
614
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
606
615
|
<input
|
|
607
616
|
type="color"
|
|
@@ -635,7 +644,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
635
644
|
borderRadius: 6,
|
|
636
645
|
paddingLeft: 10,
|
|
637
646
|
paddingRight: 10,
|
|
638
|
-
color: '
|
|
647
|
+
color: 'var(--studio-text-primary)',
|
|
639
648
|
fontSize: 12,
|
|
640
649
|
fontFamily: 'var(--font-mono)',
|
|
641
650
|
}}
|
|
@@ -680,7 +689,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
680
689
|
borderRadius: 6,
|
|
681
690
|
paddingLeft: 8,
|
|
682
691
|
paddingRight: 8,
|
|
683
|
-
color: '
|
|
692
|
+
color: 'var(--studio-text-primary)',
|
|
684
693
|
fontSize: 12,
|
|
685
694
|
fontFamily: 'var(--font-mono)',
|
|
686
695
|
}}
|
|
@@ -702,7 +711,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
702
711
|
borderRadius: 6,
|
|
703
712
|
paddingLeft: 8,
|
|
704
713
|
paddingRight: 8,
|
|
705
|
-
color: '
|
|
714
|
+
color: 'var(--studio-text-primary)',
|
|
706
715
|
fontSize: 12,
|
|
707
716
|
fontFamily: 'var(--font-mono)',
|
|
708
717
|
}}
|
|
@@ -761,7 +770,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
761
770
|
borderRadius: 6,
|
|
762
771
|
paddingLeft: 8,
|
|
763
772
|
paddingRight: 8,
|
|
764
|
-
color: '
|
|
773
|
+
color: 'var(--studio-text-primary)',
|
|
765
774
|
fontSize: 12,
|
|
766
775
|
fontFamily: 'var(--font-mono)',
|
|
767
776
|
}}
|
|
@@ -785,13 +794,13 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
785
794
|
width: '100%',
|
|
786
795
|
padding: '10px 14px',
|
|
787
796
|
backgroundColor: 'var(--studio-surface-3)',
|
|
788
|
-
color: '
|
|
797
|
+
color: 'var(--studio-text-primary)',
|
|
789
798
|
border: '1px solid rgba(56, 189, 248, 0.42)',
|
|
790
799
|
borderRadius: 8,
|
|
791
800
|
fontSize: 12.5,
|
|
792
801
|
fontWeight: 600,
|
|
793
802
|
cursor: 'pointer',
|
|
794
|
-
boxShadow: 'inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 8px 20px -8px rgba(0, 0, 0, 0.
|
|
803
|
+
boxShadow: 'inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 8px 20px -8px rgba(0, 0, 0, 0.25)',
|
|
795
804
|
}}
|
|
796
805
|
>
|
|
797
806
|
<Sparkles size={14} color="#38BDF8" />
|
|
@@ -825,7 +834,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
825
834
|
padding: 10,
|
|
826
835
|
fontSize: 11,
|
|
827
836
|
fontFamily: 'var(--font-mono)',
|
|
828
|
-
color: '
|
|
837
|
+
color: 'var(--studio-text-secondary)',
|
|
829
838
|
overflowX: 'auto',
|
|
830
839
|
margin: 0,
|
|
831
840
|
lineHeight: 1.45,
|
|
@@ -846,7 +855,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
846
855
|
<div
|
|
847
856
|
style={{
|
|
848
857
|
backgroundColor: 'var(--studio-surface-1)',
|
|
849
|
-
color: '
|
|
858
|
+
color: 'var(--studio-text-primary)',
|
|
850
859
|
border: '1px solid var(--studio-border-strong)',
|
|
851
860
|
borderRadius: 14,
|
|
852
861
|
padding: 22,
|
|
@@ -855,7 +864,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
855
864
|
display: 'flex',
|
|
856
865
|
flexDirection: 'column',
|
|
857
866
|
gap: 15,
|
|
858
|
-
boxShadow: '0 28px 60px -12px rgba(0, 0, 0, 0.
|
|
867
|
+
boxShadow: '0 28px 60px -12px rgba(0, 0, 0, 0.55)',
|
|
859
868
|
}}
|
|
860
869
|
>
|
|
861
870
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
@@ -877,7 +886,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
877
886
|
background: 'transparent',
|
|
878
887
|
border: '1px solid transparent',
|
|
879
888
|
borderRadius: 5,
|
|
880
|
-
color: '
|
|
889
|
+
color: 'var(--studio-text-secondary)',
|
|
881
890
|
cursor: 'pointer',
|
|
882
891
|
padding: 4,
|
|
883
892
|
}}
|
|
@@ -886,10 +895,10 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
886
895
|
</button>
|
|
887
896
|
</div>
|
|
888
897
|
|
|
889
|
-
<p style={{ margin: 0, fontSize: 12.5, color: '
|
|
898
|
+
<p style={{ margin: 0, fontSize: 12.5, color: 'var(--studio-text-secondary)', lineHeight: 1.55 }}>
|
|
890
899
|
Describe the structural or stateful change you want for{' '}
|
|
891
|
-
<code style={{ color: '#
|
|
892
|
-
<code className="tabular-nums" style={{ color: '#
|
|
900
|
+
<code style={{ color: '#0284C7', fontFamily: 'var(--font-mono)' }}><{inspected.tagName}></code> in{' '}
|
|
901
|
+
<code className="tabular-nums" style={{ color: '#6366F1', fontFamily: 'var(--font-mono)' }}>
|
|
893
902
|
{inspected.file}:{inspected.line}
|
|
894
903
|
</code>
|
|
895
904
|
.
|
|
@@ -906,7 +915,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
906
915
|
border: '1px solid var(--studio-border-subtle)',
|
|
907
916
|
borderRadius: 8,
|
|
908
917
|
padding: 11,
|
|
909
|
-
color: '
|
|
918
|
+
color: 'var(--studio-text-primary)',
|
|
910
919
|
fontSize: 12.5,
|
|
911
920
|
fontFamily: 'var(--font-sans)',
|
|
912
921
|
}}
|
|
@@ -923,7 +932,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
923
932
|
overflowY: 'auto',
|
|
924
933
|
fontSize: 11,
|
|
925
934
|
fontFamily: 'var(--font-mono)',
|
|
926
|
-
color: '
|
|
935
|
+
color: 'var(--studio-text-secondary)',
|
|
927
936
|
whiteSpace: 'pre-wrap',
|
|
928
937
|
}}
|
|
929
938
|
>
|
|
@@ -940,7 +949,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
940
949
|
borderRadius: 7,
|
|
941
950
|
border: '1px solid var(--studio-border-subtle)',
|
|
942
951
|
backgroundColor: 'var(--studio-surface-2)',
|
|
943
|
-
color: '
|
|
952
|
+
color: 'var(--studio-text-secondary)',
|
|
944
953
|
fontSize: 12,
|
|
945
954
|
fontWeight: 600,
|
|
946
955
|
cursor: 'pointer',
|
|
@@ -960,7 +969,7 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = ({
|
|
|
960
969
|
borderRadius: 7,
|
|
961
970
|
border: '1px solid rgba(56, 189, 248, 0.45)',
|
|
962
971
|
backgroundColor: 'var(--studio-surface-3)',
|
|
963
|
-
color: '
|
|
972
|
+
color: 'var(--studio-text-primary)',
|
|
964
973
|
fontSize: 12,
|
|
965
974
|
fontWeight: 600,
|
|
966
975
|
cursor: 'pointer',
|
|
@@ -29,6 +29,7 @@ interface ScreenArtboardProps {
|
|
|
29
29
|
mode: CanvasMode;
|
|
30
30
|
isActiveScreen: boolean;
|
|
31
31
|
isHighlightedScreen: boolean;
|
|
32
|
+
isCanvasMoving?: boolean;
|
|
32
33
|
selectedElement: SelectedElementRef | null;
|
|
33
34
|
inspectedElement: InspectedElement | null;
|
|
34
35
|
onSelectElement: (ref: SelectedElementRef) => void;
|
|
@@ -83,7 +84,7 @@ function escapeAstPathSelector(astPath: string): string {
|
|
|
83
84
|
: astPath.replace(/["\\]/g, '\\$&');
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
87
|
+
export const ScreenArtboard: React.FC<ScreenArtboardProps> = React.memo(({
|
|
87
88
|
screen,
|
|
88
89
|
ScreenComponent,
|
|
89
90
|
position,
|
|
@@ -91,6 +92,7 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
91
92
|
mode,
|
|
92
93
|
isActiveScreen,
|
|
93
94
|
isHighlightedScreen,
|
|
95
|
+
isCanvasMoving = false,
|
|
94
96
|
selectedElement,
|
|
95
97
|
inspectedElement,
|
|
96
98
|
onSelectElement,
|
|
@@ -119,6 +121,8 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
119
121
|
// Inline Double-Click Text Editing State
|
|
120
122
|
const [isEditingInlineText, setIsEditingInlineText] = useState<boolean>(false);
|
|
121
123
|
const [inlineTextDraft, setInlineTextDraft] = useState<string>('');
|
|
124
|
+
const initialInlineTextRef = useRef<string>('');
|
|
125
|
+
const prevSelectionKeyRef = useRef<string | null>(null);
|
|
122
126
|
|
|
123
127
|
const isSelectedOnThisScreen =
|
|
124
128
|
Boolean(selectedElement) && selectedElement?.screenId === screen.id;
|
|
@@ -159,9 +163,21 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
159
163
|
[zoom]
|
|
160
164
|
);
|
|
161
165
|
|
|
166
|
+
// Only close inline text editor when the selected element identity (screenId:astPath) actually changes
|
|
167
|
+
const selectionIdentityKey =
|
|
168
|
+
isSelectedOnThisScreen && selectedElement
|
|
169
|
+
? `${selectedElement.screenId}:${selectedElement.astPath}`
|
|
170
|
+
: null;
|
|
171
|
+
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (prevSelectionKeyRef.current !== selectionIdentityKey) {
|
|
174
|
+
prevSelectionKeyRef.current = selectionIdentityKey;
|
|
175
|
+
setIsEditingInlineText(false);
|
|
176
|
+
}
|
|
177
|
+
}, [selectionIdentityKey]);
|
|
178
|
+
|
|
162
179
|
// Keep selected element bounding box updated when screen re-renders or styles change
|
|
163
180
|
useEffect(() => {
|
|
164
|
-
setIsEditingInlineText(false);
|
|
165
181
|
if (!isSelectedOnThisScreen) {
|
|
166
182
|
setSelectedBox(null);
|
|
167
183
|
return;
|
|
@@ -196,19 +212,44 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
196
212
|
}
|
|
197
213
|
}, [isEditingInlineText]);
|
|
198
214
|
|
|
199
|
-
const startInlineTextEdit = useCallback(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
215
|
+
const startInlineTextEdit = useCallback(
|
|
216
|
+
(explicitDomEl?: HTMLElement | null) => {
|
|
217
|
+
const domEl = explicitDomEl ?? getSelectedDomNode();
|
|
218
|
+
if (!domEl) return;
|
|
219
|
+
|
|
220
|
+
const domTag = domEl.dataset.agyTag || '';
|
|
221
|
+
const isDomTextNode =
|
|
222
|
+
domTag === 'Text' ||
|
|
223
|
+
domTag.endsWith('.Text') ||
|
|
224
|
+
(domEl.querySelectorAll('[data-agy-ast-path]').length === 0 &&
|
|
225
|
+
Boolean(domEl.textContent?.trim()));
|
|
226
|
+
|
|
227
|
+
if (activeInspected && activeInspected.astPath === domEl.dataset.agyAstPath) {
|
|
228
|
+
if (!activeInspected.isTextEditable && !isDomTextNode) return;
|
|
229
|
+
const initialVal = activeInspected.textContent ?? domEl.textContent?.trim() ?? '';
|
|
230
|
+
initialInlineTextRef.current = initialVal;
|
|
231
|
+
setInlineTextDraft(initialVal);
|
|
232
|
+
setIsEditingInlineText(true);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!isDomTextNode) return;
|
|
237
|
+
const initialVal = domEl.textContent?.replace(/\s+/g, ' ').trim() ?? '';
|
|
238
|
+
initialInlineTextRef.current = initialVal;
|
|
239
|
+
setInlineTextDraft(initialVal);
|
|
240
|
+
setIsEditingInlineText(true);
|
|
241
|
+
},
|
|
242
|
+
[activeInspected, getSelectedDomNode]
|
|
243
|
+
);
|
|
204
244
|
|
|
205
245
|
const commitInlineTextEdit = useCallback(() => {
|
|
206
|
-
if (!isEditingInlineText || !selectedElement
|
|
246
|
+
if (!isEditingInlineText || !selectedElement) {
|
|
207
247
|
setIsEditingInlineText(false);
|
|
208
248
|
return;
|
|
209
249
|
}
|
|
210
250
|
setIsEditingInlineText(false);
|
|
211
|
-
|
|
251
|
+
const baselineText = activeInspected?.textContent ?? initialInlineTextRef.current;
|
|
252
|
+
if (inlineTextDraft !== baselineText) {
|
|
212
253
|
void onPatchElement({
|
|
213
254
|
file: selectedElement.file,
|
|
214
255
|
astPath: selectedElement.astPath,
|
|
@@ -693,8 +734,14 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
693
734
|
};
|
|
694
735
|
|
|
695
736
|
// Inspect Mode pointer handlers
|
|
737
|
+
useEffect(() => {
|
|
738
|
+
if (isCanvasMoving) {
|
|
739
|
+
setHoverBox(null);
|
|
740
|
+
}
|
|
741
|
+
}, [isCanvasMoving]);
|
|
742
|
+
|
|
696
743
|
const handlePointerMoveCapture = (e: React.PointerEvent<HTMLDivElement>) => {
|
|
697
|
-
if (mode !== 'inspect' || activeGizmoAction) return;
|
|
744
|
+
if (mode !== 'inspect' || activeGizmoAction || isCanvasMoving || e.buttons !== 0) return;
|
|
698
745
|
const targetEl = findNearestAgyElement(e.target, viewportRef.current);
|
|
699
746
|
if (!targetEl) {
|
|
700
747
|
setHoverBox(null);
|
|
@@ -722,14 +769,28 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
722
769
|
}
|
|
723
770
|
};
|
|
724
771
|
|
|
772
|
+
const resolveTextTargetForDoubleClick = (rawTarget: HTMLElement): HTMLElement => {
|
|
773
|
+
const tag = rawTarget.dataset.agyTag || '';
|
|
774
|
+
if (tag === 'Text' || tag.endsWith('.Text')) {
|
|
775
|
+
return rawTarget;
|
|
776
|
+
}
|
|
777
|
+
const childTextNodes = rawTarget.querySelectorAll<HTMLElement>('[data-agy-tag="Text"]');
|
|
778
|
+
if (childTextNodes.length === 1) {
|
|
779
|
+
return childTextNodes[0];
|
|
780
|
+
}
|
|
781
|
+
return rawTarget;
|
|
782
|
+
};
|
|
783
|
+
|
|
725
784
|
const handleClickCapture = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
726
785
|
if (mode !== 'inspect' || isEditingInlineText) return;
|
|
727
786
|
e.stopPropagation();
|
|
728
787
|
e.preventDefault();
|
|
729
788
|
|
|
730
789
|
onSelectScreen(screen.id);
|
|
731
|
-
const
|
|
732
|
-
if (!
|
|
790
|
+
const rawTargetEl = findNearestAgyElement(e.target, viewportRef.current);
|
|
791
|
+
if (!rawTargetEl) return;
|
|
792
|
+
|
|
793
|
+
const targetEl = e.detail >= 2 ? resolveTextTargetForDoubleClick(rawTargetEl) : rawTargetEl;
|
|
733
794
|
|
|
734
795
|
const file = targetEl.dataset.agyFile || screen.filePath;
|
|
735
796
|
const astPath = targetEl.dataset.agyAstPath || '0';
|
|
@@ -737,34 +798,42 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
737
798
|
const col = Number.parseInt(targetEl.dataset.agyCol || '0', 10);
|
|
738
799
|
const tagName = targetEl.dataset.agyTag || 'View';
|
|
739
800
|
|
|
801
|
+
const isAlreadySelected =
|
|
802
|
+
selectedElement?.screenId === screen.id && selectedElement?.astPath === astPath;
|
|
803
|
+
|
|
740
804
|
setSelectedBox(computeRelativeBox(targetEl));
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
805
|
+
if (!isAlreadySelected) {
|
|
806
|
+
onSelectElement({
|
|
807
|
+
screenId: screen.id,
|
|
808
|
+
file,
|
|
809
|
+
astPath,
|
|
810
|
+
line,
|
|
811
|
+
col,
|
|
812
|
+
tagName,
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (e.detail >= 2) {
|
|
817
|
+
startInlineTextEdit(targetEl);
|
|
818
|
+
}
|
|
749
819
|
};
|
|
750
820
|
|
|
751
821
|
const handleDoubleClickCapture = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
752
822
|
if (mode !== 'inspect') return;
|
|
753
|
-
const
|
|
754
|
-
if (!
|
|
823
|
+
const rawTargetEl = findNearestAgyElement(e.target, viewportRef.current);
|
|
824
|
+
if (!rawTargetEl) return;
|
|
755
825
|
|
|
756
826
|
e.stopPropagation();
|
|
757
827
|
e.preventDefault();
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
}
|
|
828
|
+
const targetEl = resolveTextTargetForDoubleClick(rawTargetEl);
|
|
829
|
+
startInlineTextEdit(targetEl);
|
|
761
830
|
};
|
|
762
831
|
|
|
763
832
|
const borderColor = isHighlightedScreen
|
|
764
833
|
? '#10B981'
|
|
765
834
|
: isActiveScreen
|
|
766
835
|
? '#38BDF8'
|
|
767
|
-
: '
|
|
836
|
+
: 'var(--studio-border-strong)';
|
|
768
837
|
|
|
769
838
|
const toolbarAbove = !selectedBox || selectedBox.top >= 30;
|
|
770
839
|
|
|
@@ -796,7 +865,7 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
796
865
|
justifyContent: 'space-between',
|
|
797
866
|
padding: '0 4px',
|
|
798
867
|
cursor: isDraggingHeader ? 'grabbing' : 'grab',
|
|
799
|
-
color: isActiveScreen ? '#
|
|
868
|
+
color: isActiveScreen ? '#0284C7' : 'var(--studio-text-secondary)',
|
|
800
869
|
}}
|
|
801
870
|
>
|
|
802
871
|
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
|
|
@@ -806,7 +875,7 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
806
875
|
fontSize: 13,
|
|
807
876
|
fontWeight: 600,
|
|
808
877
|
letterSpacing: '-0.015em',
|
|
809
|
-
color: isActiveScreen ? '
|
|
878
|
+
color: isActiveScreen ? 'var(--studio-text-primary)' : 'var(--studio-text-secondary)',
|
|
810
879
|
}}
|
|
811
880
|
>
|
|
812
881
|
{screen.name}
|
|
@@ -815,7 +884,7 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
815
884
|
style={{
|
|
816
885
|
fontSize: 11,
|
|
817
886
|
fontFamily: 'var(--font-mono)',
|
|
818
|
-
color: '
|
|
887
|
+
color: 'var(--studio-text-muted)',
|
|
819
888
|
}}
|
|
820
889
|
>
|
|
821
890
|
/{screen.routeName}
|
|
@@ -828,7 +897,7 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
828
897
|
style={{
|
|
829
898
|
fontSize: 10.5,
|
|
830
899
|
fontFamily: 'var(--font-mono)',
|
|
831
|
-
color: '
|
|
900
|
+
color: 'var(--studio-text-muted)',
|
|
832
901
|
}}
|
|
833
902
|
>
|
|
834
903
|
{position.width} × {position.height}
|
|
@@ -850,7 +919,7 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
850
919
|
borderRadius: 5,
|
|
851
920
|
border: '1px solid var(--studio-border-subtle)',
|
|
852
921
|
backgroundColor: 'var(--studio-surface-2)',
|
|
853
|
-
color: '
|
|
922
|
+
color: 'var(--studio-text-secondary)',
|
|
854
923
|
cursor: 'pointer',
|
|
855
924
|
}}
|
|
856
925
|
>
|
|
@@ -931,6 +1000,12 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
931
1000
|
flexDirection: 'column',
|
|
932
1001
|
height: '100%',
|
|
933
1002
|
width: '100%',
|
|
1003
|
+
minHeight: 0,
|
|
1004
|
+
minWidth: 0,
|
|
1005
|
+
backgroundColor: '#FFFFFF',
|
|
1006
|
+
color: '#0F172A',
|
|
1007
|
+
overflowY: 'auto',
|
|
1008
|
+
overflowX: 'hidden',
|
|
934
1009
|
}}
|
|
935
1010
|
>
|
|
936
1011
|
<ScreenComponent />
|
|
@@ -1413,4 +1488,4 @@ export const ScreenArtboard: React.FC<ScreenArtboardProps> = ({
|
|
|
1413
1488
|
</div>
|
|
1414
1489
|
</div>
|
|
1415
1490
|
);
|
|
1416
|
-
};
|
|
1491
|
+
});
|