zelune 1.6.2 → 1.6.4

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/bin/cli.mjs +13 -54
  3. package/dist/assets/CheckoutScreen-6IdrpKgm.js +229 -0
  4. package/dist/assets/HomeScreen-Dxe9L4I2.js +316 -0
  5. package/dist/assets/LoginScreen-Dp1awI4F.js +189 -0
  6. package/dist/assets/ProductDetailScreen-Wa5_nlRP.js +196 -0
  7. package/dist/assets/ProfileScreen-riAU6vVh.js +185 -0
  8. package/dist/assets/index-hEzd4JVy.js +44697 -0
  9. package/{index.html → dist/index.html} +3 -7
  10. package/mcp/server.mjs +1370 -34
  11. package/package.json +10 -17
  12. package/plugin.json +1 -1
  13. package/socket.yml +11 -0
  14. package/mcp/server.ts +0 -446
  15. package/plugins/viteReactNativeFallbackPlugin.ts +0 -519
  16. package/plugins/viteRnSourceInject.ts +0 -117
  17. package/plugins/viteVirtualScreensPlugin.ts +0 -216
  18. package/src/canvas/CanvasApp.tsx +0 -1655
  19. package/src/canvas/FlowArrowsLayer.tsx +0 -239
  20. package/src/canvas/InspectorPanel.tsx +0 -1494
  21. package/src/canvas/ScreenArtboard.tsx +0 -1491
  22. package/src/canvas/types.ts +0 -23
  23. package/src/main.tsx +0 -80
  24. package/src/runtime/MockNavigationContext.tsx +0 -1795
  25. package/src/runtime/ScreenErrorBoundary.tsx +0 -86
  26. package/src/runtime/nativewindShim.ts +0 -997
  27. package/src/runtime/reactNativeShim.ts +0 -240
  28. package/src/screens/CheckoutScreen.tsx +0 -229
  29. package/src/screens/HomeScreen.tsx +0 -288
  30. package/src/screens/LoginScreen.tsx +0 -176
  31. package/src/screens/ProductDetailScreen.tsx +0 -207
  32. package/src/screens/ProfileScreen.tsx +0 -172
  33. package/src/screens/index.ts +0 -1
  34. package/src/server/apiMiddleware.ts +0 -375
  35. package/src/server/astEngine.ts +0 -2553
  36. package/src/vite-env.d.ts +0 -7
  37. package/tsconfig.json +0 -23
  38. package/vite.config.ts +0 -340
  39. /package/{src/styles.css → dist/assets/index-C7nrS82N.css} +0 -0
@@ -1,1494 +0,0 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- import {
3
- Code2,
4
- Sparkles,
5
- Type,
6
- Palette,
7
- Box,
8
- Navigation,
9
- Copy,
10
- Check,
11
- Sliders,
12
- X,
13
- PanelRightClose,
14
- Plus,
15
- Trash2,
16
- ArrowUp,
17
- ArrowDown,
18
- LayoutTemplate,
19
- MousePointerClick,
20
- FormInput,
21
- Tag,
22
- Minus,
23
- Columns,
24
- Heading,
25
- AlignLeft,
26
- } from 'lucide-react';
27
- import type {
28
- InspectedElement,
29
- PatchPayload,
30
- ReorderPayload,
31
- InsertElementPayload,
32
- InsertElementPreset,
33
- DuplicateElementPayload,
34
- DeleteElementPayload,
35
- ScreenMetadata,
36
- StyleValue,
37
- } from '../server/astEngine';
38
-
39
- interface InspectorPanelProps {
40
- inspected: InspectedElement | null;
41
- screens?: ScreenMetadata[];
42
- activeScreenId?: string | null;
43
- isOpen: boolean;
44
- isLoading: boolean;
45
- isSaving: boolean;
46
- statusMessage: string | null;
47
- onPatch: (payload: PatchPayload) => Promise<void>;
48
- onInsert?: (payload: InsertElementPayload) => Promise<void>;
49
- onDuplicate?: (payload: DuplicateElementPayload) => Promise<void>;
50
- onDelete?: (payload: DeleteElementPayload) => Promise<void>;
51
- onReorder?: (payload: ReorderPayload) => Promise<void>;
52
- onSelectScreen?: (screenId: string) => void;
53
- onClearSelection: () => void;
54
- onCollapse: () => void;
55
- onJumpToScreen: (routeName: string) => void;
56
- }
57
-
58
- const INSERT_PRESETS: Array<{
59
- id: InsertElementPreset;
60
- title: string;
61
- subtitle: string;
62
- tagBadge: string;
63
- icon: React.ComponentType<{ size?: number; color?: string }>;
64
- }> = [
65
- {
66
- id: 'button',
67
- title: 'Button',
68
- subtitle: 'CTA + Optional Flow',
69
- tagBadge: '<TouchableOpacity>',
70
- icon: MousePointerClick,
71
- },
72
- {
73
- id: 'section',
74
- title: 'Section Card',
75
- subtitle: 'Title + Body Card',
76
- tagBadge: '<View>',
77
- icon: LayoutTemplate,
78
- },
79
- {
80
- id: 'row',
81
- title: 'Row Item',
82
- subtitle: 'Horizontal Flex Row',
83
- tagBadge: '<View row>',
84
- icon: Columns,
85
- },
86
- {
87
- id: 'heading',
88
- title: 'Heading',
89
- subtitle: 'Bold Title Copy',
90
- tagBadge: '<Text>',
91
- icon: Heading,
92
- },
93
- {
94
- id: 'text',
95
- title: 'Paragraph',
96
- subtitle: 'Supporting Copy',
97
- tagBadge: '<Text>',
98
- icon: AlignLeft,
99
- },
100
- {
101
- id: 'input',
102
- title: 'Text Input',
103
- subtitle: 'Form Input Field',
104
- tagBadge: '<TextInput>',
105
- icon: FormInput,
106
- },
107
- {
108
- id: 'badge',
109
- title: 'Badge Pill',
110
- subtitle: 'Status Tag Pill',
111
- tagBadge: '<View>',
112
- icon: Tag,
113
- },
114
- {
115
- id: 'divider',
116
- title: 'Divider',
117
- subtitle: '1px Hairline Rule',
118
- tagBadge: '<View>',
119
- icon: Minus,
120
- },
121
- ];
122
-
123
- const COLOR_SWATCHES = [
124
- '#0A0B0E',
125
- '#13161E',
126
- '#1E2433',
127
- '#38BDF8',
128
- '#6366F1',
129
- '#10B981',
130
- '#F59E0B',
131
- '#F43F5E',
132
- '#F8FAFC',
133
- ];
134
-
135
- const FONT_WEIGHTS = ['400', '500', '600', '700', '800'];
136
-
137
- export const InspectorPanel: React.FC<InspectorPanelProps> = ({
138
- inspected,
139
- screens = [],
140
- activeScreenId = null,
141
- isOpen,
142
- isLoading,
143
- isSaving,
144
- statusMessage,
145
- onPatch,
146
- onInsert,
147
- onDuplicate,
148
- onDelete,
149
- onReorder,
150
- onSelectScreen,
151
- onClearSelection,
152
- onCollapse,
153
- onJumpToScreen,
154
- }) => {
155
- const [localText, setLocalText] = useState('');
156
- const [localStyles, setLocalStyles] = useState<Record<string, StyleValue>>({});
157
- const [agentInstruction, setAgentInstruction] = useState('');
158
- const [copiedPrompt, setCopiedPrompt] = useState(false);
159
-
160
- // Add Component / Section builder state
161
- const [showInsertDrawer, setShowInsertDrawer] = useState(false);
162
- const [insertPlacement, setInsertPlacement] = useState<'after' | 'before' | 'inside'>('after');
163
- const [insertLabel, setInsertLabel] = useState('');
164
- const [insertNavigateTo, setInsertNavigateTo] = useState('');
165
-
166
- const dialogRef = useRef<HTMLDialogElement | null>(null);
167
- const debounceTimerRef = useRef<number | null>(null);
168
-
169
- useEffect(() => {
170
- const handleOpenInsert = () => {
171
- setShowInsertDrawer(true);
172
- };
173
- window.addEventListener('zelune:open-insert-palette', handleOpenInsert);
174
- return () => window.removeEventListener('zelune:open-insert-palette', handleOpenInsert);
175
- }, []);
176
-
177
- const activeScreenObj =
178
- screens.find((s) => s.id === activeScreenId) ?? screens[0] ?? null;
179
-
180
- const handleTriggerInsert = (preset: InsertElementPreset) => {
181
- if (!onInsert) return;
182
- const targetFile = inspected?.file ?? activeScreenObj?.filePath;
183
- if (!targetFile) return;
184
-
185
- void onInsert({
186
- file: targetFile,
187
- astPath: inspected?.astPath,
188
- placement: inspected ? insertPlacement : 'inside',
189
- preset,
190
- label: insertLabel.trim() || undefined,
191
- navigateTo: preset === 'button' && insertNavigateTo ? insertNavigateTo : undefined,
192
- });
193
- setInsertLabel('');
194
- };
195
-
196
- const renderInsertPaletteContent = (isCompactDrawer: boolean) => (
197
- <div
198
- style={{
199
- display: 'flex',
200
- flexDirection: 'column',
201
- gap: 10,
202
- padding: isCompactDrawer ? 12 : 0,
203
- backgroundColor: isCompactDrawer ? 'var(--studio-surface-2)' : 'transparent',
204
- borderRadius: isCompactDrawer ? 9 : 0,
205
- border: isCompactDrawer ? '1px solid rgba(56, 189, 248, 0.3)' : 'none',
206
- }}
207
- >
208
- {/* Target Screen or Placement Selector */}
209
- {inspected ? (
210
- <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
211
- <label
212
- style={{
213
- fontSize: 10,
214
- fontFamily: 'var(--font-mono)',
215
- fontWeight: 600,
216
- color: 'var(--studio-text-muted)',
217
- letterSpacing: '0.05em',
218
- }}
219
- >
220
- PLACEMENT RELATIVE TO &lt;{inspected.tagName}&gt;
221
- </label>
222
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }}>
223
- {(['after', 'before', 'inside'] as const).map((place) => (
224
- <button
225
- key={place}
226
- type="button"
227
- className="zelune-interactive"
228
- onClick={() => setInsertPlacement(place)}
229
- style={{
230
- padding: '5px 6px',
231
- borderRadius: 6,
232
- border:
233
- insertPlacement === place
234
- ? '1px solid #38BDF8'
235
- : '1px solid var(--studio-border-subtle)',
236
- backgroundColor:
237
- insertPlacement === place
238
- ? 'rgba(56, 189, 248, 0.14)'
239
- : 'var(--studio-surface-1)',
240
- color:
241
- insertPlacement === place
242
- ? 'var(--studio-text-primary)'
243
- : 'var(--studio-text-secondary)',
244
- fontSize: 11,
245
- fontWeight: 600,
246
- fontFamily: 'var(--font-mono)',
247
- textTransform: 'capitalize',
248
- cursor: 'pointer',
249
- }}
250
- >
251
- {place}
252
- </button>
253
- ))}
254
- </div>
255
- </div>
256
- ) : (
257
- screens.length > 0 && (
258
- <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
259
- <label
260
- style={{
261
- fontSize: 10,
262
- fontFamily: 'var(--font-mono)',
263
- fontWeight: 600,
264
- color: 'var(--studio-text-muted)',
265
- letterSpacing: '0.05em',
266
- }}
267
- >
268
- TARGET ARTBOARD SCREEN
269
- </label>
270
- <select
271
- value={activeScreenObj?.id ?? ''}
272
- onChange={(e) => onSelectScreen?.(e.target.value)}
273
- style={{
274
- padding: '7px 9px',
275
- borderRadius: 7,
276
- border: '1px solid var(--studio-border-subtle)',
277
- backgroundColor: 'var(--studio-surface-2)',
278
- color: 'var(--studio-text-primary)',
279
- fontSize: 12,
280
- fontFamily: 'var(--font-mono)',
281
- }}
282
- >
283
- {screens.map((s) => (
284
- <option key={s.id} value={s.id}>
285
- {s.name} ({s.filePath})
286
- </option>
287
- ))}
288
- </select>
289
- </div>
290
- )
291
- )}
292
-
293
- {/* Optional Custom Label & Navigation Link */}
294
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
295
- <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
296
- <label
297
- style={{
298
- fontSize: 10,
299
- fontFamily: 'var(--font-mono)',
300
- color: 'var(--studio-text-muted)',
301
- }}
302
- >
303
- CUSTOM LABEL / COPY
304
- </label>
305
- <input
306
- type="text"
307
- value={insertLabel}
308
- onChange={(e) => setInsertLabel(e.target.value)}
309
- placeholder="e.g. Book Now"
310
- style={{
311
- padding: '6px 8px',
312
- borderRadius: 6,
313
- border: '1px solid var(--studio-border-subtle)',
314
- backgroundColor: 'var(--studio-surface-1)',
315
- color: 'var(--studio-text-primary)',
316
- fontSize: 11.5,
317
- }}
318
- />
319
- </div>
320
-
321
- <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
322
- <label
323
- style={{
324
- fontSize: 10,
325
- fontFamily: 'var(--font-mono)',
326
- color: 'var(--studio-text-muted)',
327
- }}
328
- >
329
- BUTTON FLOW LINK
330
- </label>
331
- <select
332
- value={insertNavigateTo}
333
- onChange={(e) => setInsertNavigateTo(e.target.value)}
334
- style={{
335
- padding: '6px 7px',
336
- borderRadius: 6,
337
- border: '1px solid var(--studio-border-subtle)',
338
- backgroundColor: 'var(--studio-surface-1)',
339
- color: 'var(--studio-text-primary)',
340
- fontSize: 11.5,
341
- fontFamily: 'var(--font-mono)',
342
- }}
343
- >
344
- <option value="">None</option>
345
- {screens.map((s) => (
346
- <option key={s.id} value={s.id}>
347
- → {s.id}
348
- </option>
349
- ))}
350
- </select>
351
- </div>
352
- </div>
353
-
354
- {/* 2-Column Component Preset Grid */}
355
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 7 }}>
356
- {INSERT_PRESETS.map((preset) => {
357
- const IconComp = preset.icon;
358
- return (
359
- <button
360
- key={preset.id}
361
- type="button"
362
- className="zelune-interactive"
363
- disabled={isSaving}
364
- onClick={() => handleTriggerInsert(preset.id)}
365
- style={{
366
- display: 'flex',
367
- flexDirection: 'column',
368
- alignItems: 'flex-start',
369
- gap: 4,
370
- padding: '9px 10px',
371
- borderRadius: 8,
372
- border: '1px solid var(--studio-border-subtle)',
373
- backgroundColor: 'var(--studio-surface-1)',
374
- color: 'var(--studio-text-primary)',
375
- cursor: isSaving ? 'wait' : 'pointer',
376
- textAlign: 'left',
377
- }}
378
- >
379
- <div
380
- style={{
381
- display: 'flex',
382
- alignItems: 'center',
383
- justifyContent: 'space-between',
384
- width: '100%',
385
- }}
386
- >
387
- <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, fontWeight: 600 }}>
388
- <IconComp size={13} color="#38BDF8" />
389
- <span>{preset.title}</span>
390
- </span>
391
- <Plus size={11} color="var(--studio-text-muted)" />
392
- </div>
393
- <span style={{ fontSize: 10.5, color: 'var(--studio-text-secondary)' }}>
394
- {preset.subtitle}
395
- </span>
396
- <span
397
- style={{
398
- fontSize: 9.5,
399
- fontFamily: 'var(--font-mono)',
400
- color: '#6366F1',
401
- }}
402
- >
403
- {preset.tagBadge}
404
- </span>
405
- </button>
406
- );
407
- })}
408
- </div>
409
- </div>
410
- );
411
-
412
- useEffect(() => {
413
- if (!inspected) {
414
- setLocalText('');
415
- setLocalStyles({});
416
- return;
417
- }
418
- setLocalText(inspected.textContent ?? '');
419
- setLocalStyles(inspected.styles ?? {});
420
- }, [inspected]);
421
-
422
- // Modern Web Guidance fallback for <dialog closedby="any"> light-dismiss in browsers without native closedBy
423
- useEffect(() => {
424
- const dialog = dialogRef.current;
425
- if (!dialog) return;
426
-
427
- if (!('closedBy' in HTMLDialogElement.prototype)) {
428
- const handleBackdropClick = (event: MouseEvent) => {
429
- if (event.target !== dialog) return;
430
- const rect = dialog.getBoundingClientRect();
431
- const isDialogContent =
432
- rect.top <= event.clientY &&
433
- event.clientY <= rect.top + rect.height &&
434
- rect.left <= event.clientX &&
435
- event.clientX <= rect.left + rect.width;
436
- if (isDialogContent) return;
437
- dialog.close();
438
- };
439
- dialog.addEventListener('click', handleBackdropClick);
440
- return () => dialog.removeEventListener('click', handleBackdropClick);
441
- }
442
- }, [inspected]);
443
-
444
- const schedulePatch = (nextPatch: Omit<PatchPayload, 'file' | 'astPath'>) => {
445
- if (!inspected) return;
446
- if (debounceTimerRef.current) {
447
- window.clearTimeout(debounceTimerRef.current);
448
- }
449
- debounceTimerRef.current = window.setTimeout(() => {
450
- void onPatch({
451
- file: inspected.file,
452
- astPath: inspected.astPath,
453
- line: inspected.line,
454
- ...nextPatch,
455
- });
456
- }, 220);
457
- };
458
-
459
- const handleTextChange = (val: string) => {
460
- setLocalText(val);
461
- schedulePatch({ text: val });
462
- };
463
-
464
- const handleStyleChange = (key: string, rawVal: string | number) => {
465
- const nextStyles = { ...localStyles, [key]: rawVal };
466
- setLocalStyles(nextStyles);
467
- schedulePatch({ styles: { [key]: rawVal } });
468
- };
469
-
470
- const handleNumericStyleChange = (key: string, rawStr: string) => {
471
- if (rawStr.trim() === '') {
472
- const copy = { ...localStyles };
473
- delete copy[key];
474
- setLocalStyles(copy);
475
- schedulePatch({ styles: { [key]: null } });
476
- return;
477
- }
478
- const parsed = Number(rawStr);
479
- if (!Number.isNaN(parsed)) {
480
- handleStyleChange(key, parsed);
481
- }
482
- };
483
-
484
- const buildAntigravityPrompt = () => {
485
- if (!inspected) return '';
486
- return [
487
- `Refactor the <${inspected.tagName}> element in \`${inspected.file}\` (around line ${inspected.line}, AST index ${inspected.astPath}).`,
488
- inspected.styleSheetKey
489
- ? `Associated StyleSheet key: \`styles.${inspected.styleSheetKey}\`.`
490
- : '',
491
- '',
492
- `Current JSX Snippet:`,
493
- '```tsx',
494
- inspected.codeSnippet,
495
- '```',
496
- '',
497
- `Requested Change:`,
498
- agentInstruction.trim() || 'Update this component layout and styling per DESIGN.md.',
499
- ]
500
- .filter(Boolean)
501
- .join('\n');
502
- };
503
-
504
- const handleCopyPrompt = async () => {
505
- const text = buildAntigravityPrompt();
506
- await navigator.clipboard.writeText(text);
507
- setCopiedPrompt(true);
508
- window.setTimeout(() => setCopiedPrompt(false), 2000);
509
- };
510
-
511
- const targetWidth = inspected ? 340 : 320;
512
-
513
- return (
514
- <aside
515
- aria-hidden={!isOpen}
516
- className="zelune-collapsible-panel"
517
- style={{
518
- width: isOpen ? targetWidth : 0,
519
- opacity: isOpen ? 1 : 0,
520
- pointerEvents: isOpen ? 'auto' : 'none',
521
- backgroundColor: 'var(--studio-surface-1)',
522
- borderLeft: isOpen ? '1px solid var(--studio-border-hairline)' : '0px solid transparent',
523
- display: 'flex',
524
- flexDirection: 'column',
525
- height: '100%',
526
- zIndex: 30,
527
- }}
528
- >
529
- {!inspected ? (
530
- <div
531
- className="zelune-scroller"
532
- style={{
533
- width: 320,
534
- minWidth: 320,
535
- height: '100%',
536
- display: 'flex',
537
- flexDirection: 'column',
538
- justifyContent: 'space-between',
539
- padding: 18,
540
- color: 'var(--studio-text-secondary)',
541
- overflowY: 'auto',
542
- }}
543
- >
544
- <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
545
- <div
546
- style={{
547
- display: 'flex',
548
- alignItems: 'center',
549
- justifyContent: 'space-between',
550
- color: 'var(--studio-text-primary)',
551
- fontWeight: 600,
552
- fontSize: 13.5,
553
- letterSpacing: '-0.01em',
554
- }}
555
- >
556
- <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
557
- <Sliders size={15} color="#38BDF8" />
558
- <span>AST Visual Inspector</span>
559
- </span>
560
- <button
561
- type="button"
562
- className="zelune-interactive"
563
- onClick={onCollapse}
564
- title="Collapse Inspector Panel (])"
565
- style={{
566
- background: 'transparent',
567
- border: '1px solid transparent',
568
- borderRadius: 5,
569
- color: 'var(--studio-text-muted)',
570
- cursor: 'pointer',
571
- padding: 4,
572
- display: 'inline-flex',
573
- alignItems: 'center',
574
- justifyContent: 'center',
575
- }}
576
- >
577
- <PanelRightClose size={14} />
578
- </button>
579
- </div>
580
-
581
- <div
582
- style={{
583
- backgroundColor: 'var(--studio-surface-2)',
584
- border: '1px solid var(--studio-border-hairline)',
585
- borderRadius: 10,
586
- padding: 12,
587
- fontSize: 12,
588
- lineHeight: 1.5,
589
- }}
590
- >
591
- Select any node on an artboard to inspect &amp; live-patch its{' '}
592
- <strong style={{ color: 'var(--studio-text-primary)' }}>StyleSheet</strong> or{' '}
593
- <strong style={{ color: 'var(--studio-text-primary)' }}>copy</strong>, or add new
594
- buttons, sections, and inputs below:
595
- </div>
596
-
597
- {(isSaving || statusMessage) && (
598
- <div
599
- style={{
600
- padding: '7px 12px',
601
- backgroundColor: 'var(--studio-badge-indigo-bg)',
602
- border: '1px solid var(--studio-badge-indigo-border)',
603
- borderRadius: 7,
604
- fontSize: 11,
605
- fontFamily: 'var(--font-mono)',
606
- color: 'var(--studio-badge-indigo-text)',
607
- display: 'flex',
608
- alignItems: 'center',
609
- justifyContent: 'space-between',
610
- }}
611
- >
612
- <span>{isSaving ? 'Writing AST to .tsx...' : statusMessage}</span>
613
- <span style={{ color: 'var(--studio-accent-emerald)', fontWeight: 600 }}>● Sync</span>
614
- </div>
615
- )}
616
-
617
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
618
- <div
619
- style={{
620
- display: 'flex',
621
- alignItems: 'center',
622
- gap: 6,
623
- fontSize: 10.5,
624
- fontFamily: 'var(--font-mono)',
625
- fontWeight: 600,
626
- color: '#38BDF8',
627
- letterSpacing: '0.06em',
628
- }}
629
- >
630
- <Plus size={12} color="#38BDF8" />
631
- <span>ADD ELEMENTS &amp; SECTIONS</span>
632
- </div>
633
- {renderInsertPaletteContent(false)}
634
- </div>
635
- </div>
636
-
637
- <div
638
- style={{
639
- fontSize: 11,
640
- fontFamily: 'var(--font-mono)',
641
- color: 'var(--studio-text-muted)',
642
- borderTop: '1px solid var(--studio-border-hairline)',
643
- paddingTop: 12,
644
- }}
645
- >
646
- MCP Server: <span style={{ color: 'var(--studio-accent-emerald)' }}>Zelune Ready (mcp/server.ts)</span>
647
- </div>
648
- </div>
649
- ) : (
650
- <div
651
- className="zelune-scroller"
652
- style={{
653
- width: 340,
654
- minWidth: 340,
655
- height: '100%',
656
- display: 'flex',
657
- flexDirection: 'column',
658
- overflowY: 'auto',
659
- }}
660
- >
661
- {/* Inspector Header */}
662
- <div
663
- style={{
664
- padding: '12px 16px',
665
- borderBottom: '1px solid var(--studio-border-hairline)',
666
- display: 'flex',
667
- alignItems: 'center',
668
- justifyContent: 'space-between',
669
- backgroundColor: 'var(--studio-surface-2)',
670
- }}
671
- >
672
- <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
673
- <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
674
- <span
675
- style={{
676
- backgroundColor: 'var(--studio-badge-indigo-bg)',
677
- border: '1px solid var(--studio-badge-indigo-border)',
678
- color: 'var(--studio-badge-indigo-text)',
679
- fontSize: 11.5,
680
- fontWeight: 700,
681
- fontFamily: 'var(--font-mono)',
682
- padding: '1px 6px',
683
- borderRadius: 4,
684
- }}
685
- >
686
- &lt;{inspected.tagName}&gt;
687
- </span>
688
- {inspected.styleSheetKey && (
689
- <span
690
- style={{
691
- backgroundColor: 'var(--studio-badge-emerald-bg)',
692
- border: '1px solid var(--studio-badge-emerald-border)',
693
- color: 'var(--studio-badge-emerald-text)',
694
- fontSize: 11,
695
- fontWeight: 600,
696
- fontFamily: 'var(--font-mono)',
697
- padding: '1px 6px',
698
- borderRadius: 4,
699
- }}
700
- >
701
- styles.{inspected.styleSheetKey}
702
- </span>
703
- )}
704
- </div>
705
- <span
706
- className="tabular-nums"
707
- style={{
708
- fontSize: 10.5,
709
- fontFamily: 'var(--font-mono)',
710
- color: 'var(--studio-text-muted)',
711
- marginTop: 1,
712
- }}
713
- >
714
- {inspected.file}:{inspected.line}:{inspected.col}
715
- </span>
716
- </div>
717
-
718
- <div style={{ display: 'flex', alignItems: 'center', gap: 2 }}>
719
- <button
720
- type="button"
721
- className="zelune-interactive"
722
- onClick={onCollapse}
723
- title="Collapse Inspector Panel (])"
724
- style={{
725
- background: 'transparent',
726
- border: '1px solid transparent',
727
- borderRadius: 5,
728
- color: 'var(--studio-text-muted)',
729
- cursor: 'pointer',
730
- padding: 4,
731
- display: 'inline-flex',
732
- alignItems: 'center',
733
- }}
734
- >
735
- <PanelRightClose size={14} />
736
- </button>
737
- <button
738
- type="button"
739
- className="zelune-interactive"
740
- onClick={onClearSelection}
741
- title="Deselect element (Esc)"
742
- style={{
743
- background: 'transparent',
744
- border: '1px solid transparent',
745
- borderRadius: 5,
746
- color: 'var(--studio-text-muted)',
747
- cursor: 'pointer',
748
- padding: 4,
749
- display: 'inline-flex',
750
- alignItems: 'center',
751
- }}
752
- >
753
- <X size={15} />
754
- </button>
755
- </div>
756
- </div>
757
-
758
- {/* Live Save Status Indicator */}
759
- {(isLoading || isSaving || statusMessage) && (
760
- <div
761
- style={{
762
- padding: '6px 16px',
763
- backgroundColor: 'var(--studio-badge-indigo-bg)',
764
- borderBottom: '1px solid var(--studio-badge-indigo-border)',
765
- fontSize: 11,
766
- fontFamily: 'var(--font-mono)',
767
- color: 'var(--studio-badge-indigo-text)',
768
- display: 'flex',
769
- alignItems: 'center',
770
- justifyContent: 'space-between',
771
- }}
772
- >
773
- <span>
774
- {isSaving
775
- ? 'Patching AST & writing .tsx...'
776
- : isLoading
777
- ? 'Inspecting AST node...'
778
- : statusMessage}
779
- </span>
780
- <span style={{ color: 'var(--studio-accent-emerald)', fontWeight: 600 }}>● AST Sync</span>
781
- </div>
782
- )}
783
-
784
- {/* Structural Element Action Bar: + Add Element, Duplicate, Reorder Up/Down, Delete */}
785
- <div
786
- style={{
787
- padding: '8px 14px',
788
- borderBottom: '1px solid var(--studio-border-hairline)',
789
- backgroundColor: 'var(--studio-surface-1)',
790
- display: 'flex',
791
- flexDirection: 'column',
792
- gap: 8,
793
- }}
794
- >
795
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 5 }}>
796
- <button
797
- type="button"
798
- className="zelune-interactive"
799
- onClick={() => setShowInsertDrawer((prev) => !prev)}
800
- style={{
801
- display: 'inline-flex',
802
- alignItems: 'center',
803
- gap: 5,
804
- padding: '5px 9px',
805
- borderRadius: 6,
806
- border: showInsertDrawer
807
- ? '1px solid #38BDF8'
808
- : '1px solid rgba(56, 189, 248, 0.35)',
809
- backgroundColor: showInsertDrawer
810
- ? 'rgba(56, 189, 248, 0.18)'
811
- : 'rgba(56, 189, 248, 0.08)',
812
- color: '#38BDF8',
813
- fontSize: 11,
814
- fontWeight: 600,
815
- cursor: 'pointer',
816
- }}
817
- >
818
- <Plus size={12} />
819
- <span>Add Element</span>
820
- </button>
821
-
822
- <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
823
- <button
824
- type="button"
825
- className="zelune-interactive"
826
- disabled={isSaving || inspected.astPath === '0'}
827
- onClick={() =>
828
- onDuplicate?.({ file: inspected.file, astPath: inspected.astPath })
829
- }
830
- title="Duplicate Selected Element"
831
- style={{
832
- display: 'inline-flex',
833
- alignItems: 'center',
834
- gap: 4,
835
- padding: '5px 7px',
836
- borderRadius: 6,
837
- border: '1px solid var(--studio-border-subtle)',
838
- backgroundColor: 'var(--studio-surface-2)',
839
- color: 'var(--studio-text-secondary)',
840
- fontSize: 11,
841
- cursor: inspected.astPath === '0' ? 'not-allowed' : 'pointer',
842
- }}
843
- >
844
- <Copy size={11} />
845
- <span>Clone</span>
846
- </button>
847
-
848
- <button
849
- type="button"
850
- className="zelune-interactive"
851
- disabled={isSaving || inspected.astPath === '0'}
852
- onClick={() =>
853
- onReorder?.({
854
- file: inspected.file,
855
- astPath: inspected.astPath,
856
- direction: 'up',
857
- })
858
- }
859
- title="Move Element Up"
860
- style={{
861
- padding: '5px 6px',
862
- borderRadius: 6,
863
- border: '1px solid var(--studio-border-subtle)',
864
- backgroundColor: 'var(--studio-surface-2)',
865
- color: 'var(--studio-text-secondary)',
866
- cursor: inspected.astPath === '0' ? 'not-allowed' : 'pointer',
867
- display: 'inline-flex',
868
- alignItems: 'center',
869
- }}
870
- >
871
- <ArrowUp size={12} />
872
- </button>
873
-
874
- <button
875
- type="button"
876
- className="zelune-interactive"
877
- disabled={isSaving || inspected.astPath === '0'}
878
- onClick={() =>
879
- onReorder?.({
880
- file: inspected.file,
881
- astPath: inspected.astPath,
882
- direction: 'down',
883
- })
884
- }
885
- title="Move Element Down"
886
- style={{
887
- padding: '5px 6px',
888
- borderRadius: 6,
889
- border: '1px solid var(--studio-border-subtle)',
890
- backgroundColor: 'var(--studio-surface-2)',
891
- color: 'var(--studio-text-secondary)',
892
- cursor: inspected.astPath === '0' ? 'not-allowed' : 'pointer',
893
- display: 'inline-flex',
894
- alignItems: 'center',
895
- }}
896
- >
897
- <ArrowDown size={12} />
898
- </button>
899
-
900
- <button
901
- type="button"
902
- className="zelune-interactive"
903
- disabled={isSaving || inspected.astPath === '0'}
904
- onClick={() =>
905
- onDelete?.({ file: inspected.file, astPath: inspected.astPath })
906
- }
907
- title="Delete Element from .tsx (Cmd+Z to Undo)"
908
- style={{
909
- padding: '5px 6px',
910
- borderRadius: 6,
911
- border: '1px solid rgba(244, 63, 94, 0.35)',
912
- backgroundColor: 'rgba(244, 63, 94, 0.1)',
913
- color: '#F43F5E',
914
- cursor: inspected.astPath === '0' ? 'not-allowed' : 'pointer',
915
- display: 'inline-flex',
916
- alignItems: 'center',
917
- }}
918
- >
919
- <Trash2 size={12} />
920
- </button>
921
- </div>
922
- </div>
923
-
924
- {showInsertDrawer && renderInsertPaletteContent(true)}
925
- </div>
926
-
927
- {/* Navigation Flow Link Banner if element triggers navigation */}
928
- {inspected.navigationTarget && (
929
- <div
930
- style={{
931
- margin: '12px 14px 0',
932
- padding: '9px 12px',
933
- backgroundColor: 'var(--studio-badge-emerald-bg)',
934
- border: '1px solid var(--studio-badge-emerald-border)',
935
- borderRadius: 8,
936
- display: 'flex',
937
- alignItems: 'center',
938
- justifyContent: 'space-between',
939
- }}
940
- >
941
- <div
942
- style={{
943
- display: 'flex',
944
- alignItems: 'center',
945
- gap: 8,
946
- fontSize: 12,
947
- color: 'var(--studio-badge-emerald-text)',
948
- }}
949
- >
950
- <Navigation size={13} color="var(--studio-accent-emerald)" />
951
- <span>
952
- Flow → <strong>{inspected.navigationTarget}</strong>
953
- </span>
954
- </div>
955
- <button
956
- type="button"
957
- className="zelune-interactive"
958
- onClick={() => onJumpToScreen(inspected.navigationTarget!)}
959
- style={{
960
- backgroundColor: '#10B981',
961
- color: '#052E16',
962
- border: 'none',
963
- borderRadius: 5,
964
- padding: '4px 8px',
965
- fontSize: 11,
966
- fontWeight: 700,
967
- cursor: 'pointer',
968
- }}
969
- >
970
- Focus Target →
971
- </button>
972
- </div>
973
- )}
974
-
975
- <div className="zelune-inspector-body" style={{ display: 'flex', flexDirection: 'column' }}>
976
- {/* SECTION 1: Text Copy Editor */}
977
- {(inspected.isTextEditable || inspected.hasDynamicText) && (
978
- <section style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
979
- <div
980
- style={{
981
- display: 'flex',
982
- alignItems: 'center',
983
- gap: 6,
984
- fontSize: 10.5,
985
- fontFamily: 'var(--font-mono)',
986
- fontWeight: 600,
987
- color: 'var(--studio-text-muted)',
988
- letterSpacing: '0.06em',
989
- }}
990
- >
991
- <Type size={12} color="#38BDF8" />
992
- <span>TEXT COPY</span>
993
- </div>
994
-
995
- {inspected.hasDynamicText ? (
996
- <div
997
- style={{
998
- padding: 10,
999
- backgroundColor: 'var(--studio-surface-2)',
1000
- border: '1px solid var(--studio-border-subtle)',
1001
- borderRadius: 7,
1002
- fontSize: 11.5,
1003
- color: 'var(--studio-text-secondary)',
1004
- lineHeight: 1.5,
1005
- }}
1006
- >
1007
- Contains dynamic JSX expression (<code style={{ color: '#0284C7' }}>{'{...}'}</code>). Protected from static overwrite—use <strong>Ask Antigravity</strong> below to refactor expression logic.
1008
- </div>
1009
- ) : (
1010
- <textarea
1011
- rows={2}
1012
- value={localText}
1013
- onChange={(e) => handleTextChange(e.target.value)}
1014
- placeholder="Enter text content..."
1015
- className="zelune-input"
1016
- style={{
1017
- width: '100%',
1018
- backgroundColor: 'var(--studio-canvas-bg)',
1019
- border: '1px solid var(--studio-border-subtle)',
1020
- borderRadius: 7,
1021
- paddingLeft: 10,
1022
- paddingRight: 10,
1023
- color: 'var(--studio-text-primary)',
1024
- fontSize: 12.5,
1025
- resize: 'vertical',
1026
- fontFamily: 'var(--font-sans)',
1027
- }}
1028
- />
1029
- )}
1030
- </section>
1031
- )}
1032
-
1033
- {/* SECTION 2: Colors (Background & Text/Foreground) */}
1034
- <section style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
1035
- <div
1036
- style={{
1037
- display: 'flex',
1038
- alignItems: 'center',
1039
- gap: 6,
1040
- fontSize: 10.5,
1041
- fontFamily: 'var(--font-mono)',
1042
- fontWeight: 600,
1043
- color: 'var(--studio-text-muted)',
1044
- letterSpacing: '0.06em',
1045
- }}
1046
- >
1047
- <Palette size={12} color="#38BDF8" />
1048
- <span>COLORS &amp; SURFACES</span>
1049
- </div>
1050
-
1051
- {/* Background Color */}
1052
- <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
1053
- <label style={{ fontSize: 11.5, color: 'var(--studio-text-secondary)' }}>Background Color</label>
1054
- <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
1055
- <input
1056
- type="color"
1057
- value={
1058
- typeof localStyles.backgroundColor === 'string' &&
1059
- localStyles.backgroundColor.startsWith('#') &&
1060
- localStyles.backgroundColor.length === 7
1061
- ? localStyles.backgroundColor
1062
- : '#13161E'
1063
- }
1064
- onChange={(e) => handleStyleChange('backgroundColor', e.target.value)}
1065
- style={{
1066
- width: 30,
1067
- height: 30,
1068
- border: '1px solid var(--studio-border-subtle)',
1069
- borderRadius: 6,
1070
- backgroundColor: 'transparent',
1071
- cursor: 'pointer',
1072
- }}
1073
- />
1074
- <input
1075
- type="text"
1076
- value={String(localStyles.backgroundColor ?? '')}
1077
- placeholder="#13161E"
1078
- onChange={(e) => handleStyleChange('backgroundColor', e.target.value)}
1079
- className="zelune-input tabular-nums"
1080
- style={{
1081
- flex: 1,
1082
- backgroundColor: 'var(--studio-canvas-bg)',
1083
- border: '1px solid var(--studio-border-subtle)',
1084
- borderRadius: 6,
1085
- paddingLeft: 10,
1086
- paddingRight: 10,
1087
- color: 'var(--studio-text-primary)',
1088
- fontSize: 12,
1089
- fontFamily: 'var(--font-mono)',
1090
- }}
1091
- />
1092
- </div>
1093
- <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap', marginTop: 2 }}>
1094
- {COLOR_SWATCHES.map((hex) => (
1095
- <button
1096
- key={`bg-${hex}`}
1097
- type="button"
1098
- className="zelune-interactive"
1099
- onClick={() => handleStyleChange('backgroundColor', hex)}
1100
- title={`Set backgroundColor to ${hex}`}
1101
- style={{
1102
- width: 20,
1103
- height: 20,
1104
- borderRadius: 5,
1105
- backgroundColor: hex,
1106
- border:
1107
- localStyles.backgroundColor === hex
1108
- ? '2px solid #38BDF8'
1109
- : '1px solid var(--studio-border-strong)',
1110
- cursor: 'pointer',
1111
- }}
1112
- />
1113
- ))}
1114
- </div>
1115
- </div>
1116
-
1117
- {/* Text Color */}
1118
- <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
1119
- <label style={{ fontSize: 11.5, color: 'var(--studio-text-secondary)' }}>Text Color</label>
1120
- <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
1121
- <input
1122
- type="color"
1123
- value={
1124
- typeof localStyles.color === 'string' &&
1125
- localStyles.color.startsWith('#') &&
1126
- localStyles.color.length === 7
1127
- ? localStyles.color
1128
- : '#F8FAFC'
1129
- }
1130
- onChange={(e) => handleStyleChange('color', e.target.value)}
1131
- style={{
1132
- width: 30,
1133
- height: 30,
1134
- border: '1px solid var(--studio-border-subtle)',
1135
- borderRadius: 6,
1136
- backgroundColor: 'transparent',
1137
- cursor: 'pointer',
1138
- }}
1139
- />
1140
- <input
1141
- type="text"
1142
- value={String(localStyles.color ?? '')}
1143
- placeholder="#F8FAFC"
1144
- onChange={(e) => handleStyleChange('color', e.target.value)}
1145
- className="zelune-input tabular-nums"
1146
- style={{
1147
- flex: 1,
1148
- backgroundColor: 'var(--studio-canvas-bg)',
1149
- border: '1px solid var(--studio-border-subtle)',
1150
- borderRadius: 6,
1151
- paddingLeft: 10,
1152
- paddingRight: 10,
1153
- color: 'var(--studio-text-primary)',
1154
- fontSize: 12,
1155
- fontFamily: 'var(--font-mono)',
1156
- }}
1157
- />
1158
- </div>
1159
- </div>
1160
- </section>
1161
-
1162
- {/* SECTION 3: Typography */}
1163
- <section style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
1164
- <div
1165
- style={{
1166
- display: 'flex',
1167
- alignItems: 'center',
1168
- gap: 6,
1169
- fontSize: 10.5,
1170
- fontFamily: 'var(--font-mono)',
1171
- fontWeight: 600,
1172
- color: 'var(--studio-text-muted)',
1173
- letterSpacing: '0.06em',
1174
- }}
1175
- >
1176
- <Type size={12} color="#38BDF8" />
1177
- <span>TYPOGRAPHY</span>
1178
- </div>
1179
-
1180
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
1181
- <div>
1182
- <label style={{ fontSize: 11, color: 'var(--studio-text-secondary)', display: 'block', marginBottom: 4 }}>
1183
- Font Size (px)
1184
- </label>
1185
- <input
1186
- type="number"
1187
- value={localStyles.fontSize ?? ''}
1188
- placeholder="15"
1189
- onChange={(e) => handleNumericStyleChange('fontSize', e.target.value)}
1190
- className="zelune-input tabular-nums"
1191
- style={{
1192
- width: '100%',
1193
- backgroundColor: 'var(--studio-canvas-bg)',
1194
- border: '1px solid var(--studio-border-subtle)',
1195
- borderRadius: 6,
1196
- paddingLeft: 8,
1197
- paddingRight: 8,
1198
- color: 'var(--studio-text-primary)',
1199
- fontSize: 12,
1200
- fontFamily: 'var(--font-mono)',
1201
- }}
1202
- />
1203
- </div>
1204
-
1205
- <div>
1206
- <label style={{ fontSize: 11, color: 'var(--studio-text-secondary)', display: 'block', marginBottom: 4 }}>
1207
- Font Weight
1208
- </label>
1209
- <select
1210
- value={String(localStyles.fontWeight ?? '600')}
1211
- onChange={(e) => handleStyleChange('fontWeight', e.target.value)}
1212
- className="zelune-input tabular-nums"
1213
- style={{
1214
- width: '100%',
1215
- backgroundColor: 'var(--studio-canvas-bg)',
1216
- border: '1px solid var(--studio-border-subtle)',
1217
- borderRadius: 6,
1218
- paddingLeft: 8,
1219
- paddingRight: 8,
1220
- color: 'var(--studio-text-primary)',
1221
- fontSize: 12,
1222
- fontFamily: 'var(--font-mono)',
1223
- }}
1224
- >
1225
- {FONT_WEIGHTS.map((w) => (
1226
- <option key={w} value={w}>
1227
- {w}
1228
- </option>
1229
- ))}
1230
- </select>
1231
- </div>
1232
- </div>
1233
- </section>
1234
-
1235
- {/* SECTION 4: Layout, Spacing & Radius */}
1236
- <section style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
1237
- <div
1238
- style={{
1239
- display: 'flex',
1240
- alignItems: 'center',
1241
- gap: 6,
1242
- fontSize: 10.5,
1243
- fontFamily: 'var(--font-mono)',
1244
- fontWeight: 600,
1245
- color: 'var(--studio-text-muted)',
1246
- letterSpacing: '0.06em',
1247
- }}
1248
- >
1249
- <Box size={12} color="#38BDF8" />
1250
- <span>SPACING &amp; GEOMETRY</span>
1251
- </div>
1252
-
1253
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
1254
- {[
1255
- { key: 'borderRadius', label: 'Corner Radius' },
1256
- { key: 'padding', label: 'Padding (All)' },
1257
- { key: 'paddingHorizontal', label: 'Padding X' },
1258
- { key: 'paddingVertical', label: 'Padding Y' },
1259
- { key: 'gap', label: 'Flex Gap' },
1260
- { key: 'borderWidth', label: 'Border Width' },
1261
- ].map(({ key, label }) => (
1262
- <div key={key}>
1263
- <label style={{ fontSize: 11, color: 'var(--studio-text-secondary)', display: 'block', marginBottom: 4 }}>
1264
- {label}
1265
- </label>
1266
- <input
1267
- type="number"
1268
- value={localStyles[key] ?? ''}
1269
- placeholder="—"
1270
- onChange={(e) => handleNumericStyleChange(key, e.target.value)}
1271
- className="zelune-input tabular-nums"
1272
- style={{
1273
- width: '100%',
1274
- backgroundColor: 'var(--studio-canvas-bg)',
1275
- border: '1px solid var(--studio-border-subtle)',
1276
- borderRadius: 6,
1277
- paddingLeft: 8,
1278
- paddingRight: 8,
1279
- color: 'var(--studio-text-primary)',
1280
- fontSize: 12,
1281
- fontFamily: 'var(--font-mono)',
1282
- }}
1283
- />
1284
- </div>
1285
- ))}
1286
- </div>
1287
- </section>
1288
-
1289
- {/* SECTION 5: Tier-2 Antigravity Structural Refactor Button */}
1290
- <section style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
1291
- <button
1292
- type="button"
1293
- className="zelune-interactive"
1294
- onClick={() => dialogRef.current?.showModal()}
1295
- style={{
1296
- display: 'flex',
1297
- alignItems: 'center',
1298
- justifyContent: 'center',
1299
- gap: 8,
1300
- width: '100%',
1301
- padding: '10px 14px',
1302
- backgroundColor: 'var(--studio-surface-3)',
1303
- color: 'var(--studio-text-primary)',
1304
- border: '1px solid rgba(56, 189, 248, 0.42)',
1305
- borderRadius: 8,
1306
- fontSize: 12.5,
1307
- fontWeight: 600,
1308
- cursor: 'pointer',
1309
- boxShadow: 'inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 8px 20px -8px rgba(0, 0, 0, 0.25)',
1310
- }}
1311
- >
1312
- <Sparkles size={14} color="#38BDF8" />
1313
- <span>Ask Antigravity to Refactor</span>
1314
- </button>
1315
- </section>
1316
-
1317
- {/* SECTION 6: Live JSX Code Snippet */}
1318
- <section style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
1319
- <div
1320
- style={{
1321
- display: 'flex',
1322
- alignItems: 'center',
1323
- gap: 6,
1324
- fontSize: 10.5,
1325
- fontFamily: 'var(--font-mono)',
1326
- fontWeight: 600,
1327
- color: 'var(--studio-text-muted)',
1328
- letterSpacing: '0.06em',
1329
- }}
1330
- >
1331
- <Code2 size={12} color="#38BDF8" />
1332
- <span>LIVE JSX AST NODE</span>
1333
- </div>
1334
- <pre
1335
- className="zelune-scroller"
1336
- style={{
1337
- backgroundColor: 'var(--studio-canvas-bg)',
1338
- border: '1px solid var(--studio-border-hairline)',
1339
- borderRadius: 7,
1340
- padding: 10,
1341
- fontSize: 11,
1342
- fontFamily: 'var(--font-mono)',
1343
- color: 'var(--studio-text-secondary)',
1344
- overflowX: 'auto',
1345
- margin: 0,
1346
- lineHeight: 1.45,
1347
- }}
1348
- >
1349
- {inspected.codeSnippet}
1350
- </pre>
1351
- </section>
1352
- </div>
1353
-
1354
- {/* Native Modal Dialog with closedby="any" + Emil Kowalski --ease-out-expo entry choreography */}
1355
- <dialog
1356
- ref={dialogRef}
1357
- closedby="any"
1358
- aria-labelledby="agyDialogTitle"
1359
- className="agy-modal-dialog"
1360
- >
1361
- <div
1362
- style={{
1363
- backgroundColor: 'var(--studio-surface-1)',
1364
- color: 'var(--studio-text-primary)',
1365
- border: '1px solid var(--studio-border-strong)',
1366
- borderRadius: 14,
1367
- padding: 22,
1368
- width: 480,
1369
- maxWidth: '92vw',
1370
- display: 'flex',
1371
- flexDirection: 'column',
1372
- gap: 15,
1373
- boxShadow: '0 28px 60px -12px rgba(0, 0, 0, 0.55)',
1374
- }}
1375
- >
1376
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
1377
- <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
1378
- <Sparkles size={16} color="#38BDF8" />
1379
- <h2
1380
- id="agyDialogTitle"
1381
- className="balance-heading"
1382
- style={{ margin: 0, fontSize: 15, fontWeight: 700, letterSpacing: '-0.015em' }}
1383
- >
1384
- Ask Google Antigravity — Structural AST Edit
1385
- </h2>
1386
- </div>
1387
- <button
1388
- type="button"
1389
- className="zelune-interactive"
1390
- onClick={() => dialogRef.current?.close()}
1391
- style={{
1392
- background: 'transparent',
1393
- border: '1px solid transparent',
1394
- borderRadius: 5,
1395
- color: 'var(--studio-text-secondary)',
1396
- cursor: 'pointer',
1397
- padding: 4,
1398
- }}
1399
- >
1400
- <X size={16} />
1401
- </button>
1402
- </div>
1403
-
1404
- <p style={{ margin: 0, fontSize: 12.5, color: 'var(--studio-text-secondary)', lineHeight: 1.55 }}>
1405
- Describe the structural or stateful change you want for{' '}
1406
- <code style={{ color: '#0284C7', fontFamily: 'var(--font-mono)' }}>&lt;{inspected.tagName}&gt;</code> in{' '}
1407
- <code className="tabular-nums" style={{ color: '#6366F1', fontFamily: 'var(--font-mono)' }}>
1408
- {inspected.file}:{inspected.line}
1409
- </code>
1410
- .
1411
- </p>
1412
-
1413
- <textarea
1414
- rows={3}
1415
- value={agentInstruction}
1416
- onChange={(e) => setAgentInstruction(e.target.value)}
1417
- placeholder="e.g., Convert this spec grid into an asymmetric 2-column acoustic telemetry readout..."
1418
- style={{
1419
- width: '100%',
1420
- backgroundColor: 'var(--studio-canvas-bg)',
1421
- border: '1px solid var(--studio-border-subtle)',
1422
- borderRadius: 8,
1423
- padding: 11,
1424
- color: 'var(--studio-text-primary)',
1425
- fontSize: 12.5,
1426
- fontFamily: 'var(--font-sans)',
1427
- }}
1428
- />
1429
-
1430
- <div
1431
- className="zelune-scroller"
1432
- style={{
1433
- backgroundColor: 'var(--studio-canvas-bg)',
1434
- border: '1px solid var(--studio-border-hairline)',
1435
- borderRadius: 7,
1436
- padding: 10,
1437
- maxHeight: 150,
1438
- overflowY: 'auto',
1439
- fontSize: 11,
1440
- fontFamily: 'var(--font-mono)',
1441
- color: 'var(--studio-text-secondary)',
1442
- whiteSpace: 'pre-wrap',
1443
- }}
1444
- >
1445
- {buildAntigravityPrompt()}
1446
- </div>
1447
-
1448
- <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
1449
- <button
1450
- type="button"
1451
- className="zelune-interactive"
1452
- onClick={() => dialogRef.current?.close()}
1453
- style={{
1454
- padding: '8px 13px',
1455
- borderRadius: 7,
1456
- border: '1px solid var(--studio-border-subtle)',
1457
- backgroundColor: 'var(--studio-surface-2)',
1458
- color: 'var(--studio-text-secondary)',
1459
- fontSize: 12,
1460
- fontWeight: 600,
1461
- cursor: 'pointer',
1462
- }}
1463
- >
1464
- Cancel
1465
- </button>
1466
- <button
1467
- type="button"
1468
- className="zelune-interactive"
1469
- onClick={handleCopyPrompt}
1470
- style={{
1471
- display: 'inline-flex',
1472
- alignItems: 'center',
1473
- gap: 6,
1474
- padding: '8px 14px',
1475
- borderRadius: 7,
1476
- border: '1px solid rgba(56, 189, 248, 0.45)',
1477
- backgroundColor: 'var(--studio-surface-3)',
1478
- color: 'var(--studio-text-primary)',
1479
- fontSize: 12,
1480
- fontWeight: 600,
1481
- cursor: 'pointer',
1482
- }}
1483
- >
1484
- {copiedPrompt ? <Check size={14} color="#34D399" /> : <Copy size={14} color="#38BDF8" />}
1485
- <span>{copiedPrompt ? 'Copied Context for Antigravity!' : 'Copy Agent Prompt Context'}</span>
1486
- </button>
1487
- </div>
1488
- </div>
1489
- </dialog>
1490
- </div>
1491
- )}
1492
- </aside>
1493
- );
1494
- };