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.
@@ -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="#0B0D12"
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: isHighlighted ? '#064E3B' : '#131724',
176
- border: `1px solid ${isHighlighted ? '#10B981' : '#312E81'}`,
177
- color: isHighlighted ? '#A7F3D0' : '#C7D2FE',
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.45)',
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: '#F8FAFC',
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: '#38BDF8', fontFamily: 'var(--font-mono)' }}>393 × 852</code>{' '}
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: '#E2E8F0' }}>StyleSheet.create</strong> rules or{' '}
244
- <strong style={{ color: '#E2E8F0' }}>static copy</strong> directly in the{' '}
245
- <code style={{ color: '#A5B4FC', fontFamily: 'var(--font-mono)' }}>.tsx</code> AST.
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 &amp; SYNC GOVERNANCE
259
259
  </div>
260
260
  <div style={{ fontSize: 12, color: 'var(--studio-text-secondary)', lineHeight: 1.5 }}>
261
- • <strong style={{ color: '#CBD5E1' }}>Tier 1 (Surgical AST)</strong>: Instant write-back to colors, typography, spacing, and copy.
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: '#CBD5E1' }}>Tier 2 (Antigravity Bridge)</strong>: Dispatch structural refactors with exact file &amp; AST path coordinates.
264
+ • <strong style={{ color: 'var(--studio-text-primary)' }}>Tier 2 (Antigravity Bridge)</strong>: Dispatch structural refactors with exact file &amp; AST path coordinates.
265
265
  </div>
266
266
  <div style={{ fontSize: 12, color: 'var(--studio-text-secondary)', lineHeight: 1.5 }}>
267
- • <strong style={{ color: '#CBD5E1' }}>Impeccable &amp; Taste Guardrails</strong>: Governed by{' '}
268
- <code style={{ fontFamily: 'var(--font-mono)', color: '#38BDF8' }}>PRODUCT.md</code> &amp;{' '}
269
- <code style={{ fontFamily: 'var(--font-mono)', color: '#38BDF8' }}>DESIGN.md</code>.
267
+ • <strong style={{ color: 'var(--studio-text-primary)' }}>Impeccable &amp; Taste Guardrails</strong>: Governed by{' '}
268
+ <code style={{ fontFamily: 'var(--font-mono)', color: '#0284C7' }}>PRODUCT.md</code> &amp;{' '}
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: '#34D399' }}>Zelune Ready (mcp/server.ts)</span>
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: 'rgba(99, 102, 241, 0.18)',
314
- border: '1px solid rgba(99, 102, 241, 0.4)',
315
- color: '#C7D2FE',
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: 'rgba(16, 185, 129, 0.14)',
329
- border: '1px solid rgba(16, 185, 129, 0.35)',
330
- color: '#A7F3D0',
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: 'rgba(30, 27, 75, 0.65)',
400
- borderBottom: '1px solid rgba(99, 102, 241, 0.3)',
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: '#A5B4FC',
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: '#34D399' }}>● AST Sync</span>
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: 'rgba(6, 78, 59, 0.45)',
427
- border: '1px solid rgba(16, 185, 129, 0.45)',
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 style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: '#D1FAE5' }}>
435
- <Navigation size={13} color="#34D399" />
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: '#CBD5E1',
497
+ color: 'var(--studio-text-secondary)',
489
498
  lineHeight: 1.5,
490
499
  }}
491
500
  >
492
- Contains dynamic JSX expression (<code style={{ color: '#38BDF8' }}>{'{...}'}</code>). Protected from static overwrite—use <strong>Ask Antigravity</strong> below to refactor expression logic.
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: '#F8FAFC',
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: '#CBD5E1' }}>Background Color</label>
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: '#F8FAFC',
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 rgba(255, 255, 255, 0.14)',
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: '#CBD5E1' }}>Text Color</label>
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: '#F8FAFC',
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: '#F8FAFC',
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: '#F8FAFC',
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: '#F8FAFC',
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: '#F8FAFC',
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.6)',
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: '#CBD5E1',
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: '#F8FAFC',
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.85)',
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: '#94A3B8',
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: '#94A3B8', lineHeight: 1.55 }}>
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: '#38BDF8', fontFamily: 'var(--font-mono)' }}>&lt;{inspected.tagName}&gt;</code> in{' '}
892
- <code className="tabular-nums" style={{ color: '#A5B4FC', fontFamily: 'var(--font-mono)' }}>
900
+ <code style={{ color: '#0284C7', fontFamily: 'var(--font-mono)' }}>&lt;{inspected.tagName}&gt;</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: '#F8FAFC',
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: '#94A3B8',
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: '#CBD5E1',
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: '#F8FAFC',
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
- if (!activeInspected || !activeInspected.isTextEditable) return;
201
- setInlineTextDraft(activeInspected.textContent ?? '');
202
- setIsEditingInlineText(true);
203
- }, [activeInspected]);
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 || !activeInspected) {
246
+ if (!isEditingInlineText || !selectedElement) {
207
247
  setIsEditingInlineText(false);
208
248
  return;
209
249
  }
210
250
  setIsEditingInlineText(false);
211
- if (inlineTextDraft !== (activeInspected.textContent ?? '')) {
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 targetEl = findNearestAgyElement(e.target, viewportRef.current);
732
- if (!targetEl) return;
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
- onSelectElement({
742
- screenId: screen.id,
743
- file,
744
- astPath,
745
- line,
746
- col,
747
- tagName,
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 targetEl = findNearestAgyElement(e.target, viewportRef.current);
754
- if (!targetEl) return;
823
+ const rawTargetEl = findNearestAgyElement(e.target, viewportRef.current);
824
+ if (!rawTargetEl) return;
755
825
 
756
826
  e.stopPropagation();
757
827
  e.preventDefault();
758
- if (activeInspected?.isTextEditable) {
759
- startInlineTextEdit();
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
- : 'rgba(255, 255, 255, 0.09)';
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 ? '#38BDF8' : '#94A3B8',
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 ? '#F8FAFC' : '#CBD5E1',
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: '#64748B',
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: '#64748B',
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: '#94A3B8',
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
+ });