@virentia/inspector 0.1.2

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/dist/index.cjs ADDED
@@ -0,0 +1,802 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let react = require("react");
3
+ let react_dom_client = require("react-dom/client");
4
+ let _mantine_core = require("@mantine/core");
5
+ let _virentia_core = require("@virentia/core");
6
+ let _virentia_core_devtools = require("@virentia/core/devtools");
7
+ let _virentia_react = require("@virentia/react");
8
+ let _xyflow_react = require("@xyflow/react");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region lib/client/shared/api/devtools.ts
11
+ const emptySnapshot = {
12
+ nodes: [],
13
+ edges: [],
14
+ scopes: [],
15
+ breakpoints: []
16
+ };
17
+ //#endregion
18
+ //#region lib/client/shared/graph/index.ts
19
+ function createReactiveSelection(snapshot, selectedNodeId) {
20
+ if (!selectedNodeId || !snapshot.nodes.some((node) => node.id === selectedNodeId)) return null;
21
+ const nextByNode = /* @__PURE__ */ new Map();
22
+ const previousByNode = /* @__PURE__ */ new Map();
23
+ const nodeIds = new Set([selectedNodeId]);
24
+ const edgeIds = /* @__PURE__ */ new Set();
25
+ for (const node of snapshot.nodes) {
26
+ nextByNode.set(node.id, []);
27
+ previousByNode.set(node.id, []);
28
+ }
29
+ for (const edge of snapshot.edges) {
30
+ nextByNode.get(edge.source)?.push(edge);
31
+ previousByNode.get(edge.target)?.push(edge);
32
+ }
33
+ walkForward(selectedNodeId, /* @__PURE__ */ new Set());
34
+ walkBackward(selectedNodeId, /* @__PURE__ */ new Set());
35
+ return {
36
+ nodeIds: [...nodeIds],
37
+ edgeIds: [...edgeIds]
38
+ };
39
+ function walkForward(nodeId, visited) {
40
+ if (visited.has(nodeId)) return;
41
+ visited.add(nodeId);
42
+ nodeIds.add(nodeId);
43
+ for (const edge of nextByNode.get(nodeId) ?? []) {
44
+ edgeIds.add(edge.id);
45
+ nodeIds.add(edge.target);
46
+ walkForward(edge.target, visited);
47
+ }
48
+ }
49
+ function walkBackward(nodeId, visited) {
50
+ if (visited.has(nodeId)) return;
51
+ visited.add(nodeId);
52
+ nodeIds.add(nodeId);
53
+ for (const edge of previousByNode.get(nodeId) ?? []) {
54
+ edgeIds.add(edge.id);
55
+ nodeIds.add(edge.source);
56
+ walkBackward(edge.source, visited);
57
+ }
58
+ }
59
+ }
60
+ function createFlowLayout(snapshot) {
61
+ const incoming = /* @__PURE__ */ new Map();
62
+ const nextByNode = /* @__PURE__ */ new Map();
63
+ for (const node of snapshot.nodes) {
64
+ incoming.set(node.id, 0);
65
+ nextByNode.set(node.id, []);
66
+ }
67
+ for (const edge of snapshot.edges) {
68
+ incoming.set(edge.target, (incoming.get(edge.target) ?? 0) + 1);
69
+ nextByNode.get(edge.source)?.push(edge.target);
70
+ }
71
+ const roots = snapshot.nodes.filter((node) => (incoming.get(node.id) ?? 0) === 0);
72
+ const queue = [...roots.length ? roots : snapshot.nodes].map((node) => node.id);
73
+ const levelByNode = /* @__PURE__ */ new Map();
74
+ for (const id of queue) levelByNode.set(id, 0);
75
+ while (queue.length) {
76
+ const nodeId = queue.shift();
77
+ const level = levelByNode.get(nodeId) ?? 0;
78
+ for (const next of nextByNode.get(nodeId) ?? []) {
79
+ const nextLevel = Math.max(levelByNode.get(next) ?? 0, level + 1);
80
+ if (nextLevel !== levelByNode.get(next)) {
81
+ levelByNode.set(next, nextLevel);
82
+ queue.push(next);
83
+ }
84
+ }
85
+ }
86
+ for (const node of snapshot.nodes) if (!levelByNode.has(node.id)) levelByNode.set(node.id, 0);
87
+ const levels = /* @__PURE__ */ new Map();
88
+ for (const node of snapshot.nodes) {
89
+ const level = levelByNode.get(node.id) ?? 0;
90
+ const group = levels.get(level) ?? [];
91
+ group.push(node);
92
+ levels.set(level, group);
93
+ }
94
+ return snapshot.nodes.map((node) => {
95
+ const level = levelByNode.get(node.id) ?? 0;
96
+ const index = (levels.get(level) ?? []).findIndex((item) => item.id === node.id);
97
+ return {
98
+ id: node.id,
99
+ x: level * 220,
100
+ y: index * 86
101
+ };
102
+ });
103
+ }
104
+ //#endregion
105
+ //#region lib/client/shared/ui/node-label.tsx
106
+ function NodeLabel(props) {
107
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Box, {
108
+ className: "virentia-inspector__node-label",
109
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
110
+ className: "virentia-inspector__node-name",
111
+ children: props.node.name
112
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
113
+ className: "virentia-inspector__node-type",
114
+ children: props.node.type
115
+ })]
116
+ });
117
+ }
118
+ //#endregion
119
+ //#region lib/client/shared/ui/flow-node.tsx
120
+ function UnitFlowNode(props) {
121
+ const data = props.data;
122
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
123
+ className: "virentia-inspector__flow-node",
124
+ children: [
125
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_xyflow_react.Handle, {
126
+ className: "virentia-inspector__handle",
127
+ id: "target",
128
+ position: _xyflow_react.Position.Left,
129
+ type: "target"
130
+ }),
131
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeLabel, { node: data.node }),
132
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_xyflow_react.Handle, {
133
+ className: "virentia-inspector__handle",
134
+ id: "source",
135
+ position: _xyflow_react.Position.Right,
136
+ type: "source"
137
+ })
138
+ ]
139
+ });
140
+ }
141
+ //#endregion
142
+ //#region lib/client/shared/ui/timeline-row.tsx
143
+ function TimelineRow(props) {
144
+ const color = props.event.breakpoint ? "red" : props.event.failed ? "red" : props.event.stopped ? "yellow" : "green";
145
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Box, {
146
+ className: "virentia-inspector__timeline-row",
147
+ children: [
148
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Group, {
149
+ justify: "space-between",
150
+ gap: "xs",
151
+ wrap: "nowrap",
152
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Text, {
153
+ size: "sm",
154
+ fw: 650,
155
+ lineClamp: 1,
156
+ children: props.event.nodeName
157
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Badge, {
158
+ color,
159
+ variant: "light",
160
+ children: props.event.breakpoint ? "break" : props.event.nodeType
161
+ })]
162
+ }),
163
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Text, {
164
+ size: "xs",
165
+ c: "dimmed",
166
+ children: [
167
+ props.event.scopeName ?? "No scope",
168
+ " · ",
169
+ props.event.duration.toFixed(1),
170
+ " ms"
171
+ ]
172
+ }),
173
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Text, {
174
+ className: "virentia-inspector__payload",
175
+ children: ["payload: ", props.event.payload.preview]
176
+ }),
177
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Text, {
178
+ className: "virentia-inspector__payload",
179
+ children: ["result: ", props.event.result.preview]
180
+ })
181
+ ]
182
+ });
183
+ }
184
+ //#endregion
185
+ //#region lib/client/modules/inspector/model.ts
186
+ const recordingChanged = (0, _virentia_core.event)("inspector.recordingChanged");
187
+ const $recording = (0, _virentia_core.store)(true, void 0, { name: "inspector.recording" });
188
+ (0, _virentia_core.reaction)({
189
+ on: recordingChanged,
190
+ name: "inspector.applyRecording",
191
+ run(value) {
192
+ $recording.value = value;
193
+ }
194
+ });
195
+ //#endregion
196
+ //#region lib/client/modules/inspector/InspectorApp.tsx
197
+ const nodeTypes = { unit: UnitFlowNode };
198
+ const rightPanelLimits = {
199
+ default: 300,
200
+ min: 210,
201
+ max: 430
202
+ };
203
+ function VirentiaInspector(props) {
204
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.MantineProvider, {
205
+ defaultColorScheme: "dark",
206
+ forceColorScheme: "dark",
207
+ theme: { primaryColor: "green" },
208
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_virentia_react.ScopeProvider, {
209
+ scope: (0, react.useMemo)(() => (0, _virentia_core.scope)(), []),
210
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_xyflow_react.ReactFlowProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InspectorSurface, { channel: props.channel }) })
211
+ })
212
+ });
213
+ }
214
+ function InspectorSurface(props) {
215
+ const [snapshot, setSnapshot] = (0, react.useState)(emptySnapshot);
216
+ const [timeline, setTimeline] = (0, react.useState)([]);
217
+ const [selectedNodeId, setSelectedNodeId] = (0, react.useState)(null);
218
+ const [breakpointIds, setBreakpointIds] = (0, react.useState)([]);
219
+ const [showIsolatedUnits, setShowIsolatedUnits] = (0, react.useState)(false);
220
+ const [triggerNodeId, setTriggerNodeId] = (0, react.useState)(null);
221
+ const [triggerStage, setTriggerStage] = (0, react.useState)(null);
222
+ const [triggerModalOpened, setTriggerModalOpened] = (0, react.useState)(false);
223
+ const [payloadText, setPayloadText] = (0, react.useState)("");
224
+ const [draftBreakpointIds, setDraftBreakpointIds] = (0, react.useState)([]);
225
+ const [breakpointSelectionActive, setBreakpointSelectionActive] = (0, react.useState)(false);
226
+ const [triggerResult, setTriggerResult] = (0, react.useState)(null);
227
+ const [triggerError, setTriggerError] = (0, react.useState)(null);
228
+ const [contextMenu, setContextMenu] = (0, react.useState)(null);
229
+ const [rightPanelWidth, setRightPanelWidth] = (0, react.useState)(rightPanelLimits.default);
230
+ const recording = (0, _virentia_react.useUnit)($recording);
231
+ const setRecording = (0, _virentia_react.useUnit)(recordingChanged);
232
+ const clientRef = (0, react.useRef)(null);
233
+ const recordingRef = (0, react.useRef)(recording);
234
+ const breakpointPickerInitialIdsRef = (0, react.useRef)([]);
235
+ const breakpointPickerReturnStageRef = (0, react.useRef)("payload");
236
+ recordingRef.current = recording;
237
+ (0, react.useEffect)(() => {
238
+ const client = (0, _virentia_core_devtools.connectVirentiaInspector)({ channel: props.channel });
239
+ clientRef.current = client;
240
+ const unsubscribe = client.subscribe((message) => {
241
+ if (message.type === "app") return;
242
+ if (message.type === "graph") {
243
+ setSnapshot(message.snapshot);
244
+ setBreakpointIds(message.snapshot.breakpoints);
245
+ return;
246
+ }
247
+ if (message.type === "timeline" && recordingRef.current) setTimeline((items) => [message.event, ...items].slice(0, 300));
248
+ });
249
+ client.requestGraph();
250
+ return () => {
251
+ unsubscribe();
252
+ client.dispose();
253
+ clientRef.current = null;
254
+ };
255
+ }, [props.channel]);
256
+ const visibleSnapshot = (0, react.useMemo)(() => showIsolatedUnits ? snapshot : hideIsolatedNodes(snapshot), [showIsolatedUnits, snapshot]);
257
+ const selectedFlow = (0, react.useMemo)(() => createReactiveSelection(visibleSnapshot, breakpointSelectionActive ? triggerNodeId : selectedNodeId), [
258
+ breakpointSelectionActive,
259
+ selectedNodeId,
260
+ triggerNodeId,
261
+ visibleSnapshot
262
+ ]);
263
+ const breakpointEligibleNodeIds = (0, react.useMemo)(() => new Set(selectedFlow?.nodeIds ?? []), [selectedFlow]);
264
+ const flow = useFlowGraph(visibleSnapshot, selectedFlow, {
265
+ breakpointIds: (0, react.useMemo)(() => new Set(draftBreakpointIds), [draftBreakpointIds]),
266
+ breakpointSelectionActive,
267
+ eligibleNodeIds: breakpointEligibleNodeIds
268
+ });
269
+ const triggerNode = (0, react.useMemo)(() => snapshot.nodes.find((node) => node.id === triggerNodeId) ?? null, [snapshot.nodes, triggerNodeId]);
270
+ const selectedBreakpointNodes = (0, react.useMemo)(() => draftBreakpointIds.flatMap((id) => {
271
+ const node = snapshot.nodes.find((item) => item.id === id);
272
+ return node ? [node] : [];
273
+ }), [draftBreakpointIds, snapshot.nodes]);
274
+ const breakpointSummary = selectedBreakpointNodes.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Group, {
275
+ gap: 4,
276
+ mt: 6,
277
+ children: selectedBreakpointNodes.map((node) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Badge, {
278
+ color: "red",
279
+ size: "xs",
280
+ variant: "light",
281
+ children: node.name
282
+ }, node.id))
283
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Text, {
284
+ size: "xs",
285
+ c: "dimmed",
286
+ mt: 6,
287
+ children: "None selected"
288
+ });
289
+ const shellStyle = (0, react.useMemo)(() => ({ "--virentia-right-panel-width": `${rightPanelWidth}px` }), [rightPanelWidth]);
290
+ (0, react.useEffect)(() => {
291
+ if (selectedNodeId && !visibleSnapshot.nodes.some((node) => node.id === selectedNodeId)) setSelectedNodeId(null);
292
+ }, [selectedNodeId, visibleSnapshot.nodes]);
293
+ const beginDrawerResize = (0, react.useCallback)((event) => {
294
+ event.preventDefault();
295
+ const startX = event.clientX;
296
+ const startWidth = rightPanelWidth;
297
+ const move = (moveEvent) => {
298
+ const nextWidth = startWidth - (moveEvent.clientX - startX);
299
+ setRightPanelWidth(Math.min(rightPanelLimits.max, Math.max(rightPanelLimits.min, nextWidth)));
300
+ };
301
+ const end = () => {
302
+ window.removeEventListener("pointermove", move);
303
+ window.removeEventListener("pointerup", end);
304
+ };
305
+ window.addEventListener("pointermove", move);
306
+ window.addEventListener("pointerup", end, { once: true });
307
+ }, [rightPanelWidth]);
308
+ const updateBreakpoints = (nodeIds) => {
309
+ setBreakpointIds(nodeIds);
310
+ clientRef.current?.setBreakpoints(nodeIds);
311
+ };
312
+ const openTriggerModal = (nodeId) => {
313
+ setContextMenu(null);
314
+ setTriggerNodeId(nodeId);
315
+ setPayloadText("");
316
+ setDraftBreakpointIds([]);
317
+ setTriggerResult(null);
318
+ setTriggerError(null);
319
+ setTriggerStage("payload");
320
+ setTriggerModalOpened(true);
321
+ };
322
+ const resetTriggerFlow = () => {
323
+ setTriggerNodeId(null);
324
+ setTriggerStage(null);
325
+ setTriggerModalOpened(false);
326
+ setPayloadText("");
327
+ setDraftBreakpointIds([]);
328
+ setBreakpointSelectionActive(false);
329
+ setTriggerResult(null);
330
+ setTriggerError(null);
331
+ };
332
+ const requestCloseTriggerFlow = () => {
333
+ setTriggerModalOpened(false);
334
+ setBreakpointSelectionActive(false);
335
+ };
336
+ const handleTriggerModalExited = () => {
337
+ if (!breakpointSelectionActive && !triggerModalOpened) resetTriggerFlow();
338
+ };
339
+ const parsePayload = () => {
340
+ try {
341
+ const payload = payloadText.trim() ? JSON.parse(payloadText) : void 0;
342
+ setTriggerError(null);
343
+ return {
344
+ ok: true,
345
+ payload
346
+ };
347
+ } catch (error) {
348
+ setTriggerError(error instanceof Error ? error.message : String(error));
349
+ return { ok: false };
350
+ }
351
+ };
352
+ const acceptPayload = () => {
353
+ if (!parsePayload().ok) return;
354
+ setTriggerStage("confirm");
355
+ };
356
+ const startBreakpointSelection = () => {
357
+ if (!triggerNodeId || !parsePayload().ok) return;
358
+ breakpointPickerInitialIdsRef.current = draftBreakpointIds;
359
+ breakpointPickerReturnStageRef.current = triggerStage ?? "payload";
360
+ setTriggerResult(null);
361
+ setTriggerModalOpened(false);
362
+ setBreakpointSelectionActive(true);
363
+ setSelectedNodeId(triggerNodeId);
364
+ };
365
+ const finishBreakpointSelection = (accepted) => {
366
+ if (!accepted) setDraftBreakpointIds(breakpointPickerInitialIdsRef.current);
367
+ setBreakpointSelectionActive(false);
368
+ setTriggerStage(breakpointPickerReturnStageRef.current);
369
+ setTriggerModalOpened(true);
370
+ };
371
+ const triggerSelectedNode = async () => {
372
+ if (!triggerNodeId) return;
373
+ const parsed = parsePayload();
374
+ if (!parsed.ok) return;
375
+ const previousBreakpoints = breakpointIds;
376
+ const scopeId = snapshot.scopes[0]?.id ?? null;
377
+ updateBreakpoints(draftBreakpointIds);
378
+ const result = await clientRef.current?.triggerUnit({
379
+ nodeId: triggerNodeId,
380
+ scopeId,
381
+ payload: parsed.payload
382
+ });
383
+ updateBreakpoints(previousBreakpoints);
384
+ setTriggerResult(result ?? { ok: false });
385
+ setTriggerStage("result");
386
+ };
387
+ const handleNodeClick = (_, node) => {
388
+ setContextMenu(null);
389
+ if (breakpointSelectionActive) {
390
+ if (!breakpointEligibleNodeIds.has(node.id)) return;
391
+ setDraftBreakpointIds((ids) => ids.includes(node.id) ? ids.filter((id) => id !== node.id) : [...ids, node.id]);
392
+ return;
393
+ }
394
+ setSelectedNodeId(node.id);
395
+ };
396
+ const handleNodeContextMenu = (event, node) => {
397
+ event.preventDefault();
398
+ if (breakpointSelectionActive) return;
399
+ setSelectedNodeId(node.id);
400
+ setContextMenu({
401
+ nodeId: node.id,
402
+ x: event.clientX,
403
+ y: event.clientY
404
+ });
405
+ };
406
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Box, {
407
+ className: "virentia-inspector",
408
+ children: [
409
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Box, {
410
+ className: "virentia-inspector__shell",
411
+ style: shellStyle,
412
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Box, {
413
+ className: "virentia-inspector__main",
414
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Box, {
415
+ className: "virentia-inspector__topbar",
416
+ children: [
417
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Group, {
418
+ gap: "xs",
419
+ wrap: "nowrap",
420
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Title, {
421
+ order: 4,
422
+ children: "Virentia"
423
+ })
424
+ }),
425
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Group, {
426
+ gap: "xs",
427
+ children: [
428
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Switch, {
429
+ checked: showIsolatedUnits,
430
+ color: "green",
431
+ label: "Show isolated",
432
+ onChange: (event) => setShowIsolatedUnits(event.currentTarget.checked),
433
+ size: "xs"
434
+ }),
435
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Badge, {
436
+ color: visibleSnapshot.nodes.length ? "green" : "gray",
437
+ variant: "light",
438
+ children: [visibleSnapshot.nodes.length, " units"]
439
+ }),
440
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Badge, {
441
+ variant: "light",
442
+ color: "gray",
443
+ children: [visibleSnapshot.edges.length, " links"]
444
+ }),
445
+ breakpointIds.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Badge, {
446
+ variant: "light",
447
+ color: "red",
448
+ children: [breakpointIds.length, " breakpoints"]
449
+ }) : null
450
+ ]
451
+ }),
452
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Button, {
453
+ color: "green",
454
+ size: "xs",
455
+ variant: "light",
456
+ onClick: () => clientRef.current?.requestGraph(),
457
+ children: "Refresh"
458
+ })
459
+ ]
460
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Box, {
461
+ className: "virentia-inspector__flow",
462
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_xyflow_react.ReactFlow, {
463
+ nodes: flow.nodes,
464
+ edges: flow.edges,
465
+ fitView: true,
466
+ nodeTypes,
467
+ minZoom: .15,
468
+ onNodeClick: handleNodeClick,
469
+ onNodeContextMenu: handleNodeContextMenu,
470
+ onPaneClick: () => {
471
+ setContextMenu(null);
472
+ if (!breakpointSelectionActive) setSelectedNodeId(null);
473
+ },
474
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_xyflow_react.Background, {
475
+ gap: 18,
476
+ color: "#424242"
477
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_xyflow_react.Controls, {})]
478
+ })
479
+ })]
480
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Stack, {
481
+ className: "virentia-inspector__drawer",
482
+ gap: "sm",
483
+ p: "sm",
484
+ children: [
485
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
486
+ "aria-label": "Resize tools panel",
487
+ className: "virentia-inspector__resize-handle virentia-inspector__resize-handle--right",
488
+ onPointerDown: beginDrawerResize,
489
+ type: "button"
490
+ }),
491
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Group, {
492
+ justify: "space-between",
493
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Text, {
494
+ fw: 650,
495
+ size: "sm",
496
+ children: "Call history"
497
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Group, {
498
+ gap: "xs",
499
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Switch, {
500
+ checked: recording,
501
+ color: "green",
502
+ label: "Record",
503
+ onChange: (event) => setRecording(event.currentTarget.checked),
504
+ size: "xs"
505
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Button, {
506
+ color: "gray",
507
+ size: "xs",
508
+ variant: "subtle",
509
+ onClick: () => setTimeline([]),
510
+ children: "Clear"
511
+ })]
512
+ })]
513
+ }),
514
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.ScrollArea, {
515
+ h: "calc(100vh - 66px)",
516
+ type: "auto",
517
+ children: timeline.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimelineRow, { event: item }, item.id))
518
+ })
519
+ ]
520
+ })]
521
+ }),
522
+ contextMenu ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Paper, {
523
+ className: "virentia-inspector__context-menu",
524
+ shadow: "md",
525
+ style: {
526
+ left: contextMenu.x,
527
+ top: contextMenu.y
528
+ },
529
+ withBorder: true,
530
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
531
+ className: "virentia-inspector__context-menu-item",
532
+ onClick: () => openTriggerModal(contextMenu.nodeId),
533
+ type: "button",
534
+ children: "Trigger unit"
535
+ })
536
+ }) : null,
537
+ breakpointSelectionActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Box, {
538
+ className: "virentia-inspector__bottom-toolbar",
539
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Text, {
540
+ fw: 650,
541
+ size: "sm",
542
+ children: "Select breakpoints"
543
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Text, {
544
+ size: "xs",
545
+ c: "dimmed",
546
+ children: [
547
+ "Only units from the ",
548
+ triggerNode?.name ?? "",
549
+ " chain are available"
550
+ ]
551
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Group, {
552
+ gap: "xs",
553
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Button, {
554
+ color: "gray",
555
+ size: "xs",
556
+ variant: "subtle",
557
+ onClick: () => finishBreakpointSelection(false),
558
+ children: "Cancel"
559
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Button, {
560
+ color: "green",
561
+ size: "xs",
562
+ onClick: () => finishBreakpointSelection(true),
563
+ children: "Apply"
564
+ })]
565
+ })]
566
+ }) : null,
567
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Modal, {
568
+ centered: true,
569
+ classNames: {
570
+ body: "virentia-inspector__modal-body",
571
+ content: "virentia-inspector__modal-content",
572
+ header: "virentia-inspector__modal-header",
573
+ title: "virentia-inspector__modal-title"
574
+ },
575
+ opened: triggerModalOpened && triggerStage !== null,
576
+ onClose: requestCloseTriggerFlow,
577
+ onExitTransitionEnd: handleTriggerModalExited,
578
+ padding: "sm",
579
+ radius: "sm",
580
+ size: 420,
581
+ title: triggerNode ? `Trigger: ${triggerNode.name}` : "Trigger unit",
582
+ children: [
583
+ triggerStage === "payload" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Stack, {
584
+ gap: "xs",
585
+ children: [
586
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Textarea, {
587
+ autosize: true,
588
+ minRows: 5,
589
+ label: "Payload JSON",
590
+ size: "xs",
591
+ value: payloadText,
592
+ onChange: (event) => setPayloadText(event.currentTarget.value)
593
+ }),
594
+ triggerError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Text, {
595
+ size: "xs",
596
+ c: "red",
597
+ children: triggerError
598
+ }) : null,
599
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Paper, {
600
+ className: "virentia-inspector__modal-section",
601
+ withBorder: true,
602
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Group, {
603
+ align: "flex-start",
604
+ justify: "space-between",
605
+ gap: "xs",
606
+ wrap: "nowrap",
607
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Text, {
608
+ fw: 650,
609
+ size: "xs",
610
+ children: "Breakpoints"
611
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Text, {
612
+ size: "xs",
613
+ c: "dimmed",
614
+ children: "The chain will stop after the selected units"
615
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Button, {
616
+ color: "green",
617
+ size: "xs",
618
+ variant: "light",
619
+ onClick: startBreakpointSelection,
620
+ children: "Select"
621
+ })]
622
+ }), breakpointSummary]
623
+ }),
624
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Group, {
625
+ justify: "space-between",
626
+ mt: 4,
627
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Button, {
628
+ color: "gray",
629
+ size: "xs",
630
+ variant: "subtle",
631
+ onClick: requestCloseTriggerFlow,
632
+ children: "Cancel"
633
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Button, {
634
+ color: "green",
635
+ size: "xs",
636
+ onClick: acceptPayload,
637
+ children: "Continue"
638
+ })]
639
+ })
640
+ ]
641
+ }) : null,
642
+ triggerStage === "confirm" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Stack, {
643
+ gap: "xs",
644
+ children: [
645
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Text, {
646
+ size: "xs",
647
+ c: "dimmed",
648
+ children: "Payload"
649
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Text, {
650
+ className: "virentia-inspector__payload",
651
+ children: payloadText.trim() || "undefined"
652
+ })] }),
653
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Paper, {
654
+ className: "virentia-inspector__modal-section",
655
+ withBorder: true,
656
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Group, {
657
+ align: "flex-start",
658
+ justify: "space-between",
659
+ gap: "xs",
660
+ wrap: "nowrap",
661
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Text, {
662
+ fw: 650,
663
+ size: "xs",
664
+ children: "Breakpoints"
665
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Text, {
666
+ size: "xs",
667
+ c: "dimmed",
668
+ children: "You can change them before triggering"
669
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Button, {
670
+ color: "green",
671
+ size: "xs",
672
+ variant: "light",
673
+ onClick: startBreakpointSelection,
674
+ children: "Select"
675
+ })]
676
+ }), breakpointSummary]
677
+ }),
678
+ triggerError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Text, {
679
+ size: "xs",
680
+ c: "red",
681
+ children: triggerError
682
+ }) : null,
683
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Group, {
684
+ justify: "space-between",
685
+ mt: 4,
686
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Button, {
687
+ color: "gray",
688
+ size: "xs",
689
+ variant: "subtle",
690
+ onClick: requestCloseTriggerFlow,
691
+ children: "Cancel"
692
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Button, {
693
+ color: "green",
694
+ size: "xs",
695
+ onClick: () => void triggerSelectedNode(),
696
+ children: "Trigger"
697
+ })]
698
+ })
699
+ ]
700
+ }) : null,
701
+ triggerStage === "result" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mantine_core.Stack, {
702
+ gap: "xs",
703
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Badge, {
704
+ color: triggerResult?.ok ? "green" : "red",
705
+ size: "xs",
706
+ variant: "light",
707
+ children: triggerResult?.ok ? "Triggered successfully" : triggerResult?.error?.preview ?? "Error"
708
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Group, {
709
+ justify: "end",
710
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mantine_core.Button, {
711
+ color: "green",
712
+ size: "xs",
713
+ onClick: requestCloseTriggerFlow,
714
+ children: "Close"
715
+ })
716
+ })]
717
+ }) : null
718
+ ]
719
+ })
720
+ ]
721
+ });
722
+ }
723
+ function useFlowGraph(snapshot, selectedFlow, options) {
724
+ const selectedNodeIds = (0, react.useMemo)(() => new Set(selectedFlow?.nodeIds ?? []), [selectedFlow]);
725
+ const selectedEdgeIds = (0, react.useMemo)(() => new Set(selectedFlow?.edgeIds ?? []), [selectedFlow]);
726
+ return (0, react.useMemo)(() => {
727
+ const layout = new Map(createFlowLayout(snapshot).map((item) => [item.id, item]));
728
+ return {
729
+ nodes: snapshot.nodes.map((node) => {
730
+ const position = layout.get(node.id) ?? {
731
+ x: 0,
732
+ y: 0
733
+ };
734
+ const active = selectedNodeIds.has(node.id);
735
+ const eligible = options.eligibleNodeIds.has(node.id);
736
+ const breakpoint = options.breakpointIds.has(node.id);
737
+ return {
738
+ id: node.id,
739
+ className: [options.breakpointSelectionActive && !eligible ? "is-dimmed" : "", breakpoint ? "is-breakpoint" : ""].filter(Boolean).join(" "),
740
+ position: {
741
+ x: position.x,
742
+ y: position.y
743
+ },
744
+ type: "unit",
745
+ sourcePosition: _xyflow_react.Position.Right,
746
+ targetPosition: _xyflow_react.Position.Left,
747
+ selected: active,
748
+ data: { node }
749
+ };
750
+ }),
751
+ edges: snapshot.edges.map((edge) => {
752
+ const active = selectedEdgeIds.has(edge.id);
753
+ const owner = edge.kind === "owner";
754
+ const dimmed = options.breakpointSelectionActive && (!options.eligibleNodeIds.has(edge.source) || !options.eligibleNodeIds.has(edge.target));
755
+ return {
756
+ id: edge.id,
757
+ source: edge.source,
758
+ sourceHandle: "source",
759
+ target: edge.target,
760
+ targetHandle: "target",
761
+ animated: active,
762
+ type: "smoothstep",
763
+ style: {
764
+ opacity: dimmed ? .22 : 1,
765
+ stroke: active ? "var(--virentia-accent)" : owner ? "#424242" : "#696969",
766
+ strokeDasharray: owner ? "6 4" : void 0,
767
+ strokeWidth: active ? 3 : owner ? 1.1 : 1.4
768
+ }
769
+ };
770
+ })
771
+ };
772
+ }, [
773
+ options,
774
+ selectedEdgeIds,
775
+ selectedNodeIds,
776
+ snapshot
777
+ ]);
778
+ }
779
+ function hideIsolatedNodes(snapshot) {
780
+ const connectedNodeIds = /* @__PURE__ */ new Set();
781
+ for (const edge of snapshot.edges) if (edge.kind === "reactive") {
782
+ connectedNodeIds.add(edge.source);
783
+ connectedNodeIds.add(edge.target);
784
+ }
785
+ for (const node of snapshot.nodes) if (node.parentId && connectedNodeIds.has(node.id)) connectedNodeIds.add(node.parentId);
786
+ return {
787
+ ...snapshot,
788
+ breakpoints: snapshot.breakpoints.filter((id) => connectedNodeIds.has(id)),
789
+ edges: snapshot.edges.filter((edge) => connectedNodeIds.has(edge.source) && connectedNodeIds.has(edge.target)),
790
+ nodes: snapshot.nodes.filter((node) => connectedNodeIds.has(node.id))
791
+ };
792
+ }
793
+ //#endregion
794
+ //#region lib/client/index.ts
795
+ function mountVirentiaInspector(container, props = {}) {
796
+ const root = (0, react_dom_client.createRoot)(container);
797
+ root.render((0, react.createElement)(VirentiaInspector, props));
798
+ return root;
799
+ }
800
+ //#endregion
801
+ exports.VirentiaInspector = VirentiaInspector;
802
+ exports.mountVirentiaInspector = mountVirentiaInspector;