zelune 0.0.0-stage → 1.5.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 +81 -0
- package/DESIGN.md +90 -0
- package/PRODUCT.md +31 -0
- package/README.md +63 -2
- package/assets/logo.svg +8 -0
- package/bin/cli.mjs +427 -0
- package/index.html +18 -0
- package/mcp/server.ts +196 -0
- package/mcp_config.json +8 -0
- package/package.json +77 -4
- package/plugin.json +12 -0
- package/plugins/viteRnSourceInject.ts +117 -0
- package/plugins/viteVirtualScreensPlugin.ts +85 -0
- package/rules/AGENTS.md +21 -0
- package/skills/rn-canvas/SKILL.md +33 -0
- package/src/canvas/CanvasApp.tsx +1288 -0
- package/src/canvas/FlowArrowsLayer.tsx +197 -0
- package/src/canvas/InspectorPanel.tsx +979 -0
- package/src/canvas/ScreenArtboard.tsx +1416 -0
- package/src/canvas/types.ts +23 -0
- package/src/main.tsx +10 -0
- package/src/runtime/MockNavigationContext.tsx +199 -0
- package/src/runtime/ScreenErrorBoundary.tsx +86 -0
- package/src/screens/CheckoutScreen.tsx +229 -0
- package/src/screens/HomeScreen.tsx +288 -0
- package/src/screens/LoginScreen.tsx +176 -0
- package/src/screens/ProductDetailScreen.tsx +234 -0
- package/src/screens/ProfileScreen.tsx +172 -0
- package/src/screens/index.ts +1 -0
- package/src/server/apiMiddleware.ts +327 -0
- package/src/server/astEngine.ts +1286 -0
- package/src/styles.css +298 -0
- package/src/vite-env.d.ts +7 -0
- package/tsconfig.json +23 -0
- package/vite.config.ts +148 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ArtboardLayoutMap, NavigationEdge, ScreenMetadata } from './types';
|
|
3
|
+
|
|
4
|
+
interface FlowArrowsLayerProps {
|
|
5
|
+
screens: ScreenMetadata[];
|
|
6
|
+
layout: ArtboardLayoutMap;
|
|
7
|
+
activeScreenId: string | null;
|
|
8
|
+
highlightedScreenId: string | null;
|
|
9
|
+
onSelectFlow?: (edge: NavigationEdge) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const ARTBOARD_HEADER_OFFSET = 38;
|
|
13
|
+
|
|
14
|
+
export const FlowArrowsLayer: React.FC<FlowArrowsLayerProps> = ({
|
|
15
|
+
screens,
|
|
16
|
+
layout,
|
|
17
|
+
activeScreenId,
|
|
18
|
+
highlightedScreenId,
|
|
19
|
+
onSelectFlow,
|
|
20
|
+
}) => {
|
|
21
|
+
const edges: NavigationEdge[] = [];
|
|
22
|
+
for (const screen of screens) {
|
|
23
|
+
for (const edge of screen.outgoingFlows) {
|
|
24
|
+
if (layout[edge.fromScreen] && layout[edge.toScreen]) {
|
|
25
|
+
edges.push(edge);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (edges.length === 0) return null;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<svg
|
|
34
|
+
style={{
|
|
35
|
+
position: 'absolute',
|
|
36
|
+
top: 0,
|
|
37
|
+
left: 0,
|
|
38
|
+
width: 8000,
|
|
39
|
+
height: 4000,
|
|
40
|
+
pointerEvents: 'none',
|
|
41
|
+
overflow: 'visible',
|
|
42
|
+
zIndex: 5,
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
<defs>
|
|
46
|
+
<marker
|
|
47
|
+
id="flow-arrow-default"
|
|
48
|
+
viewBox="0 0 10 10"
|
|
49
|
+
refX="8"
|
|
50
|
+
refY="5"
|
|
51
|
+
markerWidth="6"
|
|
52
|
+
markerHeight="6"
|
|
53
|
+
orient="auto-start-reverse"
|
|
54
|
+
>
|
|
55
|
+
<path d="M 0 1.5 L 8.5 5 L 0 8.5 z" fill="#6366F1" />
|
|
56
|
+
</marker>
|
|
57
|
+
<marker
|
|
58
|
+
id="flow-arrow-active"
|
|
59
|
+
viewBox="0 0 10 10"
|
|
60
|
+
refX="8"
|
|
61
|
+
refY="5"
|
|
62
|
+
markerWidth="7"
|
|
63
|
+
markerHeight="7"
|
|
64
|
+
orient="auto-start-reverse"
|
|
65
|
+
>
|
|
66
|
+
<path d="M 0 1.5 L 8.5 5 L 0 8.5 z" fill="#34D399" />
|
|
67
|
+
</marker>
|
|
68
|
+
</defs>
|
|
69
|
+
|
|
70
|
+
{edges.map((edge, idx) => {
|
|
71
|
+
const fromBox = layout[edge.fromScreen];
|
|
72
|
+
const toBox = layout[edge.toScreen];
|
|
73
|
+
if (!fromBox || !toBox) return null;
|
|
74
|
+
|
|
75
|
+
const isForward = toBox.x >= fromBox.x;
|
|
76
|
+
const isHighlighted =
|
|
77
|
+
activeScreenId === edge.fromScreen ||
|
|
78
|
+
highlightedScreenId === edge.toScreen ||
|
|
79
|
+
activeScreenId === edge.toScreen;
|
|
80
|
+
|
|
81
|
+
// Vertical staggering so multiple arrows don't overlap
|
|
82
|
+
const verticalSlot = ((idx % 4) - 1.5) * 56;
|
|
83
|
+
|
|
84
|
+
let startX: number;
|
|
85
|
+
let startY: number;
|
|
86
|
+
let endX: number;
|
|
87
|
+
let endY: number;
|
|
88
|
+
let pathD: string;
|
|
89
|
+
let midX: number;
|
|
90
|
+
let midY: number;
|
|
91
|
+
|
|
92
|
+
if (isForward) {
|
|
93
|
+
startX = fromBox.x + fromBox.width;
|
|
94
|
+
startY = fromBox.y + ARTBOARD_HEADER_OFFSET + fromBox.height * 0.45 + verticalSlot;
|
|
95
|
+
endX = toBox.x;
|
|
96
|
+
endY = toBox.y + ARTBOARD_HEADER_OFFSET + toBox.height * 0.35 + verticalSlot * 0.6;
|
|
97
|
+
|
|
98
|
+
const dx = Math.max(80, Math.abs(endX - startX) * 0.45);
|
|
99
|
+
pathD = `M ${startX} ${startY} C ${startX + dx} ${startY}, ${endX - dx} ${endY}, ${endX} ${endY}`;
|
|
100
|
+
midX = (startX + endX) / 2;
|
|
101
|
+
midY = (startY + endY) / 2;
|
|
102
|
+
} else {
|
|
103
|
+
// Return loop arched underneath the artboards
|
|
104
|
+
startX = fromBox.x + fromBox.width * 0.5;
|
|
105
|
+
startY = fromBox.y + ARTBOARD_HEADER_OFFSET + fromBox.height;
|
|
106
|
+
endX = toBox.x + toBox.width * 0.5;
|
|
107
|
+
endY = toBox.y + ARTBOARD_HEADER_OFFSET + toBox.height;
|
|
108
|
+
|
|
109
|
+
const archDepth = 110 + (idx % 3) * 35;
|
|
110
|
+
pathD = `M ${startX} ${startY} C ${startX} ${startY + archDepth}, ${endX} ${endY + archDepth}, ${endX} ${endY}`;
|
|
111
|
+
midX = (startX + endX) / 2;
|
|
112
|
+
midY = Math.max(startY, endY) + archDepth * 0.72;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const strokeColor = isHighlighted ? '#34D399' : '#6366F1';
|
|
116
|
+
const strokeOpacity = isHighlighted ? 0.95 : 0.55;
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<g key={edge.id}>
|
|
120
|
+
{/* Subtle glow path */}
|
|
121
|
+
{isHighlighted && (
|
|
122
|
+
<path
|
|
123
|
+
d={pathD}
|
|
124
|
+
fill="none"
|
|
125
|
+
stroke="#34D399"
|
|
126
|
+
strokeWidth={6}
|
|
127
|
+
strokeOpacity={0.18}
|
|
128
|
+
/>
|
|
129
|
+
)}
|
|
130
|
+
|
|
131
|
+
{/* Main cubic-bezier connector */}
|
|
132
|
+
<path
|
|
133
|
+
d={pathD}
|
|
134
|
+
fill="none"
|
|
135
|
+
stroke={strokeColor}
|
|
136
|
+
strokeWidth={isHighlighted ? 2.25 : 1.75}
|
|
137
|
+
strokeOpacity={strokeOpacity}
|
|
138
|
+
strokeDasharray={isForward ? undefined : '6 5'}
|
|
139
|
+
markerEnd={
|
|
140
|
+
isHighlighted ? 'url(#flow-arrow-active)' : 'url(#flow-arrow-default)'
|
|
141
|
+
}
|
|
142
|
+
/>
|
|
143
|
+
|
|
144
|
+
{/* Origin anchor dot */}
|
|
145
|
+
<circle
|
|
146
|
+
cx={startX}
|
|
147
|
+
cy={startY}
|
|
148
|
+
r={4}
|
|
149
|
+
fill="#0B0D12"
|
|
150
|
+
stroke={strokeColor}
|
|
151
|
+
strokeWidth={2}
|
|
152
|
+
/>
|
|
153
|
+
|
|
154
|
+
{/* Interactive Flow Label Pill */}
|
|
155
|
+
<foreignObject
|
|
156
|
+
x={midX - 75}
|
|
157
|
+
y={midY - 13}
|
|
158
|
+
width={150}
|
|
159
|
+
height={28}
|
|
160
|
+
style={{ pointerEvents: 'auto', overflow: 'visible' }}
|
|
161
|
+
>
|
|
162
|
+
<div
|
|
163
|
+
onClick={(e) => {
|
|
164
|
+
e.stopPropagation();
|
|
165
|
+
onSelectFlow?.(edge);
|
|
166
|
+
}}
|
|
167
|
+
title={`${edge.fromScreen} → ${edge.toScreen} (${edge.triggerLabel})`}
|
|
168
|
+
style={{
|
|
169
|
+
display: 'inline-flex',
|
|
170
|
+
alignItems: 'center',
|
|
171
|
+
justifyContent: 'center',
|
|
172
|
+
gap: 4,
|
|
173
|
+
padding: '3px 9px',
|
|
174
|
+
borderRadius: 999,
|
|
175
|
+
backgroundColor: isHighlighted ? '#064E3B' : '#131724',
|
|
176
|
+
border: `1px solid ${isHighlighted ? '#10B981' : '#312E81'}`,
|
|
177
|
+
color: isHighlighted ? '#A7F3D0' : '#C7D2FE',
|
|
178
|
+
fontSize: 10,
|
|
179
|
+
fontWeight: 600,
|
|
180
|
+
fontFamily: 'JetBrains Mono, monospace',
|
|
181
|
+
whiteSpace: 'nowrap',
|
|
182
|
+
cursor: 'pointer',
|
|
183
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.45)',
|
|
184
|
+
margin: '0 auto',
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
<span>{edge.fromScreen}</span>
|
|
188
|
+
<span>→</span>
|
|
189
|
+
<span>{edge.toScreen}</span>
|
|
190
|
+
</div>
|
|
191
|
+
</foreignObject>
|
|
192
|
+
</g>
|
|
193
|
+
);
|
|
194
|
+
})}
|
|
195
|
+
</svg>
|
|
196
|
+
);
|
|
197
|
+
};
|