zelune 1.5.7 → 1.6.1
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 +27 -0
- package/bin/cli.mjs +22 -42
- package/mcp/server.mjs +2228 -0
- package/mcp/server.ts +367 -117
- package/package.json +3 -5
- package/plugin.json +1 -1
- package/plugins/viteReactNativeFallbackPlugin.ts +2 -11
- package/src/canvas/CanvasApp.tsx +174 -3
- package/src/canvas/FlowArrowsLayer.tsx +84 -45
- package/src/canvas/InspectorPanel.tsx +529 -23
- package/src/runtime/MockNavigationContext.tsx +59 -0
- package/src/server/apiMiddleware.ts +48 -0
- package/src/server/astEngine.ts +1291 -33
- package/vite.config.ts +177 -115
package/src/canvas/CanvasApp.tsx
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
Sliders,
|
|
19
19
|
Sun,
|
|
20
20
|
Moon,
|
|
21
|
+
Plus,
|
|
21
22
|
} from 'lucide-react';
|
|
22
23
|
import { SCREEN_COMPONENTS, SCREEN_ORDER } from '../screens';
|
|
23
24
|
import { ScreenArtboard } from './ScreenArtboard';
|
|
@@ -31,12 +32,18 @@ import type {
|
|
|
31
32
|
ScreenMetadata,
|
|
32
33
|
SelectedElementRef,
|
|
33
34
|
} from './types';
|
|
34
|
-
import type {
|
|
35
|
+
import type {
|
|
36
|
+
PatchPayload,
|
|
37
|
+
ReorderPayload,
|
|
38
|
+
InsertElementPayload,
|
|
39
|
+
DuplicateElementPayload,
|
|
40
|
+
DeleteElementPayload,
|
|
41
|
+
} from '../server/astEngine';
|
|
35
42
|
|
|
36
43
|
const DEFAULT_WIDTH = 393;
|
|
37
44
|
const DEFAULT_HEIGHT = 852;
|
|
38
|
-
const ARTBOARD_GAP =
|
|
39
|
-
const ARTBOARD_ROW_GAP =
|
|
45
|
+
const ARTBOARD_GAP = 160;
|
|
46
|
+
const ARTBOARD_ROW_GAP = 220;
|
|
40
47
|
const INITIAL_OFFSET_X = 80;
|
|
41
48
|
const INITIAL_OFFSET_Y = 70;
|
|
42
49
|
const MIN_ZOOM = 0.08;
|
|
@@ -241,6 +248,11 @@ export const CanvasApp: React.FC = () => {
|
|
|
241
248
|
}
|
|
242
249
|
const sorted = sortScreensByOrder(screensJson.screens);
|
|
243
250
|
setScreens(sorted);
|
|
251
|
+
setActiveScreenId((prev) => {
|
|
252
|
+
if (prev && sorted.some((s) => s.id === prev)) return prev;
|
|
253
|
+
const home = sorted.find((s) => s.id.toLowerCase() === 'home');
|
|
254
|
+
return home ? home.id : (sorted[0]?.id ?? null);
|
|
255
|
+
});
|
|
244
256
|
if (screensJson.history) {
|
|
245
257
|
setHistoryState(screensJson.history);
|
|
246
258
|
}
|
|
@@ -412,6 +424,132 @@ export const CanvasApp: React.FC = () => {
|
|
|
412
424
|
[fetchScreens]
|
|
413
425
|
);
|
|
414
426
|
|
|
427
|
+
// Surgical AST Component / Section Insertion handler
|
|
428
|
+
const handleInsertElement = useCallback(
|
|
429
|
+
async (payload: InsertElementPayload) => {
|
|
430
|
+
setIsSaving(true);
|
|
431
|
+
setStatusMessage(null);
|
|
432
|
+
try {
|
|
433
|
+
const res = await fetch('/api/insert', {
|
|
434
|
+
method: 'POST',
|
|
435
|
+
headers: { 'Content-Type': 'application/json' },
|
|
436
|
+
body: JSON.stringify(payload),
|
|
437
|
+
});
|
|
438
|
+
const data = (await res.json()) as {
|
|
439
|
+
ok: boolean;
|
|
440
|
+
element?: InspectedElement;
|
|
441
|
+
history?: { undoAvailable: number; redoAvailable: number };
|
|
442
|
+
error?: string;
|
|
443
|
+
};
|
|
444
|
+
if (data.ok && data.element) {
|
|
445
|
+
setInspectedData(data.element);
|
|
446
|
+
if (data.history) setHistoryState(data.history);
|
|
447
|
+
const ownerScreen =
|
|
448
|
+
screens.find((s) => s.filePath === data.element!.file)?.id ??
|
|
449
|
+
activeScreenId ??
|
|
450
|
+
'Home';
|
|
451
|
+
setSelectedElement({
|
|
452
|
+
screenId: ownerScreen,
|
|
453
|
+
file: data.element.file,
|
|
454
|
+
astPath: data.element.astPath,
|
|
455
|
+
line: data.element.line,
|
|
456
|
+
col: data.element.col,
|
|
457
|
+
tagName: data.element.tagName,
|
|
458
|
+
});
|
|
459
|
+
setStatusMessage(`Added <${data.element.tagName}> to .tsx`);
|
|
460
|
+
void fetchScreens();
|
|
461
|
+
} else if (data.error) {
|
|
462
|
+
setStatusMessage(`Error: ${data.error}`);
|
|
463
|
+
}
|
|
464
|
+
} catch {
|
|
465
|
+
setStatusMessage('Failed to insert AST element');
|
|
466
|
+
} finally {
|
|
467
|
+
setIsSaving(false);
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
[fetchScreens, screens, activeScreenId]
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
// Surgical AST Duplicate Element handler
|
|
474
|
+
const handleDuplicateElement = useCallback(
|
|
475
|
+
async (payload: DuplicateElementPayload) => {
|
|
476
|
+
setIsSaving(true);
|
|
477
|
+
setStatusMessage(null);
|
|
478
|
+
try {
|
|
479
|
+
const res = await fetch('/api/duplicate', {
|
|
480
|
+
method: 'POST',
|
|
481
|
+
headers: { 'Content-Type': 'application/json' },
|
|
482
|
+
body: JSON.stringify(payload),
|
|
483
|
+
});
|
|
484
|
+
const data = (await res.json()) as {
|
|
485
|
+
ok: boolean;
|
|
486
|
+
element?: InspectedElement;
|
|
487
|
+
history?: { undoAvailable: number; redoAvailable: number };
|
|
488
|
+
error?: string;
|
|
489
|
+
};
|
|
490
|
+
if (data.ok && data.element) {
|
|
491
|
+
setInspectedData(data.element);
|
|
492
|
+
if (data.history) setHistoryState(data.history);
|
|
493
|
+
setSelectedElement((prev) =>
|
|
494
|
+
prev
|
|
495
|
+
? {
|
|
496
|
+
...prev,
|
|
497
|
+
line: data.element!.line,
|
|
498
|
+
col: data.element!.col,
|
|
499
|
+
astPath: data.element!.astPath,
|
|
500
|
+
tagName: data.element!.tagName,
|
|
501
|
+
}
|
|
502
|
+
: null
|
|
503
|
+
);
|
|
504
|
+
setStatusMessage('Duplicated element in .tsx');
|
|
505
|
+
void fetchScreens();
|
|
506
|
+
} else if (data.error) {
|
|
507
|
+
setStatusMessage(`Error: ${data.error}`);
|
|
508
|
+
}
|
|
509
|
+
} catch {
|
|
510
|
+
setStatusMessage('Failed to duplicate AST element');
|
|
511
|
+
} finally {
|
|
512
|
+
setIsSaving(false);
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
[fetchScreens]
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
// Surgical AST Delete Element handler
|
|
519
|
+
const handleDeleteElement = useCallback(
|
|
520
|
+
async (payload: DeleteElementPayload) => {
|
|
521
|
+
setIsSaving(true);
|
|
522
|
+
setStatusMessage(null);
|
|
523
|
+
try {
|
|
524
|
+
const res = await fetch('/api/delete', {
|
|
525
|
+
method: 'POST',
|
|
526
|
+
headers: { 'Content-Type': 'application/json' },
|
|
527
|
+
body: JSON.stringify(payload),
|
|
528
|
+
});
|
|
529
|
+
const data = (await res.json()) as {
|
|
530
|
+
ok: boolean;
|
|
531
|
+
deletedAstPath?: string;
|
|
532
|
+
history?: { undoAvailable: number; redoAvailable: number };
|
|
533
|
+
error?: string;
|
|
534
|
+
};
|
|
535
|
+
if (data.ok) {
|
|
536
|
+
if (data.history) setHistoryState(data.history);
|
|
537
|
+
setSelectedElement(null);
|
|
538
|
+
setInspectedData(null);
|
|
539
|
+
setStatusMessage('Deleted element from .tsx');
|
|
540
|
+
void fetchScreens();
|
|
541
|
+
} else if (data.error) {
|
|
542
|
+
setStatusMessage(`Error: ${data.error}`);
|
|
543
|
+
}
|
|
544
|
+
} catch {
|
|
545
|
+
setStatusMessage('Failed to delete AST element');
|
|
546
|
+
} finally {
|
|
547
|
+
setIsSaving(false);
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
[fetchScreens]
|
|
551
|
+
);
|
|
552
|
+
|
|
415
553
|
// Undo & Redo handlers
|
|
416
554
|
const handleUndo = useCallback(async () => {
|
|
417
555
|
const res = await fetch('/api/undo', {
|
|
@@ -939,6 +1077,32 @@ export const CanvasApp: React.FC = () => {
|
|
|
939
1077
|
<GitCommit size={14} style={{ flexShrink: 0 }} />
|
|
940
1078
|
<span className="zelune-reveal-label">Flow Graph</span>
|
|
941
1079
|
</button>
|
|
1080
|
+
|
|
1081
|
+
<button
|
|
1082
|
+
type="button"
|
|
1083
|
+
className="zelune-interactive"
|
|
1084
|
+
onClick={() => {
|
|
1085
|
+
setIsRightPanelOpen(true);
|
|
1086
|
+
window.dispatchEvent(new CustomEvent('zelune:open-insert-palette'));
|
|
1087
|
+
}}
|
|
1088
|
+
title="Add Button, Section, Input, or Component to Active Screen"
|
|
1089
|
+
style={{
|
|
1090
|
+
display: 'inline-flex',
|
|
1091
|
+
alignItems: 'center',
|
|
1092
|
+
gap: 5,
|
|
1093
|
+
padding: '6px 10px',
|
|
1094
|
+
borderRadius: 7,
|
|
1095
|
+
border: '1px solid rgba(56, 189, 248, 0.4)',
|
|
1096
|
+
backgroundColor: 'rgba(56, 189, 248, 0.12)',
|
|
1097
|
+
color: colorScheme === 'dark' ? '#38BDF8' : '#0284C7',
|
|
1098
|
+
fontSize: 12,
|
|
1099
|
+
fontWeight: 600,
|
|
1100
|
+
cursor: 'pointer',
|
|
1101
|
+
}}
|
|
1102
|
+
>
|
|
1103
|
+
<Plus size={14} style={{ flexShrink: 0 }} />
|
|
1104
|
+
<span>Add Element</span>
|
|
1105
|
+
</button>
|
|
942
1106
|
</div>
|
|
943
1107
|
|
|
944
1108
|
{/* Right: Undo/Redo, Viewport Controls, Theme Toggle & Right Inspector Toggle */}
|
|
@@ -1466,11 +1630,18 @@ export const CanvasApp: React.FC = () => {
|
|
|
1466
1630
|
{/* Right: AST Visual Inspector Panel */}
|
|
1467
1631
|
<InspectorPanel
|
|
1468
1632
|
inspected={inspectedData}
|
|
1633
|
+
screens={screens}
|
|
1634
|
+
activeScreenId={activeScreenId}
|
|
1469
1635
|
isOpen={isRightPanelOpen}
|
|
1470
1636
|
isLoading={isInspecting}
|
|
1471
1637
|
isSaving={isSaving}
|
|
1472
1638
|
statusMessage={statusMessage}
|
|
1473
1639
|
onPatch={handlePatchElement}
|
|
1640
|
+
onInsert={handleInsertElement}
|
|
1641
|
+
onDuplicate={handleDuplicateElement}
|
|
1642
|
+
onDelete={handleDeleteElement}
|
|
1643
|
+
onReorder={handleReorderElement}
|
|
1644
|
+
onSelectScreen={setActiveScreenId}
|
|
1474
1645
|
onClearSelection={() => {
|
|
1475
1646
|
setSelectedElement(null);
|
|
1476
1647
|
setInspectedData(null);
|
|
@@ -36,10 +36,10 @@ export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = React.memo(({
|
|
|
36
36
|
top: 0,
|
|
37
37
|
left: 0,
|
|
38
38
|
width: 8000,
|
|
39
|
-
height:
|
|
39
|
+
height: 6000,
|
|
40
40
|
pointerEvents: 'none',
|
|
41
41
|
overflow: 'visible',
|
|
42
|
-
zIndex:
|
|
42
|
+
zIndex: 25,
|
|
43
43
|
}}
|
|
44
44
|
>
|
|
45
45
|
<defs>
|
|
@@ -48,8 +48,8 @@ export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = React.memo(({
|
|
|
48
48
|
viewBox="0 0 10 10"
|
|
49
49
|
refX="8"
|
|
50
50
|
refY="5"
|
|
51
|
-
markerWidth="6"
|
|
52
|
-
markerHeight="6"
|
|
51
|
+
markerWidth="6.5"
|
|
52
|
+
markerHeight="6.5"
|
|
53
53
|
orient="auto-start-reverse"
|
|
54
54
|
>
|
|
55
55
|
<path d="M 0 1.5 L 8.5 5 L 0 8.5 z" fill="#6366F1" />
|
|
@@ -63,7 +63,7 @@ export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = React.memo(({
|
|
|
63
63
|
markerHeight="7"
|
|
64
64
|
orient="auto-start-reverse"
|
|
65
65
|
>
|
|
66
|
-
<path d="M 0 1.5 L 8.5 5 L 0 8.5 z" fill="#
|
|
66
|
+
<path d="M 0 1.5 L 8.5 5 L 0 8.5 z" fill="#10B981" />
|
|
67
67
|
</marker>
|
|
68
68
|
</defs>
|
|
69
69
|
|
|
@@ -72,48 +72,87 @@ export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = React.memo(({
|
|
|
72
72
|
const toBox = layout[edge.toScreen];
|
|
73
73
|
if (!fromBox || !toBox) return null;
|
|
74
74
|
|
|
75
|
-
const
|
|
75
|
+
const sameRow = Math.abs(toBox.y - fromBox.y) < 180;
|
|
76
|
+
const isExplicitReturn =
|
|
77
|
+
edge.id.includes(':return') || edge.triggerLabel.startsWith('Back to ');
|
|
78
|
+
const isReturnOrBackward =
|
|
79
|
+
isExplicitReturn || (sameRow ? toBox.x < fromBox.x : toBox.y < fromBox.y);
|
|
80
|
+
|
|
76
81
|
const isHighlighted =
|
|
77
82
|
activeScreenId === edge.fromScreen ||
|
|
78
83
|
highlightedScreenId === edge.toScreen ||
|
|
79
84
|
activeScreenId === edge.toScreen;
|
|
80
85
|
|
|
81
|
-
//
|
|
82
|
-
const
|
|
86
|
+
// Emerald/Green wires for Return/Back flows AND Active/Highlighted flows; Blue/Indigo wires for Forward flows
|
|
87
|
+
const useEmeraldWire = isHighlighted || isReturnOrBackward;
|
|
88
|
+
|
|
89
|
+
const verticalSlot = ((idx % 4) - 1.5) * 52;
|
|
90
|
+
const horizontalSlot = ((idx % 5) - 2) * 38;
|
|
83
91
|
|
|
84
92
|
let startX: number;
|
|
85
93
|
let startY: number;
|
|
94
|
+
let cp1X: number;
|
|
95
|
+
let cp1Y: number;
|
|
96
|
+
let cp2X: number;
|
|
97
|
+
let cp2Y: number;
|
|
86
98
|
let endX: number;
|
|
87
99
|
let endY: number;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
|
|
101
|
+
if (sameRow) {
|
|
102
|
+
if (toBox.x >= fromBox.x) {
|
|
103
|
+
// Left-to-right forward connection in same row
|
|
104
|
+
startX = fromBox.x + fromBox.width;
|
|
105
|
+
startY = fromBox.y + ARTBOARD_HEADER_OFFSET + fromBox.height * 0.45 + verticalSlot;
|
|
106
|
+
endX = toBox.x;
|
|
107
|
+
endY = toBox.y + ARTBOARD_HEADER_OFFSET + toBox.height * 0.36 + verticalSlot * 0.6;
|
|
108
|
+
const dx = Math.max(85, Math.abs(endX - startX) * 0.45);
|
|
109
|
+
cp1X = startX + dx;
|
|
110
|
+
cp1Y = startY;
|
|
111
|
+
cp2X = endX - dx;
|
|
112
|
+
cp2Y = endY;
|
|
113
|
+
} else {
|
|
114
|
+
// Return loop arched smoothly in the horizontal alley underneath the row
|
|
115
|
+
startX = fromBox.x + fromBox.width * 0.5 + horizontalSlot * 0.5;
|
|
116
|
+
startY = fromBox.y + ARTBOARD_HEADER_OFFSET + fromBox.height;
|
|
117
|
+
endX = toBox.x + toBox.width * 0.5 - horizontalSlot * 0.5;
|
|
118
|
+
endY = toBox.y + ARTBOARD_HEADER_OFFSET + toBox.height;
|
|
119
|
+
const archDepth = 95 + (idx % 3) * 32;
|
|
120
|
+
cp1X = startX;
|
|
121
|
+
cp1Y = startY + archDepth;
|
|
122
|
+
cp2X = endX;
|
|
123
|
+
cp2Y = endY + archDepth;
|
|
124
|
+
}
|
|
125
|
+
} else if (toBox.y > fromBox.y) {
|
|
126
|
+
// Cross-row downward connection (Bottom of fromBox -> Top of toBox)
|
|
127
|
+
startX = fromBox.x + fromBox.width * 0.5 + horizontalSlot;
|
|
105
128
|
startY = fromBox.y + ARTBOARD_HEADER_OFFSET + fromBox.height;
|
|
106
|
-
endX = toBox.x + toBox.width * 0.5;
|
|
129
|
+
endX = toBox.x + toBox.width * 0.5 + horizontalSlot * 0.6;
|
|
130
|
+
endY = toBox.y + ARTBOARD_HEADER_OFFSET;
|
|
131
|
+
const dy = Math.max(90, Math.abs(endY - startY) * 0.48);
|
|
132
|
+
cp1X = startX;
|
|
133
|
+
cp1Y = startY + dy;
|
|
134
|
+
cp2X = endX;
|
|
135
|
+
cp2Y = endY - dy;
|
|
136
|
+
} else {
|
|
137
|
+
// Cross-row upward return connection (Top of fromBox -> Bottom of toBox)
|
|
138
|
+
startX = fromBox.x + fromBox.width * 0.5 + horizontalSlot;
|
|
139
|
+
startY = fromBox.y + ARTBOARD_HEADER_OFFSET;
|
|
140
|
+
endX = toBox.x + toBox.width * 0.5 + horizontalSlot * 0.6;
|
|
107
141
|
endY = toBox.y + ARTBOARD_HEADER_OFFSET + toBox.height;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
142
|
+
const dy = Math.max(90, Math.abs(startY - endY) * 0.48);
|
|
143
|
+
cp1X = startX;
|
|
144
|
+
cp1Y = startY - dy;
|
|
145
|
+
cp2X = endX;
|
|
146
|
+
cp2Y = endY + dy;
|
|
113
147
|
}
|
|
114
148
|
|
|
115
|
-
const
|
|
116
|
-
|
|
149
|
+
const pathD = `M ${startX} ${startY} C ${cp1X} ${cp1Y}, ${cp2X} ${cp2Y}, ${endX} ${endY}`;
|
|
150
|
+
// Exact cubic Bezier midpoint at t = 0.5
|
|
151
|
+
const midX = 0.125 * startX + 0.375 * cp1X + 0.375 * cp2X + 0.125 * endX;
|
|
152
|
+
const midY = 0.125 * startY + 0.375 * cp1Y + 0.375 * cp2Y + 0.125 * endY;
|
|
153
|
+
|
|
154
|
+
const strokeColor = useEmeraldWire ? '#10B981' : '#6366F1';
|
|
155
|
+
const strokeOpacity = isHighlighted ? 0.98 : 0.82;
|
|
117
156
|
|
|
118
157
|
return (
|
|
119
158
|
<g key={edge.id}>
|
|
@@ -122,9 +161,9 @@ export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = React.memo(({
|
|
|
122
161
|
<path
|
|
123
162
|
d={pathD}
|
|
124
163
|
fill="none"
|
|
125
|
-
stroke=
|
|
126
|
-
strokeWidth={
|
|
127
|
-
strokeOpacity={0.
|
|
164
|
+
stroke={strokeColor}
|
|
165
|
+
strokeWidth={7}
|
|
166
|
+
strokeOpacity={0.22}
|
|
128
167
|
/>
|
|
129
168
|
)}
|
|
130
169
|
|
|
@@ -133,11 +172,11 @@ export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = React.memo(({
|
|
|
133
172
|
d={pathD}
|
|
134
173
|
fill="none"
|
|
135
174
|
stroke={strokeColor}
|
|
136
|
-
strokeWidth={isHighlighted ? 2.
|
|
175
|
+
strokeWidth={isHighlighted ? 2.8 : 2.15}
|
|
137
176
|
strokeOpacity={strokeOpacity}
|
|
138
|
-
strokeDasharray={
|
|
177
|
+
strokeDasharray={isReturnOrBackward ? '7 5' : undefined}
|
|
139
178
|
markerEnd={
|
|
140
|
-
|
|
179
|
+
useEmeraldWire ? 'url(#flow-arrow-active)' : 'url(#flow-arrow-default)'
|
|
141
180
|
}
|
|
142
181
|
/>
|
|
143
182
|
|
|
@@ -145,17 +184,17 @@ export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = React.memo(({
|
|
|
145
184
|
<circle
|
|
146
185
|
cx={startX}
|
|
147
186
|
cy={startY}
|
|
148
|
-
r={4}
|
|
187
|
+
r={4.5}
|
|
149
188
|
fill="var(--studio-surface-1)"
|
|
150
189
|
stroke={strokeColor}
|
|
151
|
-
strokeWidth={2}
|
|
190
|
+
strokeWidth={2.2}
|
|
152
191
|
/>
|
|
153
192
|
|
|
154
193
|
{/* Interactive Flow Label Pill */}
|
|
155
194
|
<foreignObject
|
|
156
|
-
x={midX -
|
|
195
|
+
x={midX - 85}
|
|
157
196
|
y={midY - 13}
|
|
158
|
-
width={
|
|
197
|
+
width={170}
|
|
159
198
|
height={28}
|
|
160
199
|
style={{ pointerEvents: 'auto', overflow: 'visible' }}
|
|
161
200
|
>
|
|
@@ -173,8 +212,8 @@ export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = React.memo(({
|
|
|
173
212
|
padding: '3px 9px',
|
|
174
213
|
borderRadius: 999,
|
|
175
214
|
backgroundColor: 'var(--studio-surface-1)',
|
|
176
|
-
border: `1px solid ${
|
|
177
|
-
color:
|
|
215
|
+
border: `1px solid ${useEmeraldWire ? 'var(--studio-accent-emerald)' : 'var(--studio-badge-indigo-border)'}`,
|
|
216
|
+
color: useEmeraldWire ? 'var(--studio-badge-emerald-text)' : 'var(--studio-badge-indigo-text)',
|
|
178
217
|
fontSize: 10,
|
|
179
218
|
fontWeight: 600,
|
|
180
219
|
fontFamily: 'JetBrains Mono, monospace',
|