@hlang-tech/editor-ui 0.2.2 → 0.4.0

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/lib/index.js CHANGED
@@ -25,30 +25,45 @@ __export(index_exports, {
25
25
  CanvasChrome: () => CanvasChrome,
26
26
  CanvasDock: () => CanvasDock,
27
27
  Cheatsheet: () => Cheatsheet,
28
+ ConnectionInspector: () => ConnectionInspector,
29
+ DEFAULT_EDGE_CAPACITY: () => DEFAULT_EDGE_CAPACITY,
30
+ DEFAULT_MIN_CANVAS_HEIGHT: () => DEFAULT_MIN_CANVAS_HEIGHT,
28
31
  DiagnosticBadge: () => DiagnosticBadge,
29
32
  EditorShell: () => EditorShell,
30
33
  EditorUIThemeProvider: () => EditorUIThemeProvider,
31
34
  EmptyState: () => EmptyState,
35
+ FOLLOW_SLACK_PX: () => FOLLOW_SLACK_PX,
36
+ FOOTER_KEY_STEP: () => FOOTER_KEY_STEP,
32
37
  FlowEdge: () => FlowEdge,
38
+ FooterResizer: () => FooterResizer,
33
39
  HlangNode: () => HlangNode,
34
40
  InspectorPanel: () => InspectorPanel,
35
41
  KIND_ROLE: () => KIND_ROLE,
36
42
  KIND_TOKEN: () => KIND_TOKEN,
43
+ LivePulse: () => LivePulse,
37
44
  LiveSignalProvider: () => LiveSignalProvider,
38
45
  LiveSignalStore: () => LiveSignalStore,
39
46
  NodeBodySlot: () => NodeBodySlot,
40
47
  NodeChrome: () => NodeChrome,
41
48
  NodeContextMenu: () => NodeContextMenu,
42
49
  NodeInteractionProvider: () => NodeInteractionProvider,
50
+ NodeRunStats: () => NodeRunStats,
43
51
  NodeSearch: () => NodeSearch,
44
52
  OutlineTree: () => OutlineTree,
53
+ PREVIEW_LENGTH: () => PREVIEW_LENGTH,
45
54
  ParamForm: () => ParamForm,
46
55
  PillNodeChrome: () => PillNodeChrome,
47
56
  PortRow: () => PortRow,
48
57
  PositionPanel: () => PositionPanel,
49
58
  PreviewPanel: () => PreviewPanel,
59
+ RUN_LOG_CAPACITY: () => RUN_LOG_CAPACITY,
50
60
  RailTabs: () => RailTabs,
51
61
  RunButton: () => RunButton,
62
+ RunLog: () => RunLog,
63
+ RunTrace: () => RunTrace,
64
+ RunTraceProvider: () => RunTraceProvider,
65
+ SELF_LOOP_CLEARANCE: () => SELF_LOOP_CLEARANCE,
66
+ SELF_LOOP_RADIUS: () => SELF_LOOP_RADIUS,
52
67
  SignalBar: () => SignalBar,
53
68
  TOKEN_NAMES: () => TOKEN_NAMES,
54
69
  ToastProvider: () => ToastProvider,
@@ -57,26 +72,47 @@ __export(index_exports, {
57
72
  ToolbarGhostButton: () => ToolbarGhostButton,
58
73
  ToolbarStat: () => ToolbarStat,
59
74
  UndoRedo: () => UndoRedo,
75
+ boundsFields: () => boundsFields,
76
+ capacityLabel: () => capacityLabel,
77
+ clampFooter: () => clampFooter,
60
78
  connectableCandidates: () => connectableCandidates,
61
79
  createHlangFlowTypes: () => createHlangFlowTypes,
62
80
  createTheme: () => createTheme,
81
+ defaultLiveDeps: () => defaultLiveDeps,
63
82
  deriveParamSchema: () => deriveParamSchema,
64
83
  descriptorCandidates: () => descriptorCandidates,
84
+ footerBounds: () => footerBounds,
85
+ formatDuration: () => formatDuration,
86
+ formatElapsed: () => formatElapsed,
65
87
  fuzzyRank: () => fuzzyRank,
66
88
  fuzzyScore: () => fuzzyScore,
89
+ involves: () => involves,
67
90
  isEditableTarget: () => isEditableTarget,
91
+ nextBounds: () => nextBounds,
92
+ previewValue: () => previewValue,
93
+ resizeByKey: () => resizeByKey,
68
94
  searchNodes: () => searchNodes,
95
+ selfLoopPath: () => selfLoopPath,
96
+ toggleCollapsed: () => toggleCollapsed,
69
97
  tokenVar: () => tokenVar,
98
+ useEdgeBounds: () => useEdgeBounds,
70
99
  useEdgeFlowing: () => useEdgeFlowing,
100
+ useEdgePulse: () => useEdgePulse,
71
101
  useEditorHotkeys: () => useEditorHotkeys,
102
+ useFollowScroll: () => useFollowScroll,
103
+ useFooterResize: () => useFooterResize,
72
104
  useLiveSignalStore: () => useLiveSignalStore,
73
105
  useLiveSignals: () => useLiveSignals,
74
106
  useNodeContextMenu: () => useNodeContextMenu,
75
107
  useNodeInteraction: () => useNodeInteraction,
76
108
  useNodeLiveState: () => useNodeLiveState,
77
109
  useNodeParams: () => useNodeParams,
110
+ useNodePulse: () => useNodePulse,
78
111
  useNodeSearch: () => useNodeSearch,
79
112
  useNodeSearchTriggers: () => useNodeSearchTriggers,
113
+ useRunTrace: () => useRunTrace,
114
+ useRunTraceStore: () => useRunTraceStore,
115
+ useRunTraceVersion: () => useRunTraceVersion,
80
116
  useToast: () => useToast
81
117
  });
82
118
  module.exports = __toCommonJS(index_exports);
@@ -92,11 +128,18 @@ var TOKEN_NAMES = [
92
128
  "hl-silver",
93
129
  "hl-silver-hi",
94
130
  "hl-silver-lo",
131
+ "hl-glass",
132
+ "hl-glass-2",
133
+ "hl-glass-blur",
134
+ "hl-glass-sat",
135
+ "hl-glass-edge",
136
+ "hl-glass-edge-hi",
95
137
  "hl-spark",
96
138
  "hl-r",
97
139
  "hl-t",
98
140
  "hl-w",
99
141
  "hl-error",
142
+ "hl-warning",
100
143
  "hl-ink",
101
144
  "hl-ink-2",
102
145
  "hl-ink-3",
@@ -109,7 +152,9 @@ var TOKEN_NAMES = [
109
152
  "hl-shadow-node",
110
153
  "hl-shadow-glass",
111
154
  "hl-glow-silver",
112
- "hl-ease"
155
+ "hl-ease",
156
+ "hl-activity",
157
+ "hl-cost"
113
158
  ];
114
159
  function tokenVar(name) {
115
160
  return `var(--${name})`;
@@ -140,12 +185,12 @@ function createTheme(overrides) {
140
185
  var import_jsx_runtime = require("react/jsx-runtime");
141
186
  function EditorUIThemeProvider({
142
187
  theme,
143
- className,
188
+ className: className2,
144
189
  style,
145
190
  children
146
191
  }) {
147
192
  const themeStyle = theme ? createTheme(theme) : void 0;
148
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: { ...themeStyle, ...style }, children });
193
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: className2, style: { ...themeStyle, ...style }, children });
149
194
  }
150
195
 
151
196
  // src/nodes/SignalBar.tsx
@@ -195,7 +240,10 @@ var ACTIVE_WINDOW_MS = 700;
195
240
  function portKey(nodeId, portId) {
196
241
  return `${nodeId}@${portId}`;
197
242
  }
198
- var defaultDeps = {
243
+ function connectionKey(sourceNodeId, sourcePortId, targetNodeId, targetPortId) {
244
+ return `${portKey(sourceNodeId, sourcePortId)}->${portKey(targetNodeId, targetPortId)}`;
245
+ }
246
+ var defaultLiveDeps = {
199
247
  now: () => typeof performance !== "undefined" ? performance.now() : Date.now(),
200
248
  schedule: (cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : (
201
249
  // `window.setTimeout` returns a numeric handle (the DOM overload),
@@ -206,11 +254,13 @@ var defaultDeps = {
206
254
  cancel: (h) => typeof cancelAnimationFrame !== "undefined" ? cancelAnimationFrame(h) : window.clearTimeout(h)
207
255
  };
208
256
  var LiveSignalStore = class {
209
- constructor(deps = defaultDeps) {
257
+ constructor(deps = defaultLiveDeps) {
210
258
  /** nodeId -> last-active timestamp. */
211
259
  this.nodeActiveAt = /* @__PURE__ */ new Map();
212
260
  /** nodeId set that errored during this run. */
213
261
  this.erroredNodes = /* @__PURE__ */ new Set();
262
+ /** nodeId set that finished during this run (RFC 0001). */
263
+ this.finishedNodes = /* @__PURE__ */ new Set();
214
264
  /** edgeId -> last-flowing timestamp. */
215
265
  this.edgeFlowAt = /* @__PURE__ */ new Map();
216
266
  /** Per-id listeners. */
@@ -221,12 +271,21 @@ var LiveSignalStore = class {
221
271
  this.lastEdgeFlowing = /* @__PURE__ */ new Map();
222
272
  /** Edges sourced by a given (nodeId@portId), for pulse -> edge mapping. */
223
273
  this.edgesBySourcePort = /* @__PURE__ */ new Map();
274
+ /** Edges by the connection they carry (source port -> target port). */
275
+ this.edgesByConnection = /* @__PURE__ */ new Map();
276
+ /** How many messages each node has handled or sent, and each edge carried, this run. */
277
+ this.nodePulse = /* @__PURE__ */ new Map();
278
+ this.edgePulse = /* @__PURE__ */ new Map();
279
+ /** The pulse counts last notified, so a new message notifies even while the state stays active. */
280
+ this.lastNodePulse = /* @__PURE__ */ new Map();
281
+ this.lastEdgePulse = /* @__PURE__ */ new Map();
224
282
  this.rafHandle = null;
225
283
  this.running = false;
226
284
  this.deps = deps;
227
285
  }
228
286
  /** Provide the current edge index (call whenever edges change). */
229
287
  setEdgeIndex(edges) {
288
+ var _a;
230
289
  const next = /* @__PURE__ */ new Map();
231
290
  for (const edge of edges) {
232
291
  const key = portKey(edge.sourceNodeId, edge.sourcePortId);
@@ -238,6 +297,44 @@ var LiveSignalStore = class {
238
297
  }
239
298
  }
240
299
  this.edgesBySourcePort = next;
300
+ const byConnection = /* @__PURE__ */ new Map();
301
+ for (const edge of edges) {
302
+ if (edge.targetNodeId !== void 0 && edge.targetPortId !== void 0) {
303
+ const key = connectionKey(edge.sourceNodeId, edge.sourcePortId, edge.targetNodeId, edge.targetPortId);
304
+ byConnection.set(key, [...(_a = byConnection.get(key)) != null ? _a : [], edge]);
305
+ }
306
+ }
307
+ this.edgesByConnection = byConnection;
308
+ }
309
+ /**
310
+ * Record a message delivered on a connection, when it happens: its source
311
+ * and its target each pulse once (each ran), and so does the edge that
312
+ * carried it, and only that edge. Subscribers are notified on the next
313
+ * frame, once however many messages arrived in it.
314
+ */
315
+ markDelivery(signal) {
316
+ var _a;
317
+ const t = this.deps.now();
318
+ for (const nodeId of [signal.sourceNodeId, signal.targetNodeId]) {
319
+ this.nodeActiveAt.set(nodeId, t);
320
+ this.nodePulse.set(nodeId, this.getNodePulse(nodeId) + 1);
321
+ }
322
+ const key = connectionKey(signal.sourceNodeId, signal.sourcePortId, signal.targetNodeId, signal.targetPortId);
323
+ for (const edge of (_a = this.edgesByConnection.get(key)) != null ? _a : []) {
324
+ this.edgeFlowAt.set(edge.id, t);
325
+ this.edgePulse.set(edge.id, this.getEdgePulse(edge.id) + 1);
326
+ }
327
+ this.ensureLoop();
328
+ }
329
+ /** How many messages a node has handled or sent this run; it changes each time the node runs. */
330
+ getNodePulse(nodeId) {
331
+ var _a;
332
+ return (_a = this.nodePulse.get(nodeId)) != null ? _a : 0;
333
+ }
334
+ /** How many messages an edge has carried this run. */
335
+ getEdgePulse(edgeId) {
336
+ var _a;
337
+ return (_a = this.edgePulse.get(edgeId)) != null ? _a : 0;
241
338
  }
242
339
  /** Record a node emitting on an output port (drives node + edge activity). */
243
340
  markActivity(nodeId, portId) {
@@ -251,6 +348,11 @@ var LiveSignalStore = class {
251
348
  }
252
349
  this.ensureLoop();
253
350
  }
351
+ /** Record a node finishing (RFC 0001): shown once its last flash has faded. */
352
+ markFinished(nodeId) {
353
+ this.finishedNodes.add(nodeId);
354
+ this.ensureLoop();
355
+ }
254
356
  /** Record a node erroring (sticky for the run). */
255
357
  markError(nodeId) {
256
358
  this.erroredNodes.add(nodeId);
@@ -260,7 +362,10 @@ var LiveSignalStore = class {
260
362
  reset() {
261
363
  this.nodeActiveAt.clear();
262
364
  this.erroredNodes.clear();
365
+ this.finishedNodes.clear();
263
366
  this.edgeFlowAt.clear();
367
+ this.nodePulse.clear();
368
+ this.edgePulse.clear();
264
369
  this.stopLoop();
265
370
  this.flushNotifications();
266
371
  }
@@ -281,7 +386,7 @@ var LiveSignalStore = class {
281
386
  if (at !== void 0 && this.deps.now() - at <= ACTIVE_WINDOW_MS) {
282
387
  return "active";
283
388
  }
284
- return "idle";
389
+ return this.finishedNodes.has(nodeId) ? "finished" : "idle";
285
390
  }
286
391
  /** Whether an edge is currently flowing. */
287
392
  isEdgeFlowing(edgeId) {
@@ -300,9 +405,14 @@ var LiveSignalStore = class {
300
405
  }
301
406
  };
302
407
  }
408
+ /**
409
+ * Arms the frame loop if it is not running. Signals only record state; the
410
+ * next frame notifies, so a burst of signals costs one pass over the
411
+ * subscribers per frame, not one per signal, and every change shows within a
412
+ * frame.
413
+ */
303
414
  ensureLoop() {
304
415
  if (this.running) {
305
- this.flushNotifications();
306
416
  return;
307
417
  }
308
418
  this.running = true;
@@ -336,8 +446,10 @@ var LiveSignalStore = class {
336
446
  if (state !== "idle") {
337
447
  anyLive = anyLive || state === "active";
338
448
  }
339
- if (this.lastNodeState.get(nodeId) !== state) {
449
+ const pulse = this.getNodePulse(nodeId);
450
+ if (this.lastNodeState.get(nodeId) !== state || this.lastNodePulse.get(nodeId) !== pulse) {
340
451
  this.lastNodeState.set(nodeId, state);
452
+ this.lastNodePulse.set(nodeId, pulse);
341
453
  for (const listener of listeners) {
342
454
  listener();
343
455
  }
@@ -346,8 +458,10 @@ var LiveSignalStore = class {
346
458
  for (const [edgeId, listeners] of this.edgeListeners) {
347
459
  const flowing = this.isEdgeFlowing(edgeId);
348
460
  anyLive = anyLive || flowing;
349
- if (this.lastEdgeFlowing.get(edgeId) !== flowing) {
461
+ const pulse = this.getEdgePulse(edgeId);
462
+ if (this.lastEdgeFlowing.get(edgeId) !== flowing || this.lastEdgePulse.get(edgeId) !== pulse) {
350
463
  this.lastEdgeFlowing.set(edgeId, flowing);
464
+ this.lastEdgePulse.set(edgeId, pulse);
351
465
  for (const listener of listeners) {
352
466
  listener();
353
467
  }
@@ -375,6 +489,22 @@ function useNodeLiveState(nodeId) {
375
489
  () => "idle"
376
490
  );
377
491
  }
492
+ function useNodePulse(nodeId) {
493
+ const store = (0, import_react2.useContext)(LiveSignalContext);
494
+ return (0, import_react2.useSyncExternalStore)(
495
+ (onChange) => store === null ? () => void 0 : store.subscribeNode(nodeId, onChange),
496
+ () => store === null ? 0 : store.getNodePulse(nodeId),
497
+ () => 0
498
+ );
499
+ }
500
+ function useEdgePulse(edgeId) {
501
+ const store = (0, import_react2.useContext)(LiveSignalContext);
502
+ return (0, import_react2.useSyncExternalStore)(
503
+ (onChange) => store === null ? () => void 0 : store.subscribeEdge(edgeId, onChange),
504
+ () => store === null ? 0 : store.getEdgePulse(edgeId),
505
+ () => 0
506
+ );
507
+ }
378
508
  function useEdgeFlowing(edgeId) {
379
509
  const store = (0, import_react2.useContext)(LiveSignalContext);
380
510
  return (0, import_react2.useSyncExternalStore)(
@@ -384,8 +514,15 @@ function useEdgeFlowing(edgeId) {
384
514
  );
385
515
  }
386
516
 
387
- // src/nodes/AssetNode.tsx
517
+ // src/live/LivePulse.tsx
388
518
  var import_jsx_runtime6 = require("react/jsx-runtime");
519
+ function LivePulse({ nodeId }) {
520
+ const pulse = useNodePulse(nodeId);
521
+ return pulse === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "hl-live-pulse", "aria-hidden": "true" }, pulse);
522
+ }
523
+
524
+ // src/nodes/AssetNode.tsx
525
+ var import_jsx_runtime7 = require("react/jsx-runtime");
389
526
  function stringParam(node, key) {
390
527
  const v = node.params[key];
391
528
  return typeof v === "string" ? v : void 0;
@@ -397,22 +534,24 @@ function AssetNodeChrome({ node, selected }) {
397
534
  const role = stringParam(node, "role");
398
535
  const live = useNodeLiveState(node.id);
399
536
  const outputs = node.ports.filter((p) => p.direction === "O" && !p.isError);
400
- const className = [
537
+ const className2 = [
401
538
  "hl-asset",
402
539
  selected ? "is-selected" : "",
403
540
  live === "active" ? "is-active" : "",
404
- live === "error" ? "is-live-error" : ""
541
+ live === "error" ? "is-live-error" : "",
542
+ live === "finished" ? "is-finished" : ""
405
543
  ].filter(Boolean).join(" ");
406
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className, "data-kind": node.kind, children: [
407
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hl-asset-frame", children: [
408
- imageUrl.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { className: "hl-asset-img", src: imageUrl, alt: caption, draggable: false }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hl-asset-placeholder", "aria-hidden": true }),
409
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hl-asset-scrim" }),
410
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hl-asset-caption", children: [
411
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "hl-asset-name", children: caption }),
412
- role !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "hl-asset-role hl-mono", children: role })
544
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: className2, "data-kind": node.kind, children: [
545
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LivePulse, { nodeId: node.id }),
546
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hl-asset-frame", children: [
547
+ imageUrl.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { className: "hl-asset-img", src: imageUrl, alt: caption, draggable: false }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hl-asset-placeholder", "aria-hidden": true }),
548
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hl-asset-scrim" }),
549
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hl-asset-caption", children: [
550
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "hl-asset-name", children: caption }),
551
+ role !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "hl-asset-role hl-mono", children: role })
413
552
  ] })
414
553
  ] }),
415
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "hl-asset-ports", children: outputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PortRow, { port }, port.id)) })
554
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: "hl-asset-ports", children: outputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PortRow, { port }, port.id)) })
416
555
  ] });
417
556
  }
418
557
 
@@ -420,9 +559,9 @@ function AssetNodeChrome({ node, selected }) {
420
559
  var import_react4 = require("react");
421
560
 
422
561
  // src/icons/node-icons.tsx
423
- var import_jsx_runtime7 = require("react/jsx-runtime");
562
+ var import_jsx_runtime8 = require("react/jsx-runtime");
424
563
  function svg(size, children) {
425
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
564
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
426
565
  "svg",
427
566
  {
428
567
  width: size,
@@ -440,49 +579,49 @@ function svg(size, children) {
440
579
  }
441
580
  var BY_CODE = {
442
581
  "image-source": (s) => svg(s, [
443
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2.5" }, "r"),
444
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "8.5", cy: "9", r: "1.6" }, "c"),
445
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 17l5-5 4 4 3-2 4 4" }, "p")
582
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2.5" }, "r"),
583
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "8.5", cy: "9", r: "1.6" }, "c"),
584
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M4 17l5-5 4 4 3-2 4 4" }, "p")
446
585
  ]),
447
- "text-source": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5 6h14M12 6v12M9 18h6" }, "p")]),
586
+ "text-source": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M5 6h14M12 6v12M9 18h6" }, "p")]),
448
587
  "interval-source": (s) => svg(s, [
449
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "12", r: "8" }, "c"),
450
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 8v4l3 2" }, "h")
588
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "12", r: "8" }, "c"),
589
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 8v4l3 2" }, "h")
451
590
  ]),
452
591
  "range-source": (s) => svg(s, [
453
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 12h16" }, "l"),
454
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "6", cy: "12", r: "2" }, "a"),
455
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "18", cy: "12", r: "2" }, "b")
592
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M4 12h16" }, "l"),
593
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "6", cy: "12", r: "2" }, "a"),
594
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "18", cy: "12", r: "2" }, "b")
456
595
  ]),
457
- "constant-source": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M6 12h12M9 8l-3 4 3 4M15 8l3 4-3 4" }, "p")]),
596
+ "constant-source": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M6 12h12M9 8l-3 4 3 4M15 8l3 4-3 4" }, "p")]),
458
597
  "map-scale": (s) => svg(s, [
459
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 20V8l8-4" }, "a"),
460
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 8l8 4 8-4M12 12v8" }, "b")
598
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M4 20V8l8-4" }, "a"),
599
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M4 8l8 4 8-4M12 12v8" }, "b")
461
600
  ]),
462
- uppercase: (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5 18L9 6l4 12M6.5 14h5M15 10v8M15 10c3 0 3 4 0 4" }, "p")]),
463
- "filter-threshold": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 5h16l-6 7v6l-4-2v-4z" }, "p")]),
464
- "scan-sum": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M17 6H7l6 6-6 6h10" }, "p")]),
601
+ uppercase: (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M5 18L9 6l4 12M6.5 14h5M15 10v8M15 10c3 0 3 4 0 4" }, "p")]),
602
+ "filter-threshold": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M4 5h16l-6 7v6l-4-2v-4z" }, "p")]),
603
+ "scan-sum": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M17 6H7l6 6-6 6h10" }, "p")]),
465
604
  delay: (s) => svg(s, [
466
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "13", r: "7" }, "c"),
467
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 10v3l2 2M9 3h6" }, "h")
605
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "13", r: "7" }, "c"),
606
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 10v3l2 2M9 3h6" }, "h")
468
607
  ]),
469
- merge: (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M6 5v4c0 3 3 3 6 3s6 0 6-3V5M12 12v7" }, "p")]),
608
+ merge: (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M6 5v4c0 3 3 3 6 3s6 0 6-3V5M12 12v7" }, "p")]),
470
609
  "console-sink": (s) => svg(s, [
471
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "4", y: "5", width: "16", height: "12", rx: "2" }, "r"),
472
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 10l2.5 2L8 14M13 14h3" }, "p")
610
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: "4", y: "5", width: "16", height: "12", rx: "2" }, "r"),
611
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M8 10l2.5 2L8 14M13 14h3" }, "p")
473
612
  ]),
474
613
  "collect-sink": (s) => svg(s, [
475
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 7h16M4 12h16M4 17h10" }, "p")
614
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M4 7h16M4 12h16M4 17h10" }, "p")
476
615
  ])
477
616
  };
478
617
  var BY_KIND = {
479
- R: (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 5l10 7-10 7z" }, "p")]),
618
+ R: (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M8 5l10 7-10 7z" }, "p")]),
480
619
  // source ▷ (play)
481
- T: (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 8h6l4 8h6M14 8h6M4 16h6" }, "p")]),
620
+ T: (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M4 8h6l4 8h6M14 8h6M4 16h6" }, "p")]),
482
621
  // transform
483
622
  W: (s) => svg(s, [
484
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5 4h11l3 3v13H5z" }, "p"),
485
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M9 4v5h6" }, "q")
623
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M5 4h11l3 3v13H5z" }, "p"),
624
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M9 4v5h6" }, "q")
486
625
  ])
487
626
  // sink (save)
488
627
  };
@@ -493,61 +632,61 @@ function NodeIcon({ code, kind, size = 15 }) {
493
632
  }
494
633
  function EyeIcon({ size = 14 }) {
495
634
  return svg(size, [
496
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M2 12s3.5-6.5 10-6.5S22 12 22 12s-3.5 6.5-10 6.5S2 12 2 12z" }, "p"),
497
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "12", r: "2.6" }, "c")
635
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M2 12s3.5-6.5 10-6.5S22 12 22 12s-3.5 6.5-10 6.5S2 12 2 12z" }, "p"),
636
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "12", r: "2.6" }, "c")
498
637
  ]);
499
638
  }
500
639
  function EyeOffIcon({ size = 14 }) {
501
640
  return svg(size, [
502
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M2 12s3.5-6.5 10-6.5c1.7 0 3.2.45 4.5 1.1M22 12s-3.5 6.5-10 6.5c-1.7 0-3.2-.45-4.5-1.1" }, "p"),
503
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M9.9 9.9a2.6 2.6 0 003.7 3.7" }, "e"),
504
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M3 3l18 18" }, "s")
641
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M2 12s3.5-6.5 10-6.5c1.7 0 3.2.45 4.5 1.1M22 12s-3.5 6.5-10 6.5c-1.7 0-3.2-.45-4.5-1.1" }, "p"),
642
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M9.9 9.9a2.6 2.6 0 003.7 3.7" }, "e"),
643
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M3 3l18 18" }, "s")
505
644
  ]);
506
645
  }
507
646
  function UnlockIcon({ size = 14 }) {
508
647
  return svg(size, [
509
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "5", y: "11", width: "14", height: "9", rx: "2" }, "r"),
510
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 11V7a4 4 0 017.9-1" }, "s")
648
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: "5", y: "11", width: "14", height: "9", rx: "2" }, "r"),
649
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M8 11V7a4 4 0 017.9-1" }, "s")
511
650
  ]);
512
651
  }
513
652
  function LockIcon({ size = 14 }) {
514
653
  return svg(size, [
515
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "5", y: "11", width: "14", height: "9", rx: "2" }, "r"),
516
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 11V7a4 4 0 018 0v4" }, "s")
654
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: "5", y: "11", width: "14", height: "9", rx: "2" }, "r"),
655
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M8 11V7a4 4 0 018 0v4" }, "s")
517
656
  ]);
518
657
  }
519
658
  function WarningIcon({ size = 13 }) {
520
659
  return svg(size, [
521
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 4l9 15H3z" }, "t"),
522
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 10v4" }, "b"),
523
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 17v.5" }, "d")
660
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 4l9 15H3z" }, "t"),
661
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 10v4" }, "b"),
662
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 17v.5" }, "d")
524
663
  ]);
525
664
  }
526
665
  function ErrorIcon({ size = 13 }) {
527
666
  return svg(size, [
528
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "12", r: "9" }, "c"),
529
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 7v6" }, "b"),
530
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 16v.5" }, "d")
667
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "12", r: "9" }, "c"),
668
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 7v6" }, "b"),
669
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 16v.5" }, "d")
531
670
  ]);
532
671
  }
533
672
 
534
673
  // src/panels/DiagnosticBadge.tsx
535
- var import_jsx_runtime8 = require("react/jsx-runtime");
674
+ var import_jsx_runtime9 = require("react/jsx-runtime");
536
675
  function DiagnosticBadge({ diagnostic, variant, size = 12 }) {
537
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
676
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
538
677
  "span",
539
678
  {
540
679
  className: `${variant} hl-diag-${diagnostic.severity}`,
541
680
  title: diagnostic.message,
542
681
  "aria-label": `${diagnostic.severity}: ${diagnostic.message}`,
543
- children: diagnostic.severity === "error" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorIcon, { size }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(WarningIcon, { size })
682
+ children: diagnostic.severity === "error" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorIcon, { size }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(WarningIcon, { size })
544
683
  }
545
684
  );
546
685
  }
547
686
 
548
687
  // src/nodes/NodeInteractionContext.tsx
549
688
  var import_react3 = require("react");
550
- var import_jsx_runtime9 = require("react/jsx-runtime");
689
+ var import_jsx_runtime10 = require("react/jsx-runtime");
551
690
  var NoopInteraction = {
552
691
  diagnosticFor: () => void 0,
553
692
  rename: () => void 0
@@ -557,14 +696,14 @@ function NodeInteractionProvider({
557
696
  value,
558
697
  children
559
698
  }) {
560
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(NodeInteractionCtx.Provider, { value, children });
699
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(NodeInteractionCtx.Provider, { value, children });
561
700
  }
562
701
  function useNodeInteraction() {
563
702
  return (0, import_react3.useContext)(NodeInteractionCtx);
564
703
  }
565
704
 
566
705
  // src/nodes/PillNode.tsx
567
- var import_jsx_runtime10 = require("react/jsx-runtime");
706
+ var import_jsx_runtime11 = require("react/jsx-runtime");
568
707
  var DOUBLE_CLICK_MS = 320;
569
708
  function PillNodeChrome({ node, selected }) {
570
709
  const live = useNodeLiveState(node.id);
@@ -610,18 +749,20 @@ function PillNodeChrome({ node, selected }) {
610
749
  }, []);
611
750
  const inputs = node.ports.filter((p) => p.direction === "I" && !p.isError);
612
751
  const outputs = node.ports.filter((p) => p.direction === "O" && !p.isError);
613
- const className = [
752
+ const className2 = [
614
753
  "hl-pill",
615
754
  `hl-pill-${node.kind.toLowerCase()}`,
616
755
  selected ? "is-selected" : "",
617
756
  live === "active" ? "is-active" : "",
618
757
  live === "error" ? "is-live-error" : "",
758
+ live === "finished" ? "is-finished" : "",
619
759
  (diagnostic == null ? void 0 : diagnostic.severity) === "error" ? "has-diag-error" : "",
620
760
  (diagnostic == null ? void 0 : diagnostic.severity) === "warning" ? "has-diag-warning" : ""
621
761
  ].filter(Boolean).join(" ");
622
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className, "data-kind": node.kind, children: [
623
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "hl-pill-dot", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(NodeIcon, { code: node.code, kind: node.kind, size: 13 }) }),
624
- editing ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
762
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: className2, "data-kind": node.kind, children: [
763
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(LivePulse, { nodeId: node.id }),
764
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "hl-pill-dot", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(NodeIcon, { code: node.code, kind: node.kind, size: 13 }) }),
765
+ editing ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
625
766
  "input",
626
767
  {
627
768
  ref: inputRef,
@@ -633,7 +774,7 @@ function PillNodeChrome({ node, selected }) {
633
774
  spellCheck: false,
634
775
  "aria-label": "Rename node"
635
776
  }
636
- ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
777
+ ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
637
778
  "span",
638
779
  {
639
780
  className: "hl-pill-name",
@@ -644,62 +785,167 @@ function PillNodeChrome({ node, selected }) {
644
785
  children: node.name
645
786
  }
646
787
  ),
647
- diagnostic !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DiagnosticBadge, { diagnostic, variant: "hl-pill-diag", size: 12 }),
648
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "hl-pill-status", "aria-hidden": true }),
649
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { className: "hl-pill-ports hl-in", children: inputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PortRow, { port }, port.id)) }),
650
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { className: "hl-pill-ports hl-out", children: outputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PortRow, { port }, port.id)) })
788
+ diagnostic !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DiagnosticBadge, { diagnostic, variant: "hl-pill-diag", size: 12 }),
789
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "hl-pill-status", "aria-hidden": true }),
790
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "hl-pill-ports hl-in", children: inputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PortRow, { port }, port.id)) }),
791
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "hl-pill-ports hl-out", children: outputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PortRow, { port }, port.id)) })
651
792
  ] });
652
793
  }
653
794
 
654
795
  // src/nodes/HlangNode.tsx
655
- var import_jsx_runtime11 = require("react/jsx-runtime");
796
+ var import_jsx_runtime12 = require("react/jsx-runtime");
656
797
  function NodeChrome({ node, selected, body }) {
657
798
  const inputs = node.ports.filter((p) => p.direction === "I");
658
799
  const outputs = node.ports.filter((p) => p.direction === "O");
659
800
  const live = useNodeLiveState(node.id);
660
- const className = [
801
+ const className2 = [
661
802
  "hl-node",
662
803
  `hl-${node.kind.toLowerCase()}`,
663
804
  selected ? "is-selected" : "",
664
805
  live === "active" ? "is-active" : "",
665
- live === "error" ? "is-live-error" : ""
806
+ live === "error" ? "is-live-error" : "",
807
+ live === "finished" ? "is-finished" : ""
666
808
  ].filter(Boolean).join(" ");
667
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className, "data-kind": node.kind, children: [
668
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SignalBar, {}),
669
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("header", { className: "hl-head", children: [
670
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "hl-kind", children: node.kind }),
671
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "hl-name", children: node.name }),
672
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "hl-role", children: KIND_ROLE[node.kind] })
809
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: className2, "data-kind": node.kind, children: [
810
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(LivePulse, { nodeId: node.id }),
811
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SignalBar, {}),
812
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("header", { className: "hl-head", children: [
813
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "hl-kind", children: node.kind }),
814
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "hl-name", children: node.name }),
815
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "hl-role", children: KIND_ROLE[node.kind] })
673
816
  ] }),
674
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(NodeBodySlot, { node, children: body }),
675
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "hl-ports", children: [
676
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "hl-col hl-in", children: inputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PortRow, { port }, port.id)) }),
677
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "hl-col hl-out", children: outputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PortRow, { port }, port.id)) })
817
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(NodeBodySlot, { node, children: body }),
818
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "hl-ports", children: [
819
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "hl-col hl-in", children: inputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PortRow, { port }, port.id)) }),
820
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "hl-col hl-out", children: outputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PortRow, { port }, port.id)) })
678
821
  ] })
679
822
  ] });
680
823
  }
681
824
  function HlangNode({ data, selected }) {
682
825
  const node = data.editorNode;
683
826
  if (typeof node.params.imageUrl === "string" && node.params.imageUrl.length > 0) {
684
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AssetNodeChrome, { node, selected });
827
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AssetNodeChrome, { node, selected });
685
828
  }
686
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PillNodeChrome, { node, selected });
829
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PillNodeChrome, { node, selected });
687
830
  }
688
831
 
689
832
  // src/edges/FlowEdge.tsx
690
833
  var import_react5 = require("@xyflow/react");
691
- var import_jsx_runtime12 = require("react/jsx-runtime");
692
- function useSourceRole(sourceId) {
693
- var _a, _b;
694
- const node = (0, import_react5.useInternalNode)(sourceId);
695
- const editorNode = (_a = node == null ? void 0 : node.data) == null ? void 0 : _a.editorNode;
696
- const role = (_b = editorNode == null ? void 0 : editorNode.params) == null ? void 0 : _b.role;
834
+
835
+ // src/edges/self-loop.ts
836
+ var SELF_LOOP_CLEARANCE = 28;
837
+ var SELF_LOOP_RADIUS = 16;
838
+ function format(value) {
839
+ return String(Math.round(value * 100) / 100);
840
+ }
841
+ function distance(a, b) {
842
+ return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
843
+ }
844
+ function toward(from, to, length) {
845
+ const span = distance(from, to);
846
+ const share = span === 0 ? 0 : length / span;
847
+ return { x: from.x + (to.x - from.x) * share, y: from.y + (to.y - from.y) * share };
848
+ }
849
+ function selfLoopPath(input) {
850
+ var _a, _b, _c, _d, _e;
851
+ const clearance = (_a = input.clearance) != null ? _a : SELF_LOOP_CLEARANCE;
852
+ const radius = (_b = input.radius) != null ? _b : SELF_LOOP_RADIUS;
853
+ const { source, target, node } = input;
854
+ const right = Math.max(source.x, node.x + node.width) + clearance;
855
+ const left = Math.min(target.x, node.x) - clearance;
856
+ const below = node.y + node.height + clearance;
857
+ const points = [
858
+ source,
859
+ { x: right, y: source.y },
860
+ { x: right, y: below },
861
+ { x: left, y: below },
862
+ { x: left, y: target.y },
863
+ target
864
+ ];
865
+ const parts = [`M ${format(source.x)} ${format(source.y)}`];
866
+ for (let i = 1; i < points.length - 1; i += 1) {
867
+ const before = (_c = points[i - 1]) != null ? _c : source;
868
+ const corner = (_d = points[i]) != null ? _d : source;
869
+ const after = (_e = points[i + 1]) != null ? _e : target;
870
+ const r = Math.min(radius, distance(before, corner) / 2, distance(corner, after) / 2);
871
+ if (r <= 0) {
872
+ parts.push(`L ${format(corner.x)} ${format(corner.y)}`);
873
+ continue;
874
+ }
875
+ const enter = toward(corner, before, r);
876
+ const leave = toward(corner, after, r);
877
+ parts.push(
878
+ `L ${format(enter.x)} ${format(enter.y)} Q ${format(corner.x)} ${format(corner.y)} ${format(leave.x)} ${format(leave.y)}`
879
+ );
880
+ }
881
+ parts.push(`L ${format(target.x)} ${format(target.y)}`);
882
+ return { path: parts.join(" "), labelX: (right + left) / 2, labelY: below };
883
+ }
884
+
885
+ // src/edges/edge-bounds.ts
886
+ var DEFAULT_EDGE_CAPACITY = 10;
887
+ function boundsFields(bounds) {
888
+ var _a;
889
+ const bounded = bounds.capacity !== void 0;
890
+ return [
891
+ { key: "bounded", label: "Bounded", type: "toggle", value: bounded },
892
+ { key: "capacity", label: "Capacity", type: "number", value: (_a = bounds.capacity) != null ? _a : DEFAULT_EDGE_CAPACITY, disabled: !bounded },
893
+ {
894
+ key: "dropOldest",
895
+ label: "Drop oldest when full",
896
+ type: "toggle",
897
+ value: bounds.overflow === "drop-oldest",
898
+ disabled: !bounded
899
+ }
900
+ ];
901
+ }
902
+ function nextBounds(bounds, key, value) {
903
+ const { capacity, overflow } = bounds;
904
+ if (key === "bounded") {
905
+ return value === true ? { capacity: capacity != null ? capacity : DEFAULT_EDGE_CAPACITY } : {};
906
+ }
907
+ if (capacity === void 0) {
908
+ return {};
909
+ }
910
+ const keep = overflow === void 0 ? {} : { overflow };
911
+ if (key === "capacity") {
912
+ if (typeof value !== "number" || Number.isNaN(value)) {
913
+ return bounds;
914
+ }
915
+ return { capacity: Math.max(1, Math.round(value)), ...keep };
916
+ }
917
+ if (key === "dropOldest") {
918
+ return value === true ? { capacity, overflow: "drop-oldest" } : { capacity };
919
+ }
920
+ return bounds;
921
+ }
922
+ function capacityLabel(bounds) {
923
+ if (bounds.capacity === void 0) {
924
+ return void 0;
925
+ }
926
+ const label = `[${String(bounds.capacity)}]`;
927
+ return bounds.overflow === "drop-oldest" ? `${label} drop oldest` : label;
928
+ }
929
+
930
+ // src/edges/FlowEdge.tsx
931
+ var import_jsx_runtime13 = require("react/jsx-runtime");
932
+ function roleOf(node) {
933
+ const role = node == null ? void 0 : node.data.editorNode.params.role;
697
934
  return typeof role === "string" && role.length > 0 ? role : void 0;
698
935
  }
699
936
  function FlowEdge(props) {
700
- var _a;
701
- const { id, source, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data } = props;
702
- const [path] = (0, import_react5.getBezierPath)({
937
+ var _a, _b, _c, _d, _e, _f;
938
+ const { id, source, target, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data, selected } = props;
939
+ const sourceNode = (0, import_react5.useInternalNode)(source);
940
+ const role = roleOf(sourceNode);
941
+ const width = sourceNode == null ? void 0 : sourceNode.measured.width;
942
+ const height = sourceNode == null ? void 0 : sourceNode.measured.height;
943
+ const loop = source === target && sourceNode !== void 0 && width !== void 0 && height !== void 0 ? selfLoopPath({
944
+ source: { x: sourceX, y: sourceY },
945
+ target: { x: targetX, y: targetY },
946
+ node: { ...sourceNode.internals.positionAbsolute, width, height }
947
+ }) : void 0;
948
+ const [curve] = (0, import_react5.getBezierPath)({
703
949
  sourceX,
704
950
  sourceY,
705
951
  sourcePosition,
@@ -707,25 +953,39 @@ function FlowEdge(props) {
707
953
  targetY,
708
954
  targetPosition
709
955
  });
956
+ const path = (_a = loop == null ? void 0 : loop.path) != null ? _a : curve;
710
957
  const t = 0.62;
711
- const labelX = sourceX + (targetX - sourceX) * t;
712
- const labelY = sourceY + (targetY - sourceY) * t;
713
- const isError = (_a = data == null ? void 0 : data.isError) != null ? _a : false;
958
+ const labelX = (_b = loop == null ? void 0 : loop.labelX) != null ? _b : sourceX + (targetX - sourceX) * t;
959
+ const labelY = (_c = loop == null ? void 0 : loop.labelY) != null ? _c : sourceY + (targetY - sourceY) * t;
960
+ const capacity = capacityLabel(data != null ? data : {});
961
+ const capacityX = (_d = loop == null ? void 0 : loop.labelX) != null ? _d : sourceX + (targetX - sourceX) * 0.38;
962
+ const capacityY = (_e = loop == null ? void 0 : loop.labelY) != null ? _e : sourceY + (targetY - sourceY) * 0.38;
963
+ const isError = (_f = data == null ? void 0 : data.isError) != null ? _f : false;
714
964
  const isValue = (data == null ? void 0 : data.stream) === "value";
715
965
  const flowing = useEdgeFlowing(id);
716
- const role = useSourceRole(source);
717
- const className = [
966
+ const pulse = useEdgePulse(id);
967
+ const className2 = [
718
968
  "hl-edge",
719
969
  isError ? "is-error" : "",
720
970
  isValue ? "is-value" : "is-live",
721
- flowing ? "is-flowing" : ""
971
+ flowing ? "is-flowing" : "",
972
+ selected === true ? "is-selected" : ""
722
973
  ].filter(Boolean).join(" ");
723
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
724
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("g", { className, children: [
725
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react5.BaseEdge, { id, path, className: "hl-edge-base" }),
726
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: path, className: "hl-edge-flow" })
974
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
975
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("g", { className: className2, children: [
976
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react5.BaseEdge, { id, path, className: "hl-edge-base" }),
977
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: path, className: "hl-edge-flow" }),
978
+ pulse > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: path, className: "hl-edge-pulse" }, pulse)
727
979
  ] }),
728
- role !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react5.EdgeLabelRenderer, { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
980
+ capacity !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react5.EdgeLabelRenderer, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
981
+ "div",
982
+ {
983
+ className: `hl-edge-capacity hl-mono${(data == null ? void 0 : data.overflow) === "drop-oldest" ? " is-lossy" : ""}`,
984
+ style: { transform: `translate(-50%, -50%) translate(${String(capacityX)}px, ${String(capacityY)}px)` },
985
+ children: capacity
986
+ }
987
+ ) }),
988
+ role !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react5.EdgeLabelRenderer, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
729
989
  "div",
730
990
  {
731
991
  className: "hl-edge-label hl-mono",
@@ -747,49 +1007,257 @@ function createHlangFlowTypes() {
747
1007
  }
748
1008
 
749
1009
  // src/shell/EditorShell.tsx
750
- var import_jsx_runtime13 = require("react/jsx-runtime");
1010
+ var import_react7 = require("react");
1011
+
1012
+ // src/shell/FooterResizer.tsx
1013
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1014
+ function FooterResizer({ resize, controls, label }) {
1015
+ const onPointerDown = (event) => {
1016
+ if (event.pointerType === "mouse" && event.button !== 0) {
1017
+ return;
1018
+ }
1019
+ event.preventDefault();
1020
+ event.currentTarget.setPointerCapture(event.pointerId);
1021
+ resize.startDrag(event.clientY);
1022
+ };
1023
+ const onPointerMove = (event) => {
1024
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
1025
+ resize.drag(event.clientY);
1026
+ }
1027
+ };
1028
+ const onPointerEnd = (event) => {
1029
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
1030
+ event.currentTarget.releasePointerCapture(event.pointerId);
1031
+ }
1032
+ resize.endDrag();
1033
+ };
1034
+ const onKeyDown = (event) => {
1035
+ if (resize.keyDown(event.key, event.shiftKey)) {
1036
+ event.preventDefault();
1037
+ }
1038
+ };
1039
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1040
+ "div",
1041
+ {
1042
+ role: "separator",
1043
+ "aria-orientation": "horizontal",
1044
+ "aria-label": label,
1045
+ "aria-controls": controls,
1046
+ "aria-valuemin": resize.bounds.min,
1047
+ "aria-valuemax": resize.bounds.max,
1048
+ "aria-valuenow": resize.height,
1049
+ tabIndex: 0,
1050
+ className: `hl-footer-resizer${resize.dragging ? " is-dragging" : ""}`,
1051
+ onPointerDown,
1052
+ onPointerMove,
1053
+ onPointerUp: onPointerEnd,
1054
+ onPointerCancel: onPointerEnd,
1055
+ onLostPointerCapture: () => {
1056
+ resize.endDrag();
1057
+ },
1058
+ onDoubleClick: () => {
1059
+ resize.toggle();
1060
+ },
1061
+ onKeyDown
1062
+ }
1063
+ );
1064
+ }
1065
+
1066
+ // src/shell/useFooterResize.ts
1067
+ var import_react6 = require("react");
1068
+
1069
+ // src/shell/footer-size.ts
1070
+ var FOOTER_KEY_STEP = 16;
1071
+ function footerBounds(options) {
1072
+ const { available, minHeight, minCanvasHeight } = options;
1073
+ return { min: minHeight, max: Math.max(minHeight, available - minCanvasHeight) };
1074
+ }
1075
+ function clampFooter(height, bounds) {
1076
+ return Math.round(Math.min(bounds.max, Math.max(bounds.min, height)));
1077
+ }
1078
+ function resizeByKey(height, key, shift, bounds) {
1079
+ const step = FOOTER_KEY_STEP * (shift ? 4 : 1);
1080
+ switch (key) {
1081
+ case "ArrowUp":
1082
+ return clampFooter(height + step, bounds);
1083
+ case "ArrowDown":
1084
+ return clampFooter(height - step, bounds);
1085
+ case "Home":
1086
+ return bounds.min;
1087
+ case "End":
1088
+ return bounds.max;
1089
+ default:
1090
+ return void 0;
1091
+ }
1092
+ }
1093
+ function toggleCollapsed(size, bounds) {
1094
+ if (size.height > bounds.min) {
1095
+ return { height: bounds.min, restore: size.height };
1096
+ }
1097
+ return { height: clampFooter(size.restore, bounds), restore: size.restore };
1098
+ }
1099
+
1100
+ // src/shell/useFooterResize.ts
1101
+ var DEFAULT_MIN_CANVAS_HEIGHT = 160;
1102
+ function useFooterResize(options) {
1103
+ var _a;
1104
+ const { defaultHeight, minHeight } = options;
1105
+ const minCanvasHeight = (_a = options.minCanvasHeight) != null ? _a : DEFAULT_MIN_CANVAS_HEIGHT;
1106
+ const [available, setAvailable] = (0, import_react6.useState)(null);
1107
+ const [preferred, setPreferred] = (0, import_react6.useState)(defaultHeight);
1108
+ const [restore, setRestore] = (0, import_react6.useState)(defaultHeight);
1109
+ const [dragging, setDragging] = (0, import_react6.useState)(false);
1110
+ const dragStart = (0, import_react6.useRef)(null);
1111
+ const bounds = (0, import_react6.useMemo)(
1112
+ () => available === null ? { min: minHeight, max: Math.max(minHeight, defaultHeight) } : footerBounds({ available, minHeight, minCanvasHeight }),
1113
+ [available, minHeight, minCanvasHeight, defaultHeight]
1114
+ );
1115
+ const height = clampFooter(preferred, bounds);
1116
+ const choose = (0, import_react6.useCallback)(
1117
+ (next) => {
1118
+ setPreferred(next);
1119
+ if (next > bounds.min) {
1120
+ setRestore(next);
1121
+ }
1122
+ },
1123
+ [bounds.min]
1124
+ );
1125
+ const measure = (0, import_react6.useCallback)((next) => {
1126
+ setAvailable(next);
1127
+ }, []);
1128
+ const startDrag = (0, import_react6.useCallback)(
1129
+ (clientY) => {
1130
+ dragStart.current = { clientY, height };
1131
+ setDragging(true);
1132
+ },
1133
+ [height]
1134
+ );
1135
+ const drag = (0, import_react6.useCallback)(
1136
+ (clientY) => {
1137
+ const start = dragStart.current;
1138
+ if (start === null) {
1139
+ return;
1140
+ }
1141
+ choose(clampFooter(start.height + (start.clientY - clientY), bounds));
1142
+ },
1143
+ [bounds, choose]
1144
+ );
1145
+ const endDrag = (0, import_react6.useCallback)(() => {
1146
+ dragStart.current = null;
1147
+ setDragging(false);
1148
+ }, []);
1149
+ const toggle = (0, import_react6.useCallback)(() => {
1150
+ const next = toggleCollapsed({ height, restore }, bounds);
1151
+ setPreferred(next.height);
1152
+ setRestore(next.restore);
1153
+ }, [height, restore, bounds]);
1154
+ const keyDown = (0, import_react6.useCallback)(
1155
+ (key, shift) => {
1156
+ if (key === "Enter") {
1157
+ toggle();
1158
+ return true;
1159
+ }
1160
+ const next = resizeByKey(height, key, shift, bounds);
1161
+ if (next === void 0) {
1162
+ return false;
1163
+ }
1164
+ choose(next);
1165
+ return true;
1166
+ },
1167
+ [height, bounds, choose, toggle]
1168
+ );
1169
+ return { height, bounds, dragging, measure, startDrag, drag, endDrag, keyDown, toggle };
1170
+ }
1171
+
1172
+ // src/shell/EditorShell.tsx
1173
+ var import_jsx_runtime15 = require("react/jsx-runtime");
751
1174
  function EditorShell({
752
1175
  toolbar,
753
1176
  left,
754
1177
  right,
755
1178
  canvas,
756
1179
  footer,
1180
+ footerResize,
757
1181
  leftCollapsed = false,
758
1182
  rightCollapsed = false
759
1183
  }) {
760
- const className = [
1184
+ const shellRef = (0, import_react7.useRef)(null);
1185
+ const toolbarRef = (0, import_react7.useRef)(null);
1186
+ const className2 = [
761
1187
  "hl-shell",
762
1188
  leftCollapsed ? "hl-left-collapsed" : "",
763
1189
  rightCollapsed ? "hl-right-collapsed" : ""
764
1190
  ].filter(Boolean).join(" ");
765
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className, children: [
766
- toolbar !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "hl-region-toolbar", children: toolbar }),
767
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "hl-region-body", children: [
768
- left !== void 0 && !leftCollapsed && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("aside", { className: "hl-region-left", children: left }),
769
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("main", { className: "hl-region-canvas", children: canvas }),
770
- right !== void 0 && !rightCollapsed && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("aside", { className: "hl-region-right", children: right })
1191
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: className2, ref: shellRef, children: [
1192
+ toolbar !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "hl-region-toolbar", ref: toolbarRef, children: toolbar }),
1193
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "hl-region-body", children: [
1194
+ left !== void 0 && !leftCollapsed && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("aside", { className: "hl-region-left", children: left }),
1195
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("main", { className: "hl-region-canvas", children: canvas }),
1196
+ right !== void 0 && !rightCollapsed && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("aside", { className: "hl-region-right", children: right })
771
1197
  ] }),
772
- footer !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "hl-region-footer", children: footer })
1198
+ footer !== void 0 && (footerResize === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "hl-region-footer", children: footer }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ResizableFooter, { options: footerResize, shellRef, toolbarRef, children: footer }))
773
1199
  ] });
774
1200
  }
1201
+ function ResizableFooter({
1202
+ options,
1203
+ shellRef,
1204
+ toolbarRef,
1205
+ children
1206
+ }) {
1207
+ var _a;
1208
+ const resize = useFooterResize(options);
1209
+ const id = (0, import_react7.useId)();
1210
+ const { measure } = resize;
1211
+ (0, import_react7.useEffect)(() => {
1212
+ const shell = shellRef.current;
1213
+ if (shell === null) {
1214
+ return;
1215
+ }
1216
+ const update = () => {
1217
+ var _a2, _b;
1218
+ measure(shell.clientHeight - ((_b = (_a2 = toolbarRef.current) == null ? void 0 : _a2.offsetHeight) != null ? _b : 0));
1219
+ };
1220
+ update();
1221
+ const observer = new ResizeObserver(update);
1222
+ observer.observe(shell);
1223
+ if (toolbarRef.current !== null) {
1224
+ observer.observe(toolbarRef.current);
1225
+ }
1226
+ return () => {
1227
+ observer.disconnect();
1228
+ };
1229
+ }, [measure, shellRef, toolbarRef]);
1230
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1231
+ "div",
1232
+ {
1233
+ id,
1234
+ className: `hl-region-footer is-resizable${resize.dragging ? " is-resizing" : ""}`,
1235
+ style: { height: resize.height },
1236
+ children: [
1237
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(FooterResizer, { resize, controls: id, label: (_a = options.label) != null ? _a : "Resize the panel" }),
1238
+ children
1239
+ ]
1240
+ }
1241
+ );
1242
+ }
775
1243
 
776
1244
  // src/toolbar/Toolbar.tsx
777
1245
  var import_editor_react = require("@hlang-tech/editor-react");
778
- var import_jsx_runtime14 = require("react/jsx-runtime");
1246
+ var import_jsx_runtime16 = require("react/jsx-runtime");
779
1247
  function Toolbar({ title, center, actions }) {
780
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("header", { className: "hl-toolbar", children: [
781
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "hl-toolbar-brand", children: title != null ? title : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
782
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "hl-toolbar-glyph", "aria-hidden": "true" }),
783
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "hl-toolbar-wordmark", children: "hlang" })
1248
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("header", { className: "hl-toolbar", children: [
1249
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "hl-toolbar-brand", children: title != null ? title : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
1250
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "hl-toolbar-glyph", "aria-hidden": "true" }),
1251
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "hl-toolbar-wordmark", children: "hlang" })
784
1252
  ] }) }),
785
- center !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "hl-toolbar-center", children: center }),
786
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "hl-toolbar-actions", children: actions })
1253
+ center !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "hl-toolbar-center", children: center }),
1254
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "hl-toolbar-actions", children: actions })
787
1255
  ] });
788
1256
  }
789
1257
  function UndoRedo({ core }) {
790
1258
  const history = (0, import_editor_react.useHistory)(core);
791
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
792
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1259
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
1260
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
793
1261
  "button",
794
1262
  {
795
1263
  type: "button",
@@ -800,7 +1268,7 @@ function UndoRedo({ core }) {
800
1268
  children: "Undo"
801
1269
  }
802
1270
  ),
803
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1271
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
804
1272
  "button",
805
1273
  {
806
1274
  type: "button",
@@ -819,7 +1287,7 @@ function RunButton({
819
1287
  runLabel = "Run",
820
1288
  stopLabel = "Stop"
821
1289
  }) {
822
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1290
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
823
1291
  "button",
824
1292
  {
825
1293
  type: "button",
@@ -827,7 +1295,7 @@ function RunButton({
827
1295
  onClick: onRun,
828
1296
  "aria-pressed": running,
829
1297
  children: [
830
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1298
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
831
1299
  "span",
832
1300
  {
833
1301
  className: `hl-toolbar-run-icon${running ? " is-stop" : ""}`,
@@ -840,22 +1308,22 @@ function RunButton({
840
1308
  );
841
1309
  }
842
1310
  function ToolbarStat({ label, value }) {
843
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "hl-toolbar-stat hl-mono", title: label, children: value });
1311
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "hl-toolbar-stat hl-mono", title: label, children: value });
844
1312
  }
845
1313
  function ToolbarGhostButton({
846
1314
  children,
847
1315
  onClick,
848
1316
  title
849
1317
  }) {
850
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: "hl-toolbar-btn", onClick, title, children });
1318
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: "hl-toolbar-btn", onClick, title, children });
851
1319
  }
852
1320
 
853
1321
  // src/toolbar/ToolCluster.tsx
854
- var import_jsx_runtime15 = require("react/jsx-runtime");
1322
+ var import_jsx_runtime17 = require("react/jsx-runtime");
855
1323
  function ToolCluster({ items }) {
856
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "hl-toolcluster", role: "toolbar", "aria-label": "Canvas tools", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "hl-toolcluster-slot", children: [
857
- item.separatorBefore === true && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "hl-toolcluster-sep", "aria-hidden": true }),
858
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1324
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "hl-toolcluster", role: "toolbar", "aria-label": "Canvas tools", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "hl-toolcluster-slot", children: [
1325
+ item.separatorBefore === true && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "hl-toolcluster-sep", "aria-hidden": true }),
1326
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
859
1327
  "button",
860
1328
  {
861
1329
  type: "button",
@@ -872,31 +1340,31 @@ function ToolCluster({ items }) {
872
1340
  }
873
1341
 
874
1342
  // src/canvas/CanvasChrome.tsx
875
- var import_react6 = require("@xyflow/react");
876
- var import_jsx_runtime16 = require("react/jsx-runtime");
1343
+ var import_react8 = require("@xyflow/react");
1344
+ var import_jsx_runtime18 = require("react/jsx-runtime");
877
1345
  function CanvasChrome({ background = true }) {
878
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children: background && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react6.Background, { variant: import_react6.BackgroundVariant.Dots, gap: 26, size: 1, color: "#242830" }) });
1346
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, { children: background && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react8.Background, { variant: import_react8.BackgroundVariant.Dots, gap: 26, size: 1, color: "#242830" }) });
879
1347
  }
880
1348
 
881
1349
  // src/canvas/CanvasDock.tsx
882
- var import_jsx_runtime17 = require("react/jsx-runtime");
1350
+ var import_jsx_runtime19 = require("react/jsx-runtime");
883
1351
  function CursorIcon() {
884
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M5 3l6 16 2-6 6-2z" }) });
1352
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M5 3l6 16 2-6 6-2z" }) });
885
1353
  }
886
1354
  function HandIcon() {
887
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M8 12V6a1.5 1.5 0 013 0v5m0-1V5a1.5 1.5 0 013 0v6m0-1V6.5a1.5 1.5 0 013 0V13c0 3.5-2 6-5.5 6H12c-2 0-3-.8-4.2-2.5L5 12.5c-.7-1 .6-2.3 1.6-1.6z" }) });
1355
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M8 12V6a1.5 1.5 0 013 0v5m0-1V5a1.5 1.5 0 013 0v6m0-1V6.5a1.5 1.5 0 013 0V13c0 3.5-2 6-5.5 6H12c-2 0-3-.8-4.2-2.5L5 12.5c-.7-1 .6-2.3 1.6-1.6z" }) });
888
1356
  }
889
1357
  function PlusIcon() {
890
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: 17, height: 17, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M12 5v14M5 12h14" }) });
1358
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("svg", { width: 17, height: 17, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M12 5v14M5 12h14" }) });
891
1359
  }
892
1360
  function FitIcon() {
893
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5" }) });
1361
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5" }) });
894
1362
  }
895
1363
  function UndoIcon() {
896
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M9 7L4 12l5 5M4 12h11a5 5 0 010 10h-1" }) });
1364
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M9 7L4 12l5 5M4 12h11a5 5 0 010 10h-1" }) });
897
1365
  }
898
1366
  function RedoIcon() {
899
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M15 7l5 5-5 5M20 12H9a5 5 0 000 10h1" }) });
1367
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M15 7l5 5-5 5M20 12H9a5 5 0 000 10h1" }) });
900
1368
  }
901
1369
  function CanvasDock({
902
1370
  zoom,
@@ -912,8 +1380,8 @@ function CanvasDock({
912
1380
  canRedo
913
1381
  }) {
914
1382
  const pct = `${String(Math.round(zoom * 100))}%`;
915
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "hl-dock", role: "toolbar", "aria-label": "Canvas tools", children: [
916
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1383
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "hl-dock", role: "toolbar", "aria-label": "Canvas tools", children: [
1384
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
917
1385
  "button",
918
1386
  {
919
1387
  className: "hl-dock-btn",
@@ -921,10 +1389,10 @@ function CanvasDock({
921
1389
  disabled: !canUndo,
922
1390
  title: "Undo (\u2318Z)",
923
1391
  "aria-label": "Undo",
924
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(UndoIcon, {})
1392
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(UndoIcon, {})
925
1393
  }
926
1394
  ),
927
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1395
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
928
1396
  "button",
929
1397
  {
930
1398
  className: "hl-dock-btn",
@@ -932,48 +1400,48 @@ function CanvasDock({
932
1400
  disabled: !canRedo,
933
1401
  title: "Redo (\u2318\u21E7Z)",
934
1402
  "aria-label": "Redo",
935
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(RedoIcon, {})
1403
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(RedoIcon, {})
936
1404
  }
937
1405
  ),
938
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "hl-dock-sep", "aria-hidden": true }),
939
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1406
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "hl-dock-sep", "aria-hidden": true }),
1407
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
940
1408
  "button",
941
1409
  {
942
1410
  className: "hl-dock-btn" + (mode === "select" ? " is-active" : ""),
943
1411
  onClick: () => onModeChange("select"),
944
1412
  title: "Select (V)",
945
1413
  "aria-pressed": mode === "select",
946
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CursorIcon, {})
1414
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CursorIcon, {})
947
1415
  }
948
1416
  ),
949
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1417
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
950
1418
  "button",
951
1419
  {
952
1420
  className: "hl-dock-btn" + (mode === "pan" ? " is-active" : ""),
953
1421
  onClick: () => onModeChange("pan"),
954
1422
  title: "Pan (Space)",
955
1423
  "aria-pressed": mode === "pan",
956
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(HandIcon, {})
1424
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(HandIcon, {})
957
1425
  }
958
1426
  ),
959
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { className: "hl-dock-btn hl-dock-add", onClick: onAdd, title: "Add node", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PlusIcon, {}) }),
960
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "hl-dock-sep", "aria-hidden": true }),
961
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { className: "hl-dock-btn", onClick: onZoomOut, title: "Zoom out", "aria-label": "Zoom out", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "hl-dock-glyph", children: "\u2212" }) }),
962
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { className: "hl-dock-zoom hl-mono", onClick: onFitView, title: "Fit view", children: pct }),
963
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { className: "hl-dock-btn", onClick: onZoomIn, title: "Zoom in", "aria-label": "Zoom in", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "hl-dock-glyph", children: "+" }) }),
964
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { className: "hl-dock-btn hl-dock-fit", onClick: onFitView, title: "Fit view", "aria-label": "Fit view", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(FitIcon, {}) })
1427
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { className: "hl-dock-btn hl-dock-add", onClick: onAdd, title: "Add node", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PlusIcon, {}) }),
1428
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "hl-dock-sep", "aria-hidden": true }),
1429
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { className: "hl-dock-btn", onClick: onZoomOut, title: "Zoom out", "aria-label": "Zoom out", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "hl-dock-glyph", children: "\u2212" }) }),
1430
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { className: "hl-dock-zoom hl-mono", onClick: onFitView, title: "Fit view", children: pct }),
1431
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { className: "hl-dock-btn", onClick: onZoomIn, title: "Zoom in", "aria-label": "Zoom in", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "hl-dock-glyph", children: "+" }) }),
1432
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { className: "hl-dock-btn hl-dock-fit", onClick: onFitView, title: "Fit view", "aria-label": "Fit view", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(FitIcon, {}) })
965
1433
  ] });
966
1434
  }
967
1435
 
968
1436
  // src/canvas/EmptyState.tsx
969
- var import_jsx_runtime18 = require("react/jsx-runtime");
1437
+ var import_jsx_runtime20 = require("react/jsx-runtime");
970
1438
  function EmptyState({
971
1439
  title = "An empty canvas.",
972
1440
  hint = "add a source \xB7 wire it to a sink \xB7 run"
973
1441
  }) {
974
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "hl-empty", "aria-hidden": "true", children: [
975
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "hl-empty-title", children: title }),
976
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "hl-empty-hint", children: hint })
1442
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "hl-empty", "aria-hidden": "true", children: [
1443
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "hl-empty-title", children: title }),
1444
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "hl-empty-hint", children: hint })
977
1445
  ] });
978
1446
  }
979
1447
 
@@ -1009,11 +1477,11 @@ function deriveParamSchema(descriptor, node) {
1009
1477
  }
1010
1478
 
1011
1479
  // src/hooks/useNodeParams.ts
1012
- var import_react7 = require("react");
1480
+ var import_react9 = require("react");
1013
1481
  var import_editor_core = require("@hlang-tech/editor-core");
1014
1482
  function useNodeParams(core, nodeId) {
1015
- const versionRef = (0, import_react7.useRef)(0);
1016
- const subscribe = (0, import_react7.useCallback)(
1483
+ const versionRef = (0, import_react9.useRef)(0);
1484
+ const subscribe = (0, import_react9.useCallback)(
1017
1485
  (onStoreChange) => {
1018
1486
  const disposable = core.events.on("nodeChanged", (event) => {
1019
1487
  if (nodeId !== void 0 && event.node.id === nodeId) {
@@ -1027,9 +1495,9 @@ function useNodeParams(core, nodeId) {
1027
1495
  },
1028
1496
  [core, nodeId]
1029
1497
  );
1030
- const getSnapshot = (0, import_react7.useCallback)(() => versionRef.current, []);
1031
- const version = (0, import_react7.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1032
- const setParams = (0, import_react7.useCallback)(
1498
+ const getSnapshot = (0, import_react9.useCallback)(() => versionRef.current, []);
1499
+ const version = (0, import_react9.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1500
+ const setParams = (0, import_react9.useCallback)(
1033
1501
  (next) => {
1034
1502
  if (nodeId === void 0) {
1035
1503
  return;
@@ -1038,7 +1506,7 @@ function useNodeParams(core, nodeId) {
1038
1506
  },
1039
1507
  [core, nodeId]
1040
1508
  );
1041
- const setParam = (0, import_react7.useCallback)(
1509
+ const setParam = (0, import_react9.useCallback)(
1042
1510
  (key, value) => {
1043
1511
  if (nodeId === void 0) {
1044
1512
  return;
@@ -1051,7 +1519,7 @@ function useNodeParams(core, nodeId) {
1051
1519
  },
1052
1520
  [core, nodeId, setParams]
1053
1521
  );
1054
- return (0, import_react7.useMemo)(
1522
+ return (0, import_react9.useMemo)(
1055
1523
  () => ({
1056
1524
  node: nodeId === void 0 ? void 0 : core.model.getNode(nodeId),
1057
1525
  setParam,
@@ -1063,19 +1531,58 @@ function useNodeParams(core, nodeId) {
1063
1531
  );
1064
1532
  }
1065
1533
 
1534
+ // src/hooks/useEdgeBounds.ts
1535
+ var import_react10 = require("react");
1536
+ var import_editor_core2 = require("@hlang-tech/editor-core");
1537
+ function useEdgeBounds(core, edgeId) {
1538
+ const subscribe = (0, import_react10.useCallback)(
1539
+ (onStoreChange) => {
1540
+ const changed = core.events.on("edgeChanged", (event) => {
1541
+ if (event.edge.id === edgeId) {
1542
+ onStoreChange();
1543
+ }
1544
+ });
1545
+ const removed = core.events.on("edgeRemoved", (event) => {
1546
+ if (event.edgeId === edgeId) {
1547
+ onStoreChange();
1548
+ }
1549
+ });
1550
+ return () => {
1551
+ changed.dispose();
1552
+ removed.dispose();
1553
+ };
1554
+ },
1555
+ [core, edgeId]
1556
+ );
1557
+ const getSnapshot = (0, import_react10.useCallback)(
1558
+ () => edgeId === void 0 ? void 0 : core.model.getEdge(edgeId),
1559
+ [core, edgeId]
1560
+ );
1561
+ const edge = (0, import_react10.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1562
+ const setBounds = (0, import_react10.useCallback)(
1563
+ (bounds) => {
1564
+ if (edgeId !== void 0) {
1565
+ core.commands.execute(new import_editor_core2.SetEdgeBoundsCommand(edgeId, bounds));
1566
+ }
1567
+ },
1568
+ [core, edgeId]
1569
+ );
1570
+ return { edge, setBounds };
1571
+ }
1572
+
1066
1573
  // src/panels/ParamForm.tsx
1067
- var import_react8 = require("react");
1068
- var import_jsx_runtime19 = require("react/jsx-runtime");
1574
+ var import_react11 = require("react");
1575
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1069
1576
  function ParamForm({ fields, onCommit }) {
1070
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "hl-form", children: fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(FieldRow, { field, onCommit }, field.key)) });
1577
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "hl-form", children: fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FieldRow, { field, onCommit }, field.key)) });
1071
1578
  }
1072
1579
  function FieldRow({
1073
1580
  field,
1074
1581
  onCommit
1075
1582
  }) {
1076
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { className: "hl-field", children: [
1077
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "hl-field-label", children: field.label }),
1078
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(FieldControl, { field, onCommit })
1583
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { className: "hl-field", children: [
1584
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-field-label", children: field.label }),
1585
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FieldControl, { field, onCommit })
1079
1586
  ] });
1080
1587
  }
1081
1588
  function FieldControl({
@@ -1084,7 +1591,7 @@ function FieldControl({
1084
1591
  }) {
1085
1592
  switch (field.type) {
1086
1593
  case "toggle":
1087
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1594
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1088
1595
  "button",
1089
1596
  {
1090
1597
  type: "button",
@@ -1093,17 +1600,17 @@ function FieldControl({
1093
1600
  className: `hl-toggle${field.value ? " is-on" : ""}`,
1094
1601
  disabled: field.disabled,
1095
1602
  onClick: () => onCommit(field.key, !field.value),
1096
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "hl-toggle-knob" })
1603
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-toggle-knob" })
1097
1604
  }
1098
1605
  );
1099
1606
  case "number":
1100
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(NumberControl, { field, onCommit });
1607
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(NumberControl, { field, onCommit });
1101
1608
  case "textarea":
1102
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TextControl, { field, onCommit, multiline: true });
1609
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TextControl, { field, onCommit, multiline: true });
1103
1610
  case "text":
1104
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TextControl, { field, onCommit });
1611
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TextControl, { field, onCommit });
1105
1612
  case "custom":
1106
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "hl-field-custom hl-mono", title: field.fieldType, children: describeCustom(field.value) });
1613
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-field-custom hl-mono", title: field.fieldType, children: describeCustom(field.value) });
1107
1614
  }
1108
1615
  }
1109
1616
  function TextControl({
@@ -1111,7 +1618,7 @@ function TextControl({
1111
1618
  onCommit,
1112
1619
  multiline = false
1113
1620
  }) {
1114
- const [draft, setDraft] = (0, import_react8.useState)(field.value);
1621
+ const [draft, setDraft] = (0, import_react11.useState)(field.value);
1115
1622
  const commit = () => {
1116
1623
  if (draft !== field.value) {
1117
1624
  onCommit(field.key, draft);
@@ -1125,7 +1632,7 @@ function TextControl({
1125
1632
  onBlur: commit
1126
1633
  };
1127
1634
  if (multiline) {
1128
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1635
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1129
1636
  "textarea",
1130
1637
  {
1131
1638
  ...shared,
@@ -1134,7 +1641,7 @@ function TextControl({
1134
1641
  }
1135
1642
  );
1136
1643
  }
1137
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1644
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1138
1645
  "input",
1139
1646
  {
1140
1647
  ...shared,
@@ -1152,14 +1659,14 @@ function NumberControl({
1152
1659
  field,
1153
1660
  onCommit
1154
1661
  }) {
1155
- const [draft, setDraft] = (0, import_react8.useState)(String(field.value));
1662
+ const [draft, setDraft] = (0, import_react11.useState)(String(field.value));
1156
1663
  const commit = () => {
1157
1664
  const parsed = Number(draft);
1158
1665
  if (!Number.isNaN(parsed) && parsed !== field.value) {
1159
1666
  onCommit(field.key, parsed);
1160
1667
  }
1161
1668
  };
1162
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1669
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1163
1670
  "input",
1164
1671
  {
1165
1672
  className: "hl-input hl-mono",
@@ -1190,40 +1697,267 @@ function describeCustom(value) {
1190
1697
  return String(value);
1191
1698
  }
1192
1699
 
1700
+ // src/panels/ConnectionInspector.tsx
1701
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1702
+ function ConnectionInspector({ core, edgeId }) {
1703
+ const { edge, setBounds } = useEdgeBounds(core, edgeId);
1704
+ if (edge === void 0) {
1705
+ return null;
1706
+ }
1707
+ const end = (endpoint) => {
1708
+ var _a, _b;
1709
+ return `${(_b = (_a = core.model.getNode(endpoint.nodeId)) == null ? void 0 : _a.name) != null ? _b : endpoint.nodeId}.${endpoint.portId}`;
1710
+ };
1711
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "hl-inspector", children: [
1712
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("header", { className: "hl-inspector-head", children: [
1713
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "hl-inspector-kind hl-connection", children: "\u2192" }),
1714
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "hl-inspector-titles", children: [
1715
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "hl-inspector-name", children: "Connection" }),
1716
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "hl-inspector-role hl-mono", children: [
1717
+ end(edge.source),
1718
+ " \u2192 ",
1719
+ end(edge.target)
1720
+ ] })
1721
+ ] })
1722
+ ] }),
1723
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("section", { className: "hl-inspector-section", children: [
1724
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h3", { className: "hl-inspector-section-title", children: "Back-pressure" }),
1725
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "hl-inspector-hint hl-inspector-note", children: "A bounded connection holds at most its capacity, counting the message being worked on. A sender to a full connection waits, unless it drops its oldest waiting message." }),
1726
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1727
+ ParamForm,
1728
+ {
1729
+ fields: boundsFields(edge),
1730
+ onCommit: (key, value) => {
1731
+ setBounds(nextBounds(edge, key, value));
1732
+ }
1733
+ }
1734
+ )
1735
+ ] })
1736
+ ] });
1737
+ }
1738
+
1739
+ // src/live/format.ts
1740
+ function formatDuration(ms) {
1741
+ if (ms === 0) {
1742
+ return "0 ms";
1743
+ }
1744
+ if (ms < 0.01) {
1745
+ return "<0.01 ms";
1746
+ }
1747
+ if (ms < 1) {
1748
+ return `${ms.toFixed(2)} ms`;
1749
+ }
1750
+ if (ms < 10) {
1751
+ return `${ms.toFixed(1)} ms`;
1752
+ }
1753
+ if (ms < 1e3) {
1754
+ return `${Math.round(ms).toString()} ms`;
1755
+ }
1756
+ return `${(ms / 1e3).toFixed(2)} s`;
1757
+ }
1758
+ function formatElapsed(ms) {
1759
+ return `${(ms / 1e3).toFixed(3)}s`;
1760
+ }
1761
+
1762
+ // src/live/preview.ts
1763
+ var PREVIEW_LENGTH = 120;
1764
+ var CIRCULAR = "[Circular]";
1765
+ function functionPreview(value) {
1766
+ return `\u0192 ${value.name === "" ? "anonymous" : value.name}`;
1767
+ }
1768
+ function bigintPreview(value) {
1769
+ return `${value.toString()}n`;
1770
+ }
1771
+ function className(value) {
1772
+ const prototype = Object.getPrototypeOf(value);
1773
+ if (prototype === null || prototype === Object.prototype) {
1774
+ return "Object";
1775
+ }
1776
+ const name = typeof prototype === "object" && "constructor" in prototype ? prototype.constructor : void 0;
1777
+ return typeof name === "function" && name.name !== "" ? name.name : "Object";
1778
+ }
1779
+ function nestedPreview(value) {
1780
+ if (typeof value === "bigint") {
1781
+ return bigintPreview(value);
1782
+ }
1783
+ if (typeof value === "function") {
1784
+ return functionPreview(value);
1785
+ }
1786
+ if (typeof value === "symbol") {
1787
+ return value.toString();
1788
+ }
1789
+ if (value instanceof Error) {
1790
+ return `${value.name}: ${value.message}`;
1791
+ }
1792
+ if (value instanceof Map || value instanceof Set) {
1793
+ return `${value instanceof Map ? "Map" : "Set"}(${String(value.size)})`;
1794
+ }
1795
+ return value;
1796
+ }
1797
+ function json(value) {
1798
+ const ancestors = [];
1799
+ return JSON.stringify(value, function replace(_key, nested) {
1800
+ while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
1801
+ ancestors.pop();
1802
+ }
1803
+ if (typeof nested === "object" && nested !== null) {
1804
+ if (ancestors.includes(nested)) {
1805
+ return CIRCULAR;
1806
+ }
1807
+ ancestors.push(nested);
1808
+ }
1809
+ return nestedPreview(nested);
1810
+ });
1811
+ }
1812
+ function fullPreview(value) {
1813
+ switch (typeof value) {
1814
+ case "string":
1815
+ return JSON.stringify(value);
1816
+ case "bigint":
1817
+ case "function":
1818
+ case "symbol": {
1819
+ const preview = nestedPreview(value);
1820
+ return typeof preview === "string" ? preview : String(preview);
1821
+ }
1822
+ case "object":
1823
+ break;
1824
+ default:
1825
+ return String(value);
1826
+ }
1827
+ if (value === null) {
1828
+ return "null";
1829
+ }
1830
+ if (value instanceof Error || value instanceof Map || value instanceof Set) {
1831
+ return String(nestedPreview(value));
1832
+ }
1833
+ if (value instanceof Date) {
1834
+ return Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString();
1835
+ }
1836
+ const name = Array.isArray(value) ? "Array" : className(value);
1837
+ let body;
1838
+ try {
1839
+ body = json(value);
1840
+ } catch {
1841
+ return name;
1842
+ }
1843
+ if (body === void 0) {
1844
+ return name;
1845
+ }
1846
+ return name === "Object" || name === "Array" ? body : `${name} ${body}`;
1847
+ }
1848
+ function previewValue(value, maxLength = PREVIEW_LENGTH) {
1849
+ const preview = fullPreview(value);
1850
+ return preview.length <= maxLength ? preview : `${preview.slice(0, maxLength - 1)}\u2026`;
1851
+ }
1852
+
1853
+ // src/live/RunTraceContext.tsx
1854
+ var import_react12 = require("react");
1855
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1856
+ var RunTraceContext = (0, import_react12.createContext)(null);
1857
+ function RunTraceProvider({ trace, children }) {
1858
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(RunTraceContext.Provider, { value: trace, children });
1859
+ }
1860
+ function useRunTraceStore() {
1861
+ return (0, import_react12.useContext)(RunTraceContext);
1862
+ }
1863
+ function useRunTraceVersion(trace) {
1864
+ return (0, import_react12.useSyncExternalStore)(
1865
+ (onChange) => trace === null ? () => void 0 : trace.subscribe(onChange),
1866
+ () => trace === null ? 0 : trace.getVersion(),
1867
+ () => 0
1868
+ );
1869
+ }
1870
+
1871
+ // src/panels/NodeRunStats.tsx
1872
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1873
+ function NodeRunStats({ nodeId }) {
1874
+ const trace = useRunTraceStore();
1875
+ useRunTraceVersion(trace);
1876
+ if (trace === null) {
1877
+ return null;
1878
+ }
1879
+ const stats = trace.statsOf(nodeId);
1880
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("section", { className: "hl-inspector-section", children: [
1881
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h3", { className: "hl-inspector-section-title", children: "Run" }),
1882
+ stats === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "hl-inspector-hint hl-runstats-hint", children: "No activity in this run." }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("dl", { className: "hl-runstats", children: [
1883
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "hl-runstats-cell", children: [
1884
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dt", { children: "In" }),
1885
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dd", { className: "hl-mono", children: stats.received })
1886
+ ] }),
1887
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "hl-runstats-cell", children: [
1888
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dt", { children: "Out" }),
1889
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dd", { className: "hl-mono", children: stats.sent })
1890
+ ] }),
1891
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: `hl-runstats-cell${stats.errors > 0 ? " is-error" : ""}`, children: [
1892
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dt", { children: "Errors" }),
1893
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dd", { className: "hl-mono", children: stats.errors })
1894
+ ] }),
1895
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "hl-runstats-cell", children: [
1896
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dt", { children: "Avg" }),
1897
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dd", { className: "hl-mono", children: stats.received === 0 ? "\u2014" : formatDuration(stats.totalMs / stats.received) })
1898
+ ] }),
1899
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "hl-runstats-cell", children: [
1900
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dt", { children: "Max" }),
1901
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dd", { className: "hl-mono", children: stats.received === 0 ? "\u2014" : formatDuration(stats.maxMs) })
1902
+ ] }),
1903
+ stats.dropped > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "hl-runstats-cell is-warning", children: [
1904
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dt", { children: "Dropped" }),
1905
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dd", { className: "hl-mono", children: stats.dropped })
1906
+ ] }),
1907
+ stats.finished && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "hl-runstats-cell is-done", children: [
1908
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dt", { children: "State" }),
1909
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dd", { children: "Finished" })
1910
+ ] }),
1911
+ stats.received > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "hl-runstats-cell is-wide", children: [
1912
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dt", { children: "Last in" }),
1913
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dd", { className: "hl-mono", children: previewValue(stats.lastReceived) })
1914
+ ] }),
1915
+ stats.sent > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "hl-runstats-cell is-wide", children: [
1916
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dt", { children: "Last out" }),
1917
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dd", { className: "hl-mono", children: previewValue(stats.lastSent) })
1918
+ ] })
1919
+ ] })
1920
+ ] });
1921
+ }
1922
+
1193
1923
  // src/panels/InspectorPanel.tsx
1194
- var import_jsx_runtime20 = require("react/jsx-runtime");
1924
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1195
1925
  function InspectorPanel({ core, emptyState }) {
1196
1926
  const selection = (0, import_editor_react2.useSelection)(core);
1197
1927
  const selectedNodes2 = selection.selectedNodes;
1198
1928
  const singleId = selectedNodes2.length === 1 ? selectedNodes2[0] : void 0;
1199
1929
  const params = useNodeParams(core, singleId);
1200
1930
  const node = params.node;
1931
+ const singleEdgeId = selectedNodes2.length === 0 && selection.selectedEdges.length === 1 ? selection.selectedEdges[0] : void 0;
1932
+ if (node === void 0 && singleEdgeId !== void 0) {
1933
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ConnectionInspector, { core, edgeId: singleEdgeId });
1934
+ }
1201
1935
  if (node === void 0) {
1202
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "hl-inspector hl-inspector-empty", children: emptyState != null ? emptyState : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "hl-inspector-blank", children: [
1203
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-inspector-blank-badge", "aria-hidden": true }),
1204
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "hl-inspector-blank-title", children: selectedNodes2.length > 1 ? `${String(selectedNodes2.length)} nodes selected` : "Nothing selected" }),
1205
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "hl-inspector-hint", children: selectedNodes2.length > 1 ? "Select a single node to edit its parameters." : "Pick a node on the canvas or in the outline to inspect and tune it." })
1936
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "hl-inspector hl-inspector-empty", children: emptyState != null ? emptyState : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-inspector-blank", children: [
1937
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "hl-inspector-blank-badge", "aria-hidden": true }),
1938
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "hl-inspector-blank-title", children: selectedNodes2.length > 1 ? `${String(selectedNodes2.length)} nodes selected` : "Nothing selected" }),
1939
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "hl-inspector-hint", children: selectedNodes2.length > 1 ? "Select a single node to edit its parameters." : "Pick a node or a connection on the canvas to inspect and tune it." })
1206
1940
  ] }) });
1207
1941
  }
1208
1942
  const descriptor = core.registry.get(node.code);
1209
1943
  const fields = descriptor === void 0 ? [] : deriveParamSchema(descriptor, node);
1210
1944
  const imageUrl = typeof node.params.imageUrl === "string" && node.params.imageUrl.length > 0 ? node.params.imageUrl : void 0;
1211
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "hl-inspector", children: [
1212
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: `hl-inspector-preview hl-${node.kind.toLowerCase()}`, children: imageUrl !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("img", { className: "hl-inspector-preview-img", src: imageUrl, alt: node.name }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-inspector-preview-badge hl-mono", children: node.kind }) }),
1213
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("header", { className: "hl-inspector-head", children: [
1214
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: `hl-inspector-kind hl-${node.kind.toLowerCase()}`, children: node.kind }),
1215
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "hl-inspector-titles", children: [
1216
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-inspector-name", children: node.name }),
1217
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "hl-inspector-role hl-mono", children: [
1945
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-inspector", children: [
1946
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: `hl-inspector-preview hl-${node.kind.toLowerCase()}`, children: imageUrl !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("img", { className: "hl-inspector-preview-img", src: imageUrl, alt: node.name }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "hl-inspector-preview-badge hl-mono", children: node.kind }) }),
1947
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("header", { className: "hl-inspector-head", children: [
1948
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: `hl-inspector-kind hl-${node.kind.toLowerCase()}`, children: node.kind }),
1949
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-inspector-titles", children: [
1950
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "hl-inspector-name", children: node.name }),
1951
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "hl-inspector-role hl-mono", children: [
1218
1952
  node.code,
1219
1953
  " \xB7 ",
1220
1954
  KIND_ROLE[node.kind]
1221
1955
  ] })
1222
1956
  ] })
1223
1957
  ] }),
1224
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("section", { className: "hl-inspector-section", children: [
1225
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "hl-inspector-section-title", children: "Parameters" }),
1226
- fields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "hl-inspector-hint", children: "This node has no parameters." }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1958
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("section", { className: "hl-inspector-section", children: [
1959
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("h3", { className: "hl-inspector-section-title", children: "Parameters" }),
1960
+ fields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "hl-inspector-hint", children: "This node has no parameters." }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1227
1961
  ParamForm,
1228
1962
  {
1229
1963
  fields,
@@ -1231,16 +1965,17 @@ function InspectorPanel({ core, emptyState }) {
1231
1965
  }
1232
1966
  )
1233
1967
  ] }),
1234
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("section", { className: "hl-inspector-section", children: [
1235
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "hl-inspector-section-title", children: "Ports" }),
1236
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("ul", { className: "hl-inspector-ports", children: node.ports.map((port) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1968
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(NodeRunStats, { nodeId: node.id }),
1969
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("section", { className: "hl-inspector-section", children: [
1970
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("h3", { className: "hl-inspector-section-title", children: "Ports" }),
1971
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("ul", { className: "hl-inspector-ports", children: node.ports.map((port) => /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1237
1972
  "li",
1238
1973
  {
1239
1974
  className: `hl-inspector-port${port.isError ? " is-error" : ""}`,
1240
1975
  children: [
1241
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: `hl-port-dot hl-dir-${port.direction.toLowerCase()}` }),
1242
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-mono", children: port.name }),
1243
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-inspector-port-dir hl-mono", children: port.direction })
1976
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: `hl-port-dot hl-dir-${port.direction.toLowerCase()}` }),
1977
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "hl-mono", children: port.name }),
1978
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "hl-inspector-port-dir hl-mono", children: port.direction })
1244
1979
  ]
1245
1980
  },
1246
1981
  port.id
@@ -1250,14 +1985,14 @@ function InspectorPanel({ core, emptyState }) {
1250
1985
  }
1251
1986
 
1252
1987
  // src/panels/OutlineTree.tsx
1253
- var import_react9 = require("react");
1254
- var import_editor_core2 = require("@hlang-tech/editor-core");
1988
+ var import_react13 = require("react");
1989
+ var import_editor_core3 = require("@hlang-tech/editor-core");
1255
1990
  var import_editor_react3 = require("@hlang-tech/editor-react");
1256
- var import_jsx_runtime21 = require("react/jsx-runtime");
1991
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1257
1992
  function useTopologyComponents(core) {
1258
- const [tick, setTick] = (0, import_react9.useState)(0);
1259
- const bump = (0, import_react9.useCallback)(() => setTick((t) => t + 1), []);
1260
- (0, import_react9.useEffect)(() => {
1993
+ const [tick, setTick] = (0, import_react13.useState)(0);
1994
+ const bump = (0, import_react13.useCallback)(() => setTick((t) => t + 1), []);
1995
+ (0, import_react13.useEffect)(() => {
1261
1996
  const kinds = ["nodeAdded", "nodeRemoved", "nodeChanged", "edgeAdded", "edgeRemoved"];
1262
1997
  const disposers = kinds.map((k) => core.events.on(k, bump));
1263
1998
  bump();
@@ -1267,9 +2002,9 @@ function useTopologyComponents(core) {
1267
2002
  }
1268
2003
  };
1269
2004
  }, [core, bump]);
1270
- return (0, import_react9.useMemo)(() => {
2005
+ return (0, import_react13.useMemo)(() => {
1271
2006
  void tick;
1272
- const topo = new import_editor_core2.GraphTopology();
2007
+ const topo = new import_editor_core3.GraphTopology();
1273
2008
  topo.reset(core.model.getNodes(), core.model.getEdges());
1274
2009
  return topo.components();
1275
2010
  }, [core, tick]);
@@ -1279,15 +2014,15 @@ function OutlineTree({ core, onFocus }) {
1279
2014
  const selection = (0, import_editor_react3.useSelection)(core);
1280
2015
  const viewState = (0, import_editor_react3.useViewState)(core);
1281
2016
  const diagnostics = (0, import_editor_react3.useDiagnostics)(core);
1282
- const [collapsed, setCollapsed] = (0, import_react9.useState)(/* @__PURE__ */ new Set());
1283
- const nodeById = (0, import_react9.useMemo)(() => {
2017
+ const [collapsed, setCollapsed] = (0, import_react13.useState)(/* @__PURE__ */ new Set());
2018
+ const nodeById = (0, import_react13.useMemo)(() => {
1284
2019
  const m = /* @__PURE__ */ new Map();
1285
2020
  for (const n of core.model.getNodes()) {
1286
2021
  m.set(n.id, n);
1287
2022
  }
1288
2023
  return m;
1289
2024
  }, [core, components]);
1290
- const toggleGroup = (0, import_react9.useCallback)((id) => {
2025
+ const toggleGroup = (0, import_react13.useCallback)((id) => {
1291
2026
  setCollapsed((prev) => {
1292
2027
  const next = new Set(prev);
1293
2028
  if (next.has(id)) {
@@ -1298,7 +2033,7 @@ function OutlineTree({ core, onFocus }) {
1298
2033
  return next;
1299
2034
  });
1300
2035
  }, []);
1301
- const select = (0, import_react9.useCallback)(
2036
+ const select = (0, import_react13.useCallback)(
1302
2037
  (id) => {
1303
2038
  selection.selectNodes([id], "replace");
1304
2039
  onFocus == null ? void 0 : onFocus(id);
@@ -1306,79 +2041,80 @@ function OutlineTree({ core, onFocus }) {
1306
2041
  [selection, onFocus]
1307
2042
  );
1308
2043
  if (components.length === 0) {
1309
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "hl-outline-empty hl-mono", children: "no layers yet" });
2044
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "hl-outline-empty hl-mono", children: "no layers yet" });
1310
2045
  }
1311
2046
  const groups = [...components].sort((a, b) => b.nodeIds.size - a.nodeIds.size);
1312
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "hl-outline", role: "tree", "aria-label": "Outline", children: groups.map((group, gi) => {
2047
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "hl-outline", role: "tree", "aria-label": "Outline", children: groups.map((group, gi) => {
1313
2048
  const members = [...group.nodeIds].map((id) => nodeById.get(id)).filter((n) => n !== void 0);
1314
2049
  const open = !collapsed.has(group.id);
1315
2050
  const groupName = `Flow ${String(gi + 1)}`;
1316
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "hl-outline-group", role: "treeitem", "aria-expanded": open, children: [
1317
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2051
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "hl-outline-group", role: "treeitem", "aria-expanded": open, children: [
2052
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1318
2053
  "button",
1319
2054
  {
1320
2055
  className: "hl-outline-grouphead",
1321
2056
  onClick: () => toggleGroup(group.id),
1322
2057
  children: [
1323
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-caret" + (open ? " is-open" : ""), "aria-hidden": true, children: "\u25B8" }),
1324
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-groupname", children: groupName }),
1325
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-count hl-mono", children: group.nodeIds.size })
2058
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-outline-caret" + (open ? " is-open" : ""), "aria-hidden": true, children: "\u25B8" }),
2059
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-outline-groupname", children: groupName }),
2060
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-outline-count hl-mono", children: group.nodeIds.size })
1326
2061
  ]
1327
2062
  }
1328
2063
  ),
1329
- open && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { className: "hl-outline-members", children: members.map((node) => {
2064
+ open && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { className: "hl-outline-members", children: members.map((node) => {
1330
2065
  const active = selection.isSelected(node.id);
1331
2066
  const isHidden = viewState.isHidden(node.id);
1332
2067
  const isLocked = viewState.isLocked(node.id);
1333
- const hasImage = typeof node.params.imageUrl === "string" && node.params.imageUrl.length > 0;
1334
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2068
+ const { imageUrl } = node.params;
2069
+ const thumbnail = typeof imageUrl === "string" && imageUrl.length > 0 ? imageUrl : void 0;
2070
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1335
2071
  "div",
1336
2072
  {
1337
2073
  className: "hl-outline-row" + (active ? " is-active" : "") + (isHidden ? " is-hidden" : "") + (isLocked ? " is-locked" : ""),
1338
2074
  children: [
1339
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("button", { className: "hl-outline-main", onClick: () => select(node.id), children: [
1340
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2075
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("button", { className: "hl-outline-main", onClick: () => select(node.id), children: [
2076
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1341
2077
  "span",
1342
2078
  {
1343
2079
  className: `hl-outline-icon hl-kind-${node.kind.toLowerCase()}`,
1344
2080
  "aria-hidden": true,
1345
- children: hasImage ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2081
+ children: thumbnail !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1346
2082
  "img",
1347
2083
  {
1348
2084
  className: "hl-outline-thumb",
1349
- src: node.params.imageUrl,
2085
+ src: thumbnail,
1350
2086
  alt: ""
1351
2087
  }
1352
- ) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(NodeIcon, { code: node.code, kind: node.kind, size: 13 })
2088
+ ) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(NodeIcon, { code: node.code, kind: node.kind, size: 13 })
1353
2089
  }
1354
2090
  ),
1355
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-name", children: node.name }),
2091
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-outline-name", children: node.name }),
1356
2092
  (() => {
1357
2093
  const diag = diagnostics.worstOf(node.id);
1358
- return diag === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DiagnosticBadge, { diagnostic: diag, variant: "hl-outline-diag", size: 11 });
2094
+ return diag === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(DiagnosticBadge, { diagnostic: diag, variant: "hl-outline-diag", size: 11 });
1359
2095
  })(),
1360
- isLocked && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-badge", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LockIcon, { size: 11 }) }),
1361
- isHidden && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-badge", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EyeOffIcon, { size: 11 }) })
2096
+ isLocked && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-outline-badge", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(LockIcon, { size: 11 }) }),
2097
+ isHidden && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-outline-badge", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(EyeOffIcon, { size: 11 }) })
1362
2098
  ] }),
1363
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "hl-outline-controls", children: [
1364
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2099
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { className: "hl-outline-controls", children: [
2100
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1365
2101
  "button",
1366
2102
  {
1367
2103
  className: "hl-outline-ctl" + (isHidden ? " is-active-ctl" : ""),
1368
2104
  onClick: () => viewState.toggleHidden(node.id),
1369
2105
  "aria-label": isHidden ? "Show layer" : "Hide layer",
1370
2106
  title: isHidden ? "Show" : "Hide",
1371
- children: isHidden ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EyeOffIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EyeIcon, {})
2107
+ children: isHidden ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(EyeOffIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(EyeIcon, {})
1372
2108
  }
1373
2109
  ),
1374
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2110
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1375
2111
  "button",
1376
2112
  {
1377
2113
  className: "hl-outline-ctl" + (isLocked ? " is-active-ctl" : ""),
1378
2114
  onClick: () => viewState.toggleLocked(node.id),
1379
2115
  "aria-label": isLocked ? "Unlock layer" : "Lock layer",
1380
2116
  title: isLocked ? "Unlock" : "Lock",
1381
- children: isLocked ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LockIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(UnlockIcon, {})
2117
+ children: isLocked ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(LockIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(UnlockIcon, {})
1382
2118
  }
1383
2119
  )
1384
2120
  ] })
@@ -1391,21 +2127,21 @@ function OutlineTree({ core, onFocus }) {
1391
2127
  }
1392
2128
 
1393
2129
  // src/panels/PreviewPanel.tsx
1394
- var import_jsx_runtime22 = require("react/jsx-runtime");
2130
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1395
2131
  function PreviewPanel({ imageUrl, title = "Preview", status }) {
1396
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("section", { className: "hl-preview", "aria-label": "Preview", children: [
1397
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "hl-preview-head", children: [
1398
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "hl-preview-title", children: title }),
1399
- status !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "hl-preview-status hl-mono", children: status })
2132
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("section", { className: "hl-preview", "aria-label": "Preview", children: [
2133
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "hl-preview-head", children: [
2134
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-preview-title", children: title }),
2135
+ status !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-preview-status hl-mono", children: status })
1400
2136
  ] }),
1401
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "hl-preview-frame", children: imageUrl !== void 0 && imageUrl.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("img", { className: "hl-preview-img", src: imageUrl, alt: title, draggable: false }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "hl-preview-placeholder hl-mono", children: "no output yet" }) })
2137
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "hl-preview-frame", children: imageUrl !== void 0 && imageUrl.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("img", { className: "hl-preview-img", src: imageUrl, alt: title, draggable: false }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "hl-preview-placeholder hl-mono", children: "no output yet" }) })
1402
2138
  ] });
1403
2139
  }
1404
2140
 
1405
2141
  // src/panels/RailTabs.tsx
1406
- var import_jsx_runtime23 = require("react/jsx-runtime");
2142
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1407
2143
  function RailTabs({ tabs, active, onChange }) {
1408
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "hl-railtabs", role: "tablist", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2144
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "hl-railtabs", role: "tablist", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1409
2145
  "button",
1410
2146
  {
1411
2147
  role: "tab",
@@ -1419,20 +2155,20 @@ function RailTabs({ tabs, active, onChange }) {
1419
2155
  }
1420
2156
 
1421
2157
  // src/panels/AssetGrid.tsx
1422
- var import_jsx_runtime24 = require("react/jsx-runtime");
2158
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1423
2159
  function AssetGrid({ items, onSelect, emptyHint = "No assets yet" }) {
1424
2160
  if (items.length === 0) {
1425
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "hl-assetgrid-empty hl-mono", children: emptyHint });
2161
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "hl-assetgrid-empty hl-mono", children: emptyHint });
1426
2162
  }
1427
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "hl-assetgrid", children: items.map((item) => {
2163
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "hl-assetgrid", children: items.map((item) => {
1428
2164
  var _a;
1429
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2165
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1430
2166
  "button",
1431
2167
  {
1432
2168
  className: "hl-assetgrid-cell",
1433
2169
  onClick: () => onSelect == null ? void 0 : onSelect(item.id),
1434
2170
  title: item.label,
1435
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("img", { className: "hl-assetgrid-img", src: item.url, alt: (_a = item.label) != null ? _a : "", loading: "lazy" })
2171
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("img", { className: "hl-assetgrid-img", src: item.url, alt: (_a = item.label) != null ? _a : "", loading: "lazy" })
1436
2172
  },
1437
2173
  item.id
1438
2174
  );
@@ -1440,10 +2176,10 @@ function AssetGrid({ items, onSelect, emptyHint = "No assets yet" }) {
1440
2176
  }
1441
2177
 
1442
2178
  // src/panels/PositionPanel.tsx
1443
- var import_react10 = require("react");
1444
- var import_editor_core3 = require("@hlang-tech/editor-core");
2179
+ var import_react14 = require("react");
2180
+ var import_editor_core4 = require("@hlang-tech/editor-core");
1445
2181
  var import_editor_react4 = require("@hlang-tech/editor-react");
1446
- var import_jsx_runtime25 = require("react/jsx-runtime");
2182
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1447
2183
  function selectedNodes(core) {
1448
2184
  const ids = core.selection.getSelectedNodes();
1449
2185
  const out = [];
@@ -1458,8 +2194,8 @@ function selectedNodes(core) {
1458
2194
  function PositionPanel({ core, livePosition }) {
1459
2195
  var _a;
1460
2196
  (0, import_editor_react4.useSelection)(core);
1461
- const [, bump] = (0, import_react10.useReducer)((n) => n + 1, 0);
1462
- (0, import_react10.useEffect)(() => {
2197
+ const [, bump] = (0, import_react14.useReducer)((n) => n + 1, 0);
2198
+ (0, import_react14.useEffect)(() => {
1463
2199
  const kinds = ["nodeChanged", "nodeAdded", "nodeRemoved"];
1464
2200
  const disposers = kinds.map((k) => core.events.on(k, bump));
1465
2201
  return () => {
@@ -1471,7 +2207,7 @@ function PositionPanel({ core, livePosition }) {
1471
2207
  const nodes = selectedNodes(core);
1472
2208
  const single = nodes.length === 1 ? nodes[0] : void 0;
1473
2209
  const shownPos = single !== void 0 ? (_a = livePosition == null ? void 0 : livePosition(single.id)) != null ? _a : single.position : void 0;
1474
- const moveAll = (0, import_react10.useCallback)(
2210
+ const moveAll = (0, import_react14.useCallback)(
1475
2211
  (fn) => {
1476
2212
  const targets = selectedNodes(core);
1477
2213
  if (targets.length === 0) {
@@ -1479,13 +2215,13 @@ function PositionPanel({ core, livePosition }) {
1479
2215
  }
1480
2216
  core.history.transact("Align", () => {
1481
2217
  for (const n of targets) {
1482
- core.commands.execute(new import_editor_core3.MoveNodeCommand(n.id, fn(n)));
2218
+ core.commands.execute(new import_editor_core4.MoveNodeCommand(n.id, fn(n)));
1483
2219
  }
1484
2220
  });
1485
2221
  },
1486
2222
  [core]
1487
2223
  );
1488
- const align = (0, import_react10.useCallback)(
2224
+ const align = (0, import_react14.useCallback)(
1489
2225
  (kind) => {
1490
2226
  const targets = selectedNodes(core);
1491
2227
  if (targets.length === 0) {
@@ -1518,13 +2254,13 @@ function PositionPanel({ core, livePosition }) {
1518
2254
  },
1519
2255
  [core, moveAll]
1520
2256
  );
1521
- const setCoord = (0, import_react10.useCallback)(
2257
+ const setCoord = (0, import_react14.useCallback)(
1522
2258
  (axis, value) => {
1523
2259
  if (single === void 0 || Number.isNaN(value)) {
1524
2260
  return;
1525
2261
  }
1526
2262
  const next = axis === "x" ? { x: value, y: single.position.y } : { x: single.position.x, y: value };
1527
- core.commands.execute(new import_editor_core3.MoveNodeCommand(single.id, next));
2263
+ core.commands.execute(new import_editor_core4.MoveNodeCommand(single.id, next));
1528
2264
  },
1529
2265
  [core, single]
1530
2266
  );
@@ -1537,9 +2273,9 @@ function PositionPanel({ core, livePosition }) {
1537
2273
  { kind: "vcenter", label: "Align middle", d: "M4 12h16M9 8v8M15 9v6" },
1538
2274
  { kind: "bottom", label: "Align bottom", d: "M4 19h16M8 7v8M16 12v3" }
1539
2275
  ];
1540
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("section", { className: "hl-position", children: [
1541
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("h3", { className: "hl-position-title hl-mono", children: "Position" }),
1542
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "hl-position-align", role: "group", "aria-label": "Align", children: alignButtons.map((b) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2276
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("section", { className: "hl-position", children: [
2277
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("h3", { className: "hl-position-title hl-mono", children: "Position" }),
2278
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "hl-position-align", role: "group", "aria-label": "Align", children: alignButtons.map((b) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1543
2279
  "button",
1544
2280
  {
1545
2281
  className: "hl-position-alignbtn",
@@ -1547,13 +2283,13 @@ function PositionPanel({ core, livePosition }) {
1547
2283
  disabled,
1548
2284
  title: b.label,
1549
2285
  "aria-label": b.label,
1550
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("path", { d: b.d }) })
2286
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: b.d }) })
1551
2287
  },
1552
2288
  b.kind
1553
2289
  )) }),
1554
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-position-coords", children: [
1555
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("label", { className: "hl-position-field", children: [
1556
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2290
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "hl-position-coords", children: [
2291
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("label", { className: "hl-position-field", children: [
2292
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1557
2293
  "input",
1558
2294
  {
1559
2295
  type: "number",
@@ -1564,10 +2300,10 @@ function PositionPanel({ core, livePosition }) {
1564
2300
  "aria-label": "X position"
1565
2301
  }
1566
2302
  ),
1567
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "hl-position-axis hl-mono", children: "X" })
2303
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "hl-position-axis hl-mono", children: "X" })
1568
2304
  ] }),
1569
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("label", { className: "hl-position-field", children: [
1570
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2305
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("label", { className: "hl-position-field", children: [
2306
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1571
2307
  "input",
1572
2308
  {
1573
2309
  type: "number",
@@ -1578,24 +2314,24 @@ function PositionPanel({ core, livePosition }) {
1578
2314
  "aria-label": "Y position"
1579
2315
  }
1580
2316
  ),
1581
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "hl-position-axis hl-mono", children: "Y" })
2317
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "hl-position-axis hl-mono", children: "Y" })
1582
2318
  ] })
1583
2319
  ] })
1584
2320
  ] });
1585
2321
  }
1586
2322
 
1587
2323
  // src/command-palette/NodeSearch.tsx
1588
- var import_react11 = require("react");
1589
- var import_jsx_runtime26 = require("react/jsx-runtime");
2324
+ var import_react15 = require("react");
2325
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1590
2326
  var PANEL_WIDTH = 320;
1591
2327
  var PANEL_MAX_HEIGHT = 360;
1592
2328
  function NodeSearch({ controller }) {
1593
2329
  const { state } = controller;
1594
- const [query, setQuery] = (0, import_react11.useState)("");
1595
- const [active, setActive] = (0, import_react11.useState)(0);
1596
- const inputRef = (0, import_react11.useRef)(null);
2330
+ const [query, setQuery] = (0, import_react15.useState)("");
2331
+ const [active, setActive] = (0, import_react15.useState)(0);
2332
+ const inputRef = (0, import_react15.useRef)(null);
1597
2333
  const isOpen = state !== void 0;
1598
- (0, import_react11.useEffect)(() => {
2334
+ (0, import_react15.useEffect)(() => {
1599
2335
  var _a;
1600
2336
  if (isOpen) {
1601
2337
  setQuery("");
@@ -1603,12 +2339,12 @@ function NodeSearch({ controller }) {
1603
2339
  (_a = inputRef.current) == null ? void 0 : _a.focus();
1604
2340
  }
1605
2341
  }, [isOpen]);
1606
- const results = (0, import_react11.useMemo)(
2342
+ const results = (0, import_react15.useMemo)(
1607
2343
  () => state !== void 0 ? controller.results(query) : [],
1608
2344
  [controller, state, query]
1609
2345
  );
1610
2346
  const clampedActive = results.length === 0 ? 0 : Math.min(active, results.length - 1);
1611
- const choose = (0, import_react11.useCallback)(
2347
+ const choose = (0, import_react15.useCallback)(
1612
2348
  (view) => {
1613
2349
  if (view !== void 0) {
1614
2350
  controller.commit(view);
@@ -1616,7 +2352,7 @@ function NodeSearch({ controller }) {
1616
2352
  },
1617
2353
  [controller]
1618
2354
  );
1619
- const onKeyDown = (0, import_react11.useCallback)(
2355
+ const onKeyDown = (0, import_react15.useCallback)(
1620
2356
  (event) => {
1621
2357
  switch (event.key) {
1622
2358
  case "ArrowDown":
@@ -1646,8 +2382,8 @@ function NodeSearch({ controller }) {
1646
2382
  }
1647
2383
  const style = clampToViewport(state.screen.x, state.screen.y);
1648
2384
  const connectMode = state.pending !== void 0;
1649
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
1650
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2385
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
2386
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1651
2387
  "div",
1652
2388
  {
1653
2389
  className: "hl-node-search-scrim",
@@ -1656,10 +2392,10 @@ function NodeSearch({ controller }) {
1656
2392
  }
1657
2393
  }
1658
2394
  ),
1659
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "hl-node-search", style, role: "dialog", "aria-label": "Create node", children: [
1660
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "hl-node-search-field", children: [
1661
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-node-search-glyph", "aria-hidden": true, children: connectMode ? "\u2933" : "\uFF0B" }),
1662
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2395
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "hl-node-search", style, role: "dialog", "aria-label": "Create node", children: [
2396
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "hl-node-search-field", children: [
2397
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "hl-node-search-glyph", "aria-hidden": true, children: connectMode ? "\u2933" : "\uFF0B" }),
2398
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1663
2399
  "input",
1664
2400
  {
1665
2401
  ref: inputRef,
@@ -1677,12 +2413,12 @@ function NodeSearch({ controller }) {
1677
2413
  }
1678
2414
  )
1679
2415
  ] }),
1680
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("ul", { className: "hl-node-search-list", role: "listbox", children: [
1681
- results.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { className: "hl-node-search-empty hl-mono", children: "no matching nodes" }),
2416
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("ul", { className: "hl-node-search-list", role: "listbox", children: [
2417
+ results.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("li", { className: "hl-node-search-empty hl-mono", children: "no matching nodes" }),
1682
2418
  results.map((view, index) => {
1683
2419
  const descriptor = view.result.value;
1684
2420
  const kind = descriptor.kind;
1685
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2421
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1686
2422
  "li",
1687
2423
  {
1688
2424
  role: "option",
@@ -1694,15 +2430,15 @@ function NodeSearch({ controller }) {
1694
2430
  choose(view);
1695
2431
  },
1696
2432
  children: [
1697
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2433
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1698
2434
  "span",
1699
2435
  {
1700
2436
  className: `hl-node-search-kind hl-mono hl-kind-${kind.toLowerCase()}`,
1701
2437
  children: kind
1702
2438
  }
1703
2439
  ),
1704
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-node-search-label", children: highlight(descriptor.displayName, view.result.match) }),
1705
- view.connectPortId !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-node-search-port hl-mono", children: view.connectPortId })
2440
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "hl-node-search-label", children: highlight(descriptor.displayName, view.result.match) }),
2441
+ view.connectPortId !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "hl-node-search-port hl-mono", children: view.connectPortId })
1706
2442
  ]
1707
2443
  },
1708
2444
  descriptor.code
@@ -1716,7 +2452,7 @@ function highlight(text, match) {
1716
2452
  const hit = new Set(match.positions);
1717
2453
  const chars = Array.from(text);
1718
2454
  return chars.map(
1719
- (ch, i) => hit.has(i) ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("mark", { className: "hl-node-search-hit", children: ch }, i) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { children: ch }, i)
2455
+ (ch, i) => hit.has(i) ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("mark", { className: "hl-node-search-hit", children: ch }, i) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { children: ch }, i)
1720
2456
  );
1721
2457
  }
1722
2458
  function clampToViewport(x, y) {
@@ -1729,8 +2465,8 @@ function clampToViewport(x, y) {
1729
2465
  }
1730
2466
 
1731
2467
  // src/command-palette/useNodeSearch.ts
1732
- var import_react12 = require("react");
1733
- var import_editor_core4 = require("@hlang-tech/editor-core");
2468
+ var import_react16 = require("react");
2469
+ var import_editor_core5 = require("@hlang-tech/editor-core");
1734
2470
 
1735
2471
  // src/command-palette/fuzzy.ts
1736
2472
  function fuzzyScore(query, target) {
@@ -1820,21 +2556,21 @@ function firstCompletingPort(ports, pending, validator) {
1820
2556
 
1821
2557
  // src/command-palette/useNodeSearch.ts
1822
2558
  function useNodeSearch(core) {
1823
- const [state, setState] = (0, import_react12.useState)(void 0);
1824
- const openBlank = (0, import_react12.useCallback)((screen, flow) => {
2559
+ const [state, setState] = (0, import_react16.useState)(void 0);
2560
+ const openBlank = (0, import_react16.useCallback)((screen, flow) => {
1825
2561
  setState({ screen, flow });
1826
2562
  }, []);
1827
- const openFromConnection = (0, import_react12.useCallback)(
2563
+ const openFromConnection = (0, import_react16.useCallback)(
1828
2564
  (screen, flow, pending) => {
1829
2565
  setState({ screen, flow, pending });
1830
2566
  },
1831
2567
  []
1832
2568
  );
1833
- const close = (0, import_react12.useCallback)(() => {
2569
+ const close = (0, import_react16.useCallback)(() => {
1834
2570
  setState(void 0);
1835
2571
  }, []);
1836
2572
  const descriptors = core.registry.list();
1837
- const results = (0, import_react12.useCallback)(
2573
+ const results = (0, import_react16.useCallback)(
1838
2574
  (query) => {
1839
2575
  if ((state == null ? void 0 : state.pending) !== void 0) {
1840
2576
  return connectableCandidates(
@@ -1848,7 +2584,7 @@ function useNodeSearch(core) {
1848
2584
  },
1849
2585
  [state, descriptors, core]
1850
2586
  );
1851
- const commit = (0, import_react12.useCallback)(
2587
+ const commit = (0, import_react16.useCallback)(
1852
2588
  (view) => {
1853
2589
  if (state === void 0) {
1854
2590
  return;
@@ -1859,40 +2595,40 @@ function useNodeSearch(core) {
1859
2595
  const pending = state.pending;
1860
2596
  const connectPortId = view.connectPortId;
1861
2597
  core.history.transact("Create & Connect Node", () => {
1862
- core.commands.execute(new import_editor_core4.AddNodeCommand(node));
2598
+ core.commands.execute(new import_editor_core5.AddNodeCommand(node));
1863
2599
  const edgeId = core.idGenerator.nextEdgeId();
1864
2600
  const newEndpoint = { nodeId: node.id, portId: connectPortId };
1865
2601
  const [source, target] = pending.direction === "from-source" ? [pending.fromEndpoint, newEndpoint] : [newEndpoint, pending.fromEndpoint];
1866
- core.commands.execute(new import_editor_core4.ConnectCommand(edgeId, source, target));
2602
+ core.commands.execute(new import_editor_core5.ConnectCommand(edgeId, source, target));
1867
2603
  });
1868
2604
  } else {
1869
- core.commands.execute(new import_editor_core4.AddNodeCommand(node));
2605
+ core.commands.execute(new import_editor_core5.AddNodeCommand(node));
1870
2606
  }
1871
2607
  setState(void 0);
1872
2608
  },
1873
2609
  [state, core]
1874
2610
  );
1875
- return (0, import_react12.useMemo)(
2611
+ return (0, import_react16.useMemo)(
1876
2612
  () => ({ state, openBlank, openFromConnection, close, results, commit }),
1877
2613
  [state, openBlank, openFromConnection, close, results, commit]
1878
2614
  );
1879
2615
  }
1880
2616
 
1881
2617
  // src/command-palette/useNodeSearchTriggers.ts
1882
- var import_react13 = require("react");
1883
- var import_react14 = require("@xyflow/react");
2618
+ var import_react17 = require("react");
2619
+ var import_react18 = require("@xyflow/react");
1884
2620
  var DEFAULT_DOUBLE_CLICK_MS = 350;
1885
2621
  function useNodeSearchTriggers(core, search, options = {}) {
1886
2622
  var _a, _b;
1887
- const flow = (0, import_react14.useReactFlow)();
2623
+ const flow = (0, import_react18.useReactFlow)();
1888
2624
  const doubleClickMs = (_a = options.doubleClickMs) != null ? _a : DEFAULT_DOUBLE_CLICK_MS;
1889
2625
  const now = (_b = options.now) != null ? _b : defaultNow;
1890
- const startRef = (0, import_react13.useRef)(null);
1891
- const lastPaneClickRef = (0, import_react13.useRef)(Number.NEGATIVE_INFINITY);
1892
- const onConnectStart = (0, import_react13.useCallback)((_event, params) => {
2626
+ const startRef = (0, import_react17.useRef)(null);
2627
+ const lastPaneClickRef = (0, import_react17.useRef)(Number.NEGATIVE_INFINITY);
2628
+ const onConnectStart = (0, import_react17.useCallback)((_event, params) => {
1893
2629
  startRef.current = params;
1894
2630
  }, []);
1895
- const onConnectEnd = (0, import_react13.useCallback)(
2631
+ const onConnectEnd = (0, import_react17.useCallback)(
1896
2632
  (event, connectionState) => {
1897
2633
  const start = startRef.current;
1898
2634
  startRef.current = null;
@@ -1925,7 +2661,7 @@ function useNodeSearchTriggers(core, search, options = {}) {
1925
2661
  },
1926
2662
  [core, flow, search]
1927
2663
  );
1928
- const onPaneClick = (0, import_react13.useCallback)(
2664
+ const onPaneClick = (0, import_react17.useCallback)(
1929
2665
  (event) => {
1930
2666
  const t = now();
1931
2667
  const isDouble = t - lastPaneClickRef.current <= doubleClickMs;
@@ -1957,7 +2693,7 @@ function pointerOf(event) {
1957
2693
  }
1958
2694
 
1959
2695
  // src/hooks/useEditorHotkeys.ts
1960
- var import_react15 = require("react");
2696
+ var import_react19 = require("react");
1961
2697
  var import_editor_react5 = require("@hlang-tech/editor-react");
1962
2698
  function isEditableTarget(target) {
1963
2699
  if (!(target instanceof HTMLElement)) {
@@ -1976,7 +2712,7 @@ function withinScope(target, scope) {
1976
2712
  function useEditorHotkeys(core, options = {}) {
1977
2713
  const clipboard = (0, import_editor_react5.useClipboard)(core);
1978
2714
  const { scope } = options;
1979
- (0, import_react15.useEffect)(() => {
2715
+ (0, import_react19.useEffect)(() => {
1980
2716
  const onKeyDown = (event) => {
1981
2717
  if (!withinScope(event.target, scope) || isEditableTarget(event.target)) {
1982
2718
  return;
@@ -2039,33 +2775,33 @@ function useEditorHotkeys(core, options = {}) {
2039
2775
  }
2040
2776
 
2041
2777
  // src/context-menu/NodeContextMenu.tsx
2042
- var import_react16 = require("react");
2043
- var import_jsx_runtime27 = require("react/jsx-runtime");
2778
+ var import_react20 = require("react");
2779
+ var import_jsx_runtime32 = require("react/jsx-runtime");
2044
2780
  var PANEL_WIDTH2 = 208;
2045
2781
  var ITEM_HEIGHT = 34;
2046
2782
  var VIEWPORT_MARGIN = 8;
2047
2783
  var MENU_VPAD = 12;
2048
2784
  function NodeContextMenu({ controller, items }) {
2049
2785
  const { state } = controller;
2050
- const [active, setActive] = (0, import_react16.useState)(0);
2051
- const menuRef = (0, import_react16.useRef)(null);
2786
+ const [active, setActive] = (0, import_react20.useState)(0);
2787
+ const menuRef = (0, import_react20.useRef)(null);
2052
2788
  const isOpen = state !== void 0;
2053
- (0, import_react16.useEffect)(() => {
2789
+ (0, import_react20.useEffect)(() => {
2054
2790
  var _a;
2055
2791
  if (isOpen) {
2056
2792
  setActive(0);
2057
2793
  (_a = menuRef.current) == null ? void 0 : _a.focus();
2058
2794
  }
2059
2795
  }, [isOpen]);
2060
- const rows = (0, import_react16.useMemo)(
2796
+ const rows = (0, import_react20.useMemo)(
2061
2797
  () => state !== void 0 ? items(state.nodeId) : [],
2062
2798
  [items, state]
2063
2799
  );
2064
- const enabledIndices = (0, import_react16.useMemo)(
2800
+ const enabledIndices = (0, import_react20.useMemo)(
2065
2801
  () => rows.map((r, i) => r.disabled ? -1 : i).filter((i) => i >= 0),
2066
2802
  [rows]
2067
2803
  );
2068
- const move = (0, import_react16.useCallback)(
2804
+ const move = (0, import_react20.useCallback)(
2069
2805
  (delta) => {
2070
2806
  if (enabledIndices.length === 0) {
2071
2807
  return;
@@ -2079,7 +2815,7 @@ function NodeContextMenu({ controller, items }) {
2079
2815
  },
2080
2816
  [enabledIndices]
2081
2817
  );
2082
- const choose = (0, import_react16.useCallback)(
2818
+ const choose = (0, import_react20.useCallback)(
2083
2819
  (item) => {
2084
2820
  if (item === void 0 || item.disabled) {
2085
2821
  return;
@@ -2089,7 +2825,7 @@ function NodeContextMenu({ controller, items }) {
2089
2825
  },
2090
2826
  [controller]
2091
2827
  );
2092
- const onKeyDown = (0, import_react16.useCallback)(
2828
+ const onKeyDown = (0, import_react20.useCallback)(
2093
2829
  (event) => {
2094
2830
  switch (event.key) {
2095
2831
  case "ArrowDown":
@@ -2118,8 +2854,8 @@ function NodeContextMenu({ controller, items }) {
2118
2854
  return null;
2119
2855
  }
2120
2856
  const style = clampToViewport2(state.screen.x, state.screen.y, rows.length);
2121
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
2122
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2857
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
2858
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2123
2859
  "div",
2124
2860
  {
2125
2861
  className: "hl-ctx-scrim",
@@ -2132,7 +2868,7 @@ function NodeContextMenu({ controller, items }) {
2132
2868
  }
2133
2869
  }
2134
2870
  ),
2135
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2871
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2136
2872
  "div",
2137
2873
  {
2138
2874
  ref: menuRef,
@@ -2142,7 +2878,7 @@ function NodeContextMenu({ controller, items }) {
2142
2878
  "aria-label": "Node actions",
2143
2879
  tabIndex: -1,
2144
2880
  onKeyDown,
2145
- children: rows.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
2881
+ children: rows.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
2146
2882
  "button",
2147
2883
  {
2148
2884
  type: "button",
@@ -2155,9 +2891,9 @@ function NodeContextMenu({ controller, items }) {
2155
2891
  choose(item);
2156
2892
  },
2157
2893
  children: [
2158
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-ctx-item-icon", "aria-hidden": true, children: item.icon }),
2159
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-ctx-item-label", children: item.label }),
2160
- item.shortcut !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-ctx-item-key hl-mono", children: item.shortcut })
2894
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "hl-ctx-item-icon", "aria-hidden": true, children: item.icon }),
2895
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "hl-ctx-item-label", children: item.label }),
2896
+ item.shortcut !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "hl-ctx-item-key hl-mono", children: item.shortcut })
2161
2897
  ]
2162
2898
  },
2163
2899
  item.id
@@ -2177,21 +2913,21 @@ function clampToViewport2(x, y, rowCount) {
2177
2913
  }
2178
2914
 
2179
2915
  // src/context-menu/useNodeContextMenu.ts
2180
- var import_react17 = require("react");
2916
+ var import_react21 = require("react");
2181
2917
  function useNodeContextMenu() {
2182
- const [state, setState] = (0, import_react17.useState)(void 0);
2183
- const openForNode = (0, import_react17.useCallback)((screen, nodeId) => {
2918
+ const [state, setState] = (0, import_react21.useState)(void 0);
2919
+ const openForNode = (0, import_react21.useCallback)((screen, nodeId) => {
2184
2920
  setState({ screen, nodeId });
2185
2921
  }, []);
2186
- const close = (0, import_react17.useCallback)(() => {
2922
+ const close = (0, import_react21.useCallback)(() => {
2187
2923
  setState(void 0);
2188
2924
  }, []);
2189
- return (0, import_react17.useMemo)(() => ({ state, openForNode, close }), [state, openForNode, close]);
2925
+ return (0, import_react21.useMemo)(() => ({ state, openForNode, close }), [state, openForNode, close]);
2190
2926
  }
2191
2927
 
2192
2928
  // src/overlays/Cheatsheet.tsx
2193
- var import_react18 = require("react");
2194
- var import_jsx_runtime28 = require("react/jsx-runtime");
2929
+ var import_react22 = require("react");
2930
+ var import_jsx_runtime33 = require("react/jsx-runtime");
2195
2931
  var DEFAULT_GROUPS = [
2196
2932
  {
2197
2933
  title: "Edit",
@@ -2227,8 +2963,8 @@ var DEFAULT_GROUPS = [
2227
2963
  }
2228
2964
  ];
2229
2965
  function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2230
- const dialogRef = (0, import_react18.useRef)(null);
2231
- (0, import_react18.useEffect)(() => {
2966
+ const dialogRef = (0, import_react22.useRef)(null);
2967
+ (0, import_react22.useEffect)(() => {
2232
2968
  var _a;
2233
2969
  if (!open) {
2234
2970
  return;
@@ -2245,9 +2981,9 @@ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2245
2981
  if (!open) {
2246
2982
  return null;
2247
2983
  }
2248
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
2249
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "hl-cheat-scrim", onPointerDown: onClose }),
2250
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2984
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
2985
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "hl-cheat-scrim", onPointerDown: onClose }),
2986
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
2251
2987
  "div",
2252
2988
  {
2253
2989
  ref: dialogRef,
@@ -2257,18 +2993,18 @@ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2257
2993
  "aria-label": "Keyboard shortcuts",
2258
2994
  tabIndex: -1,
2259
2995
  children: [
2260
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "hl-cheat-head", children: [
2261
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "hl-cheat-title", children: "Shortcuts" }),
2262
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("button", { className: "hl-cheat-close", onClick: onClose, "aria-label": "Close", children: "\u2715" })
2996
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "hl-cheat-head", children: [
2997
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "hl-cheat-title", children: "Shortcuts" }),
2998
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("button", { className: "hl-cheat-close", onClick: onClose, "aria-label": "Close", children: "\u2715" })
2263
2999
  ] }),
2264
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "hl-cheat-grid", children: groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("section", { className: "hl-cheat-group", children: [
2265
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h3", { className: "hl-cheat-group-title hl-mono", children: group.title }),
2266
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("ul", { className: "hl-cheat-rows", children: group.rows.map((row) => (
3000
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "hl-cheat-grid", children: groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("section", { className: "hl-cheat-group", children: [
3001
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("h3", { className: "hl-cheat-group-title hl-mono", children: group.title }),
3002
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("ul", { className: "hl-cheat-rows", children: group.rows.map((row) => (
2267
3003
  // A row is a binding: one action can have several (two ways to pan),
2268
3004
  // so neither the label nor the keys alone identify it.
2269
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("li", { className: "hl-cheat-row", children: [
2270
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "hl-cheat-label", children: row.label }),
2271
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("kbd", { className: "hl-cheat-keys hl-mono", children: row.keys })
3005
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("li", { className: "hl-cheat-row", children: [
3006
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "hl-cheat-label", children: row.label }),
3007
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("kbd", { className: "hl-cheat-keys hl-mono", children: row.keys })
2272
3008
  ] }, `${row.keys}\0${row.label}`)
2273
3009
  )) })
2274
3010
  ] }, group.title)) })
@@ -2279,13 +3015,13 @@ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2279
3015
  }
2280
3016
 
2281
3017
  // src/overlays/Toast.tsx
2282
- var import_react19 = require("react");
2283
- var import_jsx_runtime29 = require("react/jsx-runtime");
2284
- var ToastCtx = (0, import_react19.createContext)({ show: () => void 0 });
3018
+ var import_react23 = require("react");
3019
+ var import_jsx_runtime34 = require("react/jsx-runtime");
3020
+ var ToastCtx = (0, import_react23.createContext)({ show: () => void 0 });
2285
3021
  function ToastProvider({ children, duration = 2200 }) {
2286
- const [items, setItems] = (0, import_react19.useState)([]);
2287
- const seq = (0, import_react19.useRef)(0);
2288
- const show = (0, import_react19.useCallback)(
3022
+ const [items, setItems] = (0, import_react23.useState)([]);
3023
+ const seq = (0, import_react23.useRef)(0);
3024
+ const show = (0, import_react23.useCallback)(
2289
3025
  (message) => {
2290
3026
  seq.current += 1;
2291
3027
  const id = seq.current;
@@ -2296,22 +3032,22 @@ function ToastProvider({ children, duration = 2200 }) {
2296
3032
  },
2297
3033
  [duration]
2298
3034
  );
2299
- const api = (0, import_react19.useMemo)(() => ({ show }), [show]);
2300
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(ToastCtx.Provider, { value: api, children: [
3035
+ const api = (0, import_react23.useMemo)(() => ({ show }), [show]);
3036
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(ToastCtx.Provider, { value: api, children: [
2301
3037
  children,
2302
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "hl-toast-stack", "aria-live": "polite", children: items.map((t) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "hl-toast", children: t.message }, t.id)) })
3038
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "hl-toast-stack", "aria-live": "polite", children: items.map((t) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "hl-toast", children: t.message }, t.id)) })
2303
3039
  ] });
2304
3040
  }
2305
3041
  function useToast() {
2306
- return (0, import_react19.useContext)(ToastCtx);
3042
+ return (0, import_react23.useContext)(ToastCtx);
2307
3043
  }
2308
3044
 
2309
3045
  // src/hooks/useLiveSignals.ts
2310
- var import_react20 = require("react");
3046
+ var import_react24 = require("react");
2311
3047
  function useLiveSignals(core, handle, explicitStore) {
2312
3048
  const contextStore = useLiveSignalStore();
2313
3049
  const store = explicitStore != null ? explicitStore : contextStore;
2314
- (0, import_react20.useEffect)(() => {
3050
+ (0, import_react24.useEffect)(() => {
2315
3051
  if (store === null) {
2316
3052
  return;
2317
3053
  }
@@ -2320,6 +3056,8 @@ function useLiveSignals(core, handle, explicitStore) {
2320
3056
  id: edge.id,
2321
3057
  sourceNodeId: edge.source.nodeId,
2322
3058
  sourcePortId: edge.source.portId,
3059
+ targetNodeId: edge.target.nodeId,
3060
+ targetPortId: edge.target.portId,
2323
3061
  isError: edge.source.portId === "@node/error"
2324
3062
  }));
2325
3063
  store.setEdgeIndex(entries);
@@ -2332,7 +3070,8 @@ function useLiveSignals(core, handle, explicitStore) {
2332
3070
  removed.dispose();
2333
3071
  };
2334
3072
  }, [core, store]);
2335
- (0, import_react20.useEffect)(() => {
3073
+ (0, import_react24.useEffect)(() => {
3074
+ var _a, _b, _c;
2336
3075
  if (store === null) {
2337
3076
  return;
2338
3077
  }
@@ -2340,15 +3079,407 @@ function useLiveSignals(core, handle, explicitStore) {
2340
3079
  store.reset();
2341
3080
  return;
2342
3081
  }
2343
- const activitySub = handle.onActivity((e) => store.markActivity(e.nodeId, e.portId));
3082
+ const signalSub = (_b = (_a = handle.onDelivery) == null ? void 0 : _a.call(handle, (event) => {
3083
+ store.markDelivery(event);
3084
+ })) != null ? _b : handle.onActivity((e) => store.markActivity(e.nodeId, e.portId));
2344
3085
  const errorSub = handle.onError((e) => store.markError(e.nodeId));
3086
+ const finishedSub = (_c = handle.onFinished) == null ? void 0 : _c.call(handle, (e) => {
3087
+ store.markFinished(e.nodeId);
3088
+ });
2345
3089
  return () => {
2346
- activitySub.dispose();
3090
+ signalSub.dispose();
2347
3091
  errorSub.dispose();
3092
+ finishedSub == null ? void 0 : finishedSub.dispose();
2348
3093
  store.reset();
2349
3094
  };
2350
3095
  }, [store, handle]);
2351
3096
  }
3097
+
3098
+ // src/live/run-trace.ts
3099
+ var RUN_LOG_CAPACITY = 500;
3100
+ var RunTrace = class {
3101
+ constructor(options = {}) {
3102
+ /** The log as a ring: `ring[(head + i) % capacity]` is the i-th oldest entry. */
3103
+ this.ring = [];
3104
+ this.head = 0;
3105
+ this.droppedCount = 0;
3106
+ this.seq = 0;
3107
+ this.startedAt = 0;
3108
+ this.stats = /* @__PURE__ */ new Map();
3109
+ this.listeners = /* @__PURE__ */ new Set();
3110
+ /** Bumped by every change; the entries cache follows it. */
3111
+ this.revision = 0;
3112
+ /** Bumped once per notified frame; what subscribers compare. */
3113
+ this.version = 0;
3114
+ this.frame = null;
3115
+ /** The entries as an array, rebuilt only after a change. */
3116
+ this.entriesCache = [];
3117
+ this.entriesRevision = -1;
3118
+ var _a, _b;
3119
+ const capacity = (_a = options.capacity) != null ? _a : RUN_LOG_CAPACITY;
3120
+ if (!Number.isInteger(capacity) || capacity < 1) {
3121
+ throw new RangeError("RunTrace capacity must be a positive integer.");
3122
+ }
3123
+ this.capacity = capacity;
3124
+ this.deps = (_b = options.deps) != null ? _b : defaultLiveDeps;
3125
+ }
3126
+ /** Begins a run: clears everything and times entries from now. */
3127
+ start() {
3128
+ this.clear();
3129
+ this.startedAt = this.deps.now();
3130
+ }
3131
+ /** Forgets the log and the statistics. */
3132
+ clear() {
3133
+ this.ring.length = 0;
3134
+ this.head = 0;
3135
+ this.droppedCount = 0;
3136
+ this.stats.clear();
3137
+ this.changed();
3138
+ }
3139
+ recordDelivery(event) {
3140
+ this.append({
3141
+ kind: "delivery",
3142
+ seq: this.nextSeq(),
3143
+ at: this.elapsed(),
3144
+ sourceNodeId: event.sourceNodeId,
3145
+ sourcePortId: event.sourcePortId,
3146
+ targetNodeId: event.targetNodeId,
3147
+ targetPortId: event.targetPortId,
3148
+ message: event.message,
3149
+ duration: event.duration
3150
+ });
3151
+ const source = this.statsFor(event.sourceNodeId);
3152
+ source.sent += 1;
3153
+ source.lastSent = event.message;
3154
+ const target = this.statsFor(event.targetNodeId);
3155
+ target.received += 1;
3156
+ target.totalMs += event.duration;
3157
+ target.maxMs = Math.max(target.maxMs, event.duration);
3158
+ target.lastReceived = event.message;
3159
+ this.changed();
3160
+ }
3161
+ recordError(event) {
3162
+ this.append({ kind: "error", seq: this.nextSeq(), at: this.elapsed(), nodeId: event.nodeId, error: event.error });
3163
+ this.statsFor(event.nodeId).errors += 1;
3164
+ this.changed();
3165
+ }
3166
+ recordEnded(event) {
3167
+ this.append({ kind: "ended", seq: this.nextSeq(), at: this.elapsed(), nodeId: event.nodeId, portId: event.portId });
3168
+ this.statsFor(event.nodeId);
3169
+ this.changed();
3170
+ }
3171
+ recordFinished(event) {
3172
+ this.append({ kind: "finished", seq: this.nextSeq(), at: this.elapsed(), nodeId: event.nodeId });
3173
+ this.statsFor(event.nodeId).finished = true;
3174
+ this.changed();
3175
+ }
3176
+ recordDropped(event) {
3177
+ this.append({
3178
+ kind: "dropped",
3179
+ seq: this.nextSeq(),
3180
+ at: this.elapsed(),
3181
+ sourceNodeId: event.sourceNodeId,
3182
+ sourcePortId: event.sourcePortId,
3183
+ targetNodeId: event.targetNodeId,
3184
+ targetPortId: event.targetPortId,
3185
+ message: event.message
3186
+ });
3187
+ this.statsFor(event.sourceNodeId);
3188
+ this.statsFor(event.targetNodeId).dropped += 1;
3189
+ this.changed();
3190
+ }
3191
+ /** The log, oldest first. The same array is returned until something changes. */
3192
+ entries() {
3193
+ if (this.entriesRevision !== this.revision) {
3194
+ const count = this.ring.length;
3195
+ const ordered = [];
3196
+ for (let i = 0; i < count; i += 1) {
3197
+ const entry = this.ring[(this.head + i) % count];
3198
+ if (entry !== void 0) {
3199
+ ordered.push(entry);
3200
+ }
3201
+ }
3202
+ this.entriesCache = ordered;
3203
+ this.entriesRevision = this.revision;
3204
+ }
3205
+ return this.entriesCache;
3206
+ }
3207
+ /** A node's statistics, or `undefined` when it has done nothing this run. */
3208
+ statsOf(nodeId) {
3209
+ const stats = this.stats.get(nodeId);
3210
+ return stats === void 0 ? void 0 : { ...stats };
3211
+ }
3212
+ /** How many entries the log has let go of to stay within its capacity. */
3213
+ get dropped() {
3214
+ return this.droppedCount;
3215
+ }
3216
+ /** Changes once per notified frame; a snapshot for `useSyncExternalStore`. */
3217
+ getVersion() {
3218
+ return this.version;
3219
+ }
3220
+ subscribe(listener) {
3221
+ this.listeners.add(listener);
3222
+ return () => {
3223
+ this.listeners.delete(listener);
3224
+ };
3225
+ }
3226
+ append(entry) {
3227
+ if (this.ring.length < this.capacity) {
3228
+ this.ring.push(entry);
3229
+ return;
3230
+ }
3231
+ this.ring[this.head] = entry;
3232
+ this.head = (this.head + 1) % this.capacity;
3233
+ this.droppedCount += 1;
3234
+ }
3235
+ nextSeq() {
3236
+ this.seq += 1;
3237
+ return this.seq;
3238
+ }
3239
+ elapsed() {
3240
+ return this.deps.now() - this.startedAt;
3241
+ }
3242
+ statsFor(nodeId) {
3243
+ let stats = this.stats.get(nodeId);
3244
+ if (stats === void 0) {
3245
+ stats = {
3246
+ received: 0,
3247
+ sent: 0,
3248
+ errors: 0,
3249
+ totalMs: 0,
3250
+ maxMs: 0,
3251
+ lastReceived: void 0,
3252
+ lastSent: void 0,
3253
+ dropped: 0,
3254
+ finished: false
3255
+ };
3256
+ this.stats.set(nodeId, stats);
3257
+ }
3258
+ return stats;
3259
+ }
3260
+ /** Schedules one notification for the next frame, however many changes come before it. */
3261
+ changed() {
3262
+ this.revision += 1;
3263
+ if (this.frame !== null) {
3264
+ return;
3265
+ }
3266
+ this.frame = this.deps.schedule(() => {
3267
+ this.frame = null;
3268
+ this.version += 1;
3269
+ for (const listener of [...this.listeners]) {
3270
+ listener();
3271
+ }
3272
+ });
3273
+ }
3274
+ };
3275
+ function involves(entry, nodeId) {
3276
+ switch (entry.kind) {
3277
+ case "delivery":
3278
+ case "dropped":
3279
+ return entry.sourceNodeId === nodeId || entry.targetNodeId === nodeId;
3280
+ case "error":
3281
+ case "ended":
3282
+ case "finished":
3283
+ return entry.nodeId === nodeId;
3284
+ }
3285
+ }
3286
+
3287
+ // src/hooks/useRunTrace.ts
3288
+ var import_react25 = require("react");
3289
+ function useRunTrace(trace, handle) {
3290
+ (0, import_react25.useEffect)(() => {
3291
+ var _a, _b, _c, _d;
3292
+ if (handle === void 0) {
3293
+ return;
3294
+ }
3295
+ trace.start();
3296
+ const deliveries = (_a = handle.onDelivery) == null ? void 0 : _a.call(handle, (event) => {
3297
+ trace.recordDelivery(event);
3298
+ });
3299
+ const errors = handle.onError((event) => {
3300
+ trace.recordError(event);
3301
+ });
3302
+ const ended = (_b = handle.onEnded) == null ? void 0 : _b.call(handle, (event) => {
3303
+ trace.recordEnded(event);
3304
+ });
3305
+ const finished = (_c = handle.onFinished) == null ? void 0 : _c.call(handle, (event) => {
3306
+ trace.recordFinished(event);
3307
+ });
3308
+ const dropped = (_d = handle.onDropped) == null ? void 0 : _d.call(handle, (event) => {
3309
+ trace.recordDropped(event);
3310
+ });
3311
+ return () => {
3312
+ deliveries == null ? void 0 : deliveries.dispose();
3313
+ errors.dispose();
3314
+ ended == null ? void 0 : ended.dispose();
3315
+ finished == null ? void 0 : finished.dispose();
3316
+ dropped == null ? void 0 : dropped.dispose();
3317
+ };
3318
+ }, [trace, handle]);
3319
+ }
3320
+
3321
+ // src/hooks/useFollowScroll.ts
3322
+ var import_react26 = require("react");
3323
+ var FOLLOW_SLACK_PX = 24;
3324
+ function useFollowScroll() {
3325
+ const ref = (0, import_react26.useRef)(null);
3326
+ const [follow, setFollow] = (0, import_react26.useState)(true);
3327
+ (0, import_react26.useLayoutEffect)(() => {
3328
+ const element = ref.current;
3329
+ if (follow && element !== null) {
3330
+ element.scrollTop = element.scrollHeight;
3331
+ }
3332
+ });
3333
+ const onScroll = (0, import_react26.useCallback)((event) => {
3334
+ const element = event.currentTarget;
3335
+ setFollow(element.scrollHeight - element.scrollTop - element.clientHeight <= FOLLOW_SLACK_PX);
3336
+ }, []);
3337
+ return { ref, onScroll, follow, setFollow };
3338
+ }
3339
+
3340
+ // src/panels/RunLog.tsx
3341
+ var import_react27 = require("react");
3342
+ var import_editor_react6 = require("@hlang-tech/editor-react");
3343
+ var import_jsx_runtime35 = require("react/jsx-runtime");
3344
+ function RunLog({ core, onSelectNode, title = "Run log", leading }) {
3345
+ const trace = useRunTraceStore();
3346
+ useRunTraceVersion(trace);
3347
+ const selection = (0, import_editor_react6.useSelection)(core);
3348
+ const [onlySelected, setOnlySelected] = (0, import_react27.useState)(false);
3349
+ const { ref: bodyRef, onScroll, follow, setFollow } = useFollowScroll();
3350
+ const all = trace === null ? [] : trace.entries();
3351
+ const selectedId = selection.selectedNodes.length === 1 ? selection.selectedNodes[0] : void 0;
3352
+ const filterId = onlySelected ? selectedId : void 0;
3353
+ const entries = filterId === void 0 ? all : all.filter((entry) => involves(entry, filterId));
3354
+ if (trace === null) {
3355
+ return null;
3356
+ }
3357
+ const label = (nodeId) => {
3358
+ var _a, _b;
3359
+ return (_b = (_a = core.model.getNode(nodeId)) == null ? void 0 : _a.name) != null ? _b : nodeId;
3360
+ };
3361
+ const choose = (nodeId) => {
3362
+ core.selection.selectNodes([nodeId], "replace");
3363
+ onSelectNode == null ? void 0 : onSelectNode(nodeId);
3364
+ };
3365
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("section", { className: "hl-runlog", "aria-label": title, children: [
3366
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("header", { className: "hl-runlog-head", children: [
3367
+ leading != null ? leading : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-title", children: title }),
3368
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "hl-runlog-count hl-mono", children: [
3369
+ entries.length === all.length ? `${String(all.length)} events` : `${String(entries.length)} of ${String(all.length)} events`,
3370
+ trace.dropped > 0 ? ` \xB7 ${String(trace.dropped)} earlier not kept` : ""
3371
+ ] }),
3372
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "hl-runlog-actions", children: [
3373
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3374
+ "button",
3375
+ {
3376
+ type: "button",
3377
+ className: `hl-runlog-toggle${onlySelected ? " is-on" : ""}`,
3378
+ "aria-pressed": onlySelected,
3379
+ disabled: selectedId === void 0 && !onlySelected,
3380
+ title: selectedId === void 0 ? "Select a node to filter by it" : `Only ${label(selectedId)}`,
3381
+ onClick: () => {
3382
+ setOnlySelected((value) => !value);
3383
+ },
3384
+ children: onlySelected && selectedId !== void 0 ? `Only ${label(selectedId)}` : "Selected node"
3385
+ }
3386
+ ),
3387
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3388
+ "button",
3389
+ {
3390
+ type: "button",
3391
+ className: `hl-runlog-toggle${follow ? " is-on" : ""}`,
3392
+ "aria-pressed": follow,
3393
+ onClick: () => {
3394
+ setFollow(!follow);
3395
+ },
3396
+ children: "Follow"
3397
+ }
3398
+ ),
3399
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3400
+ "button",
3401
+ {
3402
+ type: "button",
3403
+ className: "hl-runlog-toggle",
3404
+ disabled: all.length === 0,
3405
+ onClick: () => {
3406
+ trace.clear();
3407
+ },
3408
+ children: "Clear"
3409
+ }
3410
+ )
3411
+ ] })
3412
+ ] }),
3413
+ entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "hl-runlog-empty", children: all.length === 0 ? "Run the graph to see every message as it moves through it." : "Nothing involving the selected node yet." }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ol", { className: "hl-runlog-body hl-mono", ref: bodyRef, onScroll, children: entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(Row, { entry, label, onChoose: choose }, entry.seq)) })
3414
+ ] });
3415
+ }
3416
+ function Row({
3417
+ entry,
3418
+ label,
3419
+ onChoose
3420
+ }) {
3421
+ switch (entry.kind) {
3422
+ case "error":
3423
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(Event, { className: "is-error", at: entry.at, onChoose: () => onChoose(entry.nodeId), ms: "", children: [
3424
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(NodePort, { node: label(entry.nodeId), port: "error" }),
3425
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-value", children: previewValue(entry.error) })
3426
+ ] });
3427
+ case "ended":
3428
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(Event, { className: "is-end", at: entry.at, onChoose: () => onChoose(entry.nodeId), ms: "", children: [
3429
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(NodePort, { node: label(entry.nodeId), port: entry.portId }),
3430
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-value", children: "ended" })
3431
+ ] });
3432
+ case "finished":
3433
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(Event, { className: "is-end", at: entry.at, onChoose: () => onChoose(entry.nodeId), ms: "", children: [
3434
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(NodePort, { node: label(entry.nodeId), port: "" }),
3435
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-value", children: "finished" })
3436
+ ] });
3437
+ case "dropped":
3438
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(Event, { className: "is-dropped", at: entry.at, onChoose: () => onChoose(entry.targetNodeId), ms: "", children: [
3439
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(Route, { entry, label }),
3440
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "hl-runlog-value", children: [
3441
+ "dropped ",
3442
+ previewValue(entry.message)
3443
+ ] })
3444
+ ] });
3445
+ case "delivery":
3446
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(Event, { className: "", at: entry.at, onChoose: () => onChoose(entry.targetNodeId), ms: formatDuration(entry.duration), children: [
3447
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(Route, { entry, label }),
3448
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-value", children: previewValue(entry.message) })
3449
+ ] });
3450
+ }
3451
+ }
3452
+ function Event({
3453
+ className: className2,
3454
+ at,
3455
+ ms,
3456
+ onChoose,
3457
+ children
3458
+ }) {
3459
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { className: `hl-runlog-row ${className2}`.trim(), children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("button", { type: "button", className: "hl-runlog-hit", onClick: onChoose, children: [
3460
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-at", children: formatElapsed(at) }),
3461
+ children,
3462
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-ms", children: ms })
3463
+ ] }) });
3464
+ }
3465
+ function NodePort({ node, port }) {
3466
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "hl-runlog-route", children: [
3467
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-node", children: node }),
3468
+ port === "" ? null : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-port", children: port })
3469
+ ] });
3470
+ }
3471
+ function Route({
3472
+ entry,
3473
+ label
3474
+ }) {
3475
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "hl-runlog-route", children: [
3476
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-node", children: label(entry.sourceNodeId) }),
3477
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-port", children: entry.sourcePortId }),
3478
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-arrow", "aria-label": "to", children: "\u2192" }),
3479
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-node", children: label(entry.targetNodeId) }),
3480
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-port", children: entry.targetPortId })
3481
+ ] });
3482
+ }
2352
3483
  // Annotate the CommonJS export names for ESM import in node:
2353
3484
  0 && (module.exports = {
2354
3485
  AssetGrid,
@@ -2356,30 +3487,45 @@ function useLiveSignals(core, handle, explicitStore) {
2356
3487
  CanvasChrome,
2357
3488
  CanvasDock,
2358
3489
  Cheatsheet,
3490
+ ConnectionInspector,
3491
+ DEFAULT_EDGE_CAPACITY,
3492
+ DEFAULT_MIN_CANVAS_HEIGHT,
2359
3493
  DiagnosticBadge,
2360
3494
  EditorShell,
2361
3495
  EditorUIThemeProvider,
2362
3496
  EmptyState,
3497
+ FOLLOW_SLACK_PX,
3498
+ FOOTER_KEY_STEP,
2363
3499
  FlowEdge,
3500
+ FooterResizer,
2364
3501
  HlangNode,
2365
3502
  InspectorPanel,
2366
3503
  KIND_ROLE,
2367
3504
  KIND_TOKEN,
3505
+ LivePulse,
2368
3506
  LiveSignalProvider,
2369
3507
  LiveSignalStore,
2370
3508
  NodeBodySlot,
2371
3509
  NodeChrome,
2372
3510
  NodeContextMenu,
2373
3511
  NodeInteractionProvider,
3512
+ NodeRunStats,
2374
3513
  NodeSearch,
2375
3514
  OutlineTree,
3515
+ PREVIEW_LENGTH,
2376
3516
  ParamForm,
2377
3517
  PillNodeChrome,
2378
3518
  PortRow,
2379
3519
  PositionPanel,
2380
3520
  PreviewPanel,
3521
+ RUN_LOG_CAPACITY,
2381
3522
  RailTabs,
2382
3523
  RunButton,
3524
+ RunLog,
3525
+ RunTrace,
3526
+ RunTraceProvider,
3527
+ SELF_LOOP_CLEARANCE,
3528
+ SELF_LOOP_RADIUS,
2383
3529
  SignalBar,
2384
3530
  TOKEN_NAMES,
2385
3531
  ToastProvider,
@@ -2388,26 +3534,47 @@ function useLiveSignals(core, handle, explicitStore) {
2388
3534
  ToolbarGhostButton,
2389
3535
  ToolbarStat,
2390
3536
  UndoRedo,
3537
+ boundsFields,
3538
+ capacityLabel,
3539
+ clampFooter,
2391
3540
  connectableCandidates,
2392
3541
  createHlangFlowTypes,
2393
3542
  createTheme,
3543
+ defaultLiveDeps,
2394
3544
  deriveParamSchema,
2395
3545
  descriptorCandidates,
3546
+ footerBounds,
3547
+ formatDuration,
3548
+ formatElapsed,
2396
3549
  fuzzyRank,
2397
3550
  fuzzyScore,
3551
+ involves,
2398
3552
  isEditableTarget,
3553
+ nextBounds,
3554
+ previewValue,
3555
+ resizeByKey,
2399
3556
  searchNodes,
3557
+ selfLoopPath,
3558
+ toggleCollapsed,
2400
3559
  tokenVar,
3560
+ useEdgeBounds,
2401
3561
  useEdgeFlowing,
3562
+ useEdgePulse,
2402
3563
  useEditorHotkeys,
3564
+ useFollowScroll,
3565
+ useFooterResize,
2403
3566
  useLiveSignalStore,
2404
3567
  useLiveSignals,
2405
3568
  useNodeContextMenu,
2406
3569
  useNodeInteraction,
2407
3570
  useNodeLiveState,
2408
3571
  useNodeParams,
3572
+ useNodePulse,
2409
3573
  useNodeSearch,
2410
3574
  useNodeSearchTriggers,
3575
+ useRunTrace,
3576
+ useRunTraceStore,
3577
+ useRunTraceVersion,
2411
3578
  useToast
2412
3579
  });
2413
3580
  //# sourceMappingURL=index.js.map