@hlang-org/editor-ui 0.3.0 → 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);
@@ -149,12 +185,12 @@ function createTheme(overrides) {
149
185
  var import_jsx_runtime = require("react/jsx-runtime");
150
186
  function EditorUIThemeProvider({
151
187
  theme,
152
- className,
188
+ className: className2,
153
189
  style,
154
190
  children
155
191
  }) {
156
192
  const themeStyle = theme ? createTheme(theme) : void 0;
157
- 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 });
158
194
  }
159
195
 
160
196
  // src/nodes/SignalBar.tsx
@@ -204,7 +240,10 @@ var ACTIVE_WINDOW_MS = 700;
204
240
  function portKey(nodeId, portId) {
205
241
  return `${nodeId}@${portId}`;
206
242
  }
207
- var defaultDeps = {
243
+ function connectionKey(sourceNodeId, sourcePortId, targetNodeId, targetPortId) {
244
+ return `${portKey(sourceNodeId, sourcePortId)}->${portKey(targetNodeId, targetPortId)}`;
245
+ }
246
+ var defaultLiveDeps = {
208
247
  now: () => typeof performance !== "undefined" ? performance.now() : Date.now(),
209
248
  schedule: (cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : (
210
249
  // `window.setTimeout` returns a numeric handle (the DOM overload),
@@ -215,11 +254,13 @@ var defaultDeps = {
215
254
  cancel: (h) => typeof cancelAnimationFrame !== "undefined" ? cancelAnimationFrame(h) : window.clearTimeout(h)
216
255
  };
217
256
  var LiveSignalStore = class {
218
- constructor(deps = defaultDeps) {
257
+ constructor(deps = defaultLiveDeps) {
219
258
  /** nodeId -> last-active timestamp. */
220
259
  this.nodeActiveAt = /* @__PURE__ */ new Map();
221
260
  /** nodeId set that errored during this run. */
222
261
  this.erroredNodes = /* @__PURE__ */ new Set();
262
+ /** nodeId set that finished during this run (RFC 0001). */
263
+ this.finishedNodes = /* @__PURE__ */ new Set();
223
264
  /** edgeId -> last-flowing timestamp. */
224
265
  this.edgeFlowAt = /* @__PURE__ */ new Map();
225
266
  /** Per-id listeners. */
@@ -230,12 +271,21 @@ var LiveSignalStore = class {
230
271
  this.lastEdgeFlowing = /* @__PURE__ */ new Map();
231
272
  /** Edges sourced by a given (nodeId@portId), for pulse -> edge mapping. */
232
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();
233
282
  this.rafHandle = null;
234
283
  this.running = false;
235
284
  this.deps = deps;
236
285
  }
237
286
  /** Provide the current edge index (call whenever edges change). */
238
287
  setEdgeIndex(edges) {
288
+ var _a;
239
289
  const next = /* @__PURE__ */ new Map();
240
290
  for (const edge of edges) {
241
291
  const key = portKey(edge.sourceNodeId, edge.sourcePortId);
@@ -247,6 +297,44 @@ var LiveSignalStore = class {
247
297
  }
248
298
  }
249
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;
250
338
  }
251
339
  /** Record a node emitting on an output port (drives node + edge activity). */
252
340
  markActivity(nodeId, portId) {
@@ -260,6 +348,11 @@ var LiveSignalStore = class {
260
348
  }
261
349
  this.ensureLoop();
262
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
+ }
263
356
  /** Record a node erroring (sticky for the run). */
264
357
  markError(nodeId) {
265
358
  this.erroredNodes.add(nodeId);
@@ -269,7 +362,10 @@ var LiveSignalStore = class {
269
362
  reset() {
270
363
  this.nodeActiveAt.clear();
271
364
  this.erroredNodes.clear();
365
+ this.finishedNodes.clear();
272
366
  this.edgeFlowAt.clear();
367
+ this.nodePulse.clear();
368
+ this.edgePulse.clear();
273
369
  this.stopLoop();
274
370
  this.flushNotifications();
275
371
  }
@@ -290,7 +386,7 @@ var LiveSignalStore = class {
290
386
  if (at !== void 0 && this.deps.now() - at <= ACTIVE_WINDOW_MS) {
291
387
  return "active";
292
388
  }
293
- return "idle";
389
+ return this.finishedNodes.has(nodeId) ? "finished" : "idle";
294
390
  }
295
391
  /** Whether an edge is currently flowing. */
296
392
  isEdgeFlowing(edgeId) {
@@ -350,8 +446,10 @@ var LiveSignalStore = class {
350
446
  if (state !== "idle") {
351
447
  anyLive = anyLive || state === "active";
352
448
  }
353
- if (this.lastNodeState.get(nodeId) !== state) {
449
+ const pulse = this.getNodePulse(nodeId);
450
+ if (this.lastNodeState.get(nodeId) !== state || this.lastNodePulse.get(nodeId) !== pulse) {
354
451
  this.lastNodeState.set(nodeId, state);
452
+ this.lastNodePulse.set(nodeId, pulse);
355
453
  for (const listener of listeners) {
356
454
  listener();
357
455
  }
@@ -360,8 +458,10 @@ var LiveSignalStore = class {
360
458
  for (const [edgeId, listeners] of this.edgeListeners) {
361
459
  const flowing = this.isEdgeFlowing(edgeId);
362
460
  anyLive = anyLive || flowing;
363
- if (this.lastEdgeFlowing.get(edgeId) !== flowing) {
461
+ const pulse = this.getEdgePulse(edgeId);
462
+ if (this.lastEdgeFlowing.get(edgeId) !== flowing || this.lastEdgePulse.get(edgeId) !== pulse) {
364
463
  this.lastEdgeFlowing.set(edgeId, flowing);
464
+ this.lastEdgePulse.set(edgeId, pulse);
365
465
  for (const listener of listeners) {
366
466
  listener();
367
467
  }
@@ -389,6 +489,22 @@ function useNodeLiveState(nodeId) {
389
489
  () => "idle"
390
490
  );
391
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
+ }
392
508
  function useEdgeFlowing(edgeId) {
393
509
  const store = (0, import_react2.useContext)(LiveSignalContext);
394
510
  return (0, import_react2.useSyncExternalStore)(
@@ -398,8 +514,15 @@ function useEdgeFlowing(edgeId) {
398
514
  );
399
515
  }
400
516
 
401
- // src/nodes/AssetNode.tsx
517
+ // src/live/LivePulse.tsx
402
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");
403
526
  function stringParam(node, key) {
404
527
  const v = node.params[key];
405
528
  return typeof v === "string" ? v : void 0;
@@ -411,22 +534,24 @@ function AssetNodeChrome({ node, selected }) {
411
534
  const role = stringParam(node, "role");
412
535
  const live = useNodeLiveState(node.id);
413
536
  const outputs = node.ports.filter((p) => p.direction === "O" && !p.isError);
414
- const className = [
537
+ const className2 = [
415
538
  "hl-asset",
416
539
  selected ? "is-selected" : "",
417
540
  live === "active" ? "is-active" : "",
418
- live === "error" ? "is-live-error" : ""
541
+ live === "error" ? "is-live-error" : "",
542
+ live === "finished" ? "is-finished" : ""
419
543
  ].filter(Boolean).join(" ");
420
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className, "data-kind": node.kind, children: [
421
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hl-asset-frame", children: [
422
- 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 }),
423
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hl-asset-scrim" }),
424
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hl-asset-caption", children: [
425
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "hl-asset-name", children: caption }),
426
- 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 })
427
552
  ] })
428
553
  ] }),
429
- /* @__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)) })
430
555
  ] });
431
556
  }
432
557
 
@@ -434,9 +559,9 @@ function AssetNodeChrome({ node, selected }) {
434
559
  var import_react4 = require("react");
435
560
 
436
561
  // src/icons/node-icons.tsx
437
- var import_jsx_runtime7 = require("react/jsx-runtime");
562
+ var import_jsx_runtime8 = require("react/jsx-runtime");
438
563
  function svg(size, children) {
439
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
564
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
440
565
  "svg",
441
566
  {
442
567
  width: size,
@@ -454,49 +579,49 @@ function svg(size, children) {
454
579
  }
455
580
  var BY_CODE = {
456
581
  "image-source": (s) => svg(s, [
457
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2.5" }, "r"),
458
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "8.5", cy: "9", r: "1.6" }, "c"),
459
- /* @__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")
460
585
  ]),
461
- "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")]),
462
587
  "interval-source": (s) => svg(s, [
463
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "12", r: "8" }, "c"),
464
- /* @__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")
465
590
  ]),
466
591
  "range-source": (s) => svg(s, [
467
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 12h16" }, "l"),
468
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "6", cy: "12", r: "2" }, "a"),
469
- /* @__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")
470
595
  ]),
471
- "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")]),
472
597
  "map-scale": (s) => svg(s, [
473
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 20V8l8-4" }, "a"),
474
- /* @__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")
475
600
  ]),
476
- 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")]),
477
- "filter-threshold": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 5h16l-6 7v6l-4-2v-4z" }, "p")]),
478
- "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")]),
479
604
  delay: (s) => svg(s, [
480
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "13", r: "7" }, "c"),
481
- /* @__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")
482
607
  ]),
483
- 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")]),
484
609
  "console-sink": (s) => svg(s, [
485
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "4", y: "5", width: "16", height: "12", rx: "2" }, "r"),
486
- /* @__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")
487
612
  ]),
488
613
  "collect-sink": (s) => svg(s, [
489
- /* @__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")
490
615
  ])
491
616
  };
492
617
  var BY_KIND = {
493
- 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")]),
494
619
  // source ▷ (play)
495
- 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")]),
496
621
  // transform
497
622
  W: (s) => svg(s, [
498
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5 4h11l3 3v13H5z" }, "p"),
499
- /* @__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")
500
625
  ])
501
626
  // sink (save)
502
627
  };
@@ -507,61 +632,61 @@ function NodeIcon({ code, kind, size = 15 }) {
507
632
  }
508
633
  function EyeIcon({ size = 14 }) {
509
634
  return svg(size, [
510
- /* @__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"),
511
- /* @__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")
512
637
  ]);
513
638
  }
514
639
  function EyeOffIcon({ size = 14 }) {
515
640
  return svg(size, [
516
- /* @__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"),
517
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M9.9 9.9a2.6 2.6 0 003.7 3.7" }, "e"),
518
- /* @__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")
519
644
  ]);
520
645
  }
521
646
  function UnlockIcon({ size = 14 }) {
522
647
  return svg(size, [
523
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "5", y: "11", width: "14", height: "9", rx: "2" }, "r"),
524
- /* @__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")
525
650
  ]);
526
651
  }
527
652
  function LockIcon({ size = 14 }) {
528
653
  return svg(size, [
529
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "5", y: "11", width: "14", height: "9", rx: "2" }, "r"),
530
- /* @__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")
531
656
  ]);
532
657
  }
533
658
  function WarningIcon({ size = 13 }) {
534
659
  return svg(size, [
535
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 4l9 15H3z" }, "t"),
536
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 10v4" }, "b"),
537
- /* @__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")
538
663
  ]);
539
664
  }
540
665
  function ErrorIcon({ size = 13 }) {
541
666
  return svg(size, [
542
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "12", r: "9" }, "c"),
543
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 7v6" }, "b"),
544
- /* @__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")
545
670
  ]);
546
671
  }
547
672
 
548
673
  // src/panels/DiagnosticBadge.tsx
549
- var import_jsx_runtime8 = require("react/jsx-runtime");
674
+ var import_jsx_runtime9 = require("react/jsx-runtime");
550
675
  function DiagnosticBadge({ diagnostic, variant, size = 12 }) {
551
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
676
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
552
677
  "span",
553
678
  {
554
679
  className: `${variant} hl-diag-${diagnostic.severity}`,
555
680
  title: diagnostic.message,
556
681
  "aria-label": `${diagnostic.severity}: ${diagnostic.message}`,
557
- 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 })
558
683
  }
559
684
  );
560
685
  }
561
686
 
562
687
  // src/nodes/NodeInteractionContext.tsx
563
688
  var import_react3 = require("react");
564
- var import_jsx_runtime9 = require("react/jsx-runtime");
689
+ var import_jsx_runtime10 = require("react/jsx-runtime");
565
690
  var NoopInteraction = {
566
691
  diagnosticFor: () => void 0,
567
692
  rename: () => void 0
@@ -571,14 +696,14 @@ function NodeInteractionProvider({
571
696
  value,
572
697
  children
573
698
  }) {
574
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(NodeInteractionCtx.Provider, { value, children });
699
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(NodeInteractionCtx.Provider, { value, children });
575
700
  }
576
701
  function useNodeInteraction() {
577
702
  return (0, import_react3.useContext)(NodeInteractionCtx);
578
703
  }
579
704
 
580
705
  // src/nodes/PillNode.tsx
581
- var import_jsx_runtime10 = require("react/jsx-runtime");
706
+ var import_jsx_runtime11 = require("react/jsx-runtime");
582
707
  var DOUBLE_CLICK_MS = 320;
583
708
  function PillNodeChrome({ node, selected }) {
584
709
  const live = useNodeLiveState(node.id);
@@ -624,18 +749,20 @@ function PillNodeChrome({ node, selected }) {
624
749
  }, []);
625
750
  const inputs = node.ports.filter((p) => p.direction === "I" && !p.isError);
626
751
  const outputs = node.ports.filter((p) => p.direction === "O" && !p.isError);
627
- const className = [
752
+ const className2 = [
628
753
  "hl-pill",
629
754
  `hl-pill-${node.kind.toLowerCase()}`,
630
755
  selected ? "is-selected" : "",
631
756
  live === "active" ? "is-active" : "",
632
757
  live === "error" ? "is-live-error" : "",
758
+ live === "finished" ? "is-finished" : "",
633
759
  (diagnostic == null ? void 0 : diagnostic.severity) === "error" ? "has-diag-error" : "",
634
760
  (diagnostic == null ? void 0 : diagnostic.severity) === "warning" ? "has-diag-warning" : ""
635
761
  ].filter(Boolean).join(" ");
636
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className, "data-kind": node.kind, children: [
637
- /* @__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 }) }),
638
- 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)(
639
766
  "input",
640
767
  {
641
768
  ref: inputRef,
@@ -647,7 +774,7 @@ function PillNodeChrome({ node, selected }) {
647
774
  spellCheck: false,
648
775
  "aria-label": "Rename node"
649
776
  }
650
- ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
777
+ ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
651
778
  "span",
652
779
  {
653
780
  className: "hl-pill-name",
@@ -658,60 +785,167 @@ function PillNodeChrome({ node, selected }) {
658
785
  children: node.name
659
786
  }
660
787
  ),
661
- diagnostic !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DiagnosticBadge, { diagnostic, variant: "hl-pill-diag", size: 12 }),
662
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "hl-pill-status", "aria-hidden": true }),
663
- /* @__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)) }),
664
- /* @__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)) })
665
792
  ] });
666
793
  }
667
794
 
668
795
  // src/nodes/HlangNode.tsx
669
- var import_jsx_runtime11 = require("react/jsx-runtime");
796
+ var import_jsx_runtime12 = require("react/jsx-runtime");
670
797
  function NodeChrome({ node, selected, body }) {
671
798
  const inputs = node.ports.filter((p) => p.direction === "I");
672
799
  const outputs = node.ports.filter((p) => p.direction === "O");
673
800
  const live = useNodeLiveState(node.id);
674
- const className = [
801
+ const className2 = [
675
802
  "hl-node",
676
803
  `hl-${node.kind.toLowerCase()}`,
677
804
  selected ? "is-selected" : "",
678
805
  live === "active" ? "is-active" : "",
679
- live === "error" ? "is-live-error" : ""
806
+ live === "error" ? "is-live-error" : "",
807
+ live === "finished" ? "is-finished" : ""
680
808
  ].filter(Boolean).join(" ");
681
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className, "data-kind": node.kind, children: [
682
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SignalBar, {}),
683
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("header", { className: "hl-head", children: [
684
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "hl-kind", children: node.kind }),
685
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "hl-name", children: node.name }),
686
- /* @__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] })
687
816
  ] }),
688
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(NodeBodySlot, { node, children: body }),
689
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "hl-ports", children: [
690
- /* @__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)) }),
691
- /* @__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)) })
692
821
  ] })
693
822
  ] });
694
823
  }
695
824
  function HlangNode({ data, selected }) {
696
825
  const node = data.editorNode;
697
826
  if (typeof node.params.imageUrl === "string" && node.params.imageUrl.length > 0) {
698
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AssetNodeChrome, { node, selected });
827
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AssetNodeChrome, { node, selected });
699
828
  }
700
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PillNodeChrome, { node, selected });
829
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PillNodeChrome, { node, selected });
701
830
  }
702
831
 
703
832
  // src/edges/FlowEdge.tsx
704
833
  var import_react5 = require("@xyflow/react");
705
- var import_jsx_runtime12 = require("react/jsx-runtime");
706
- function useSourceRole(sourceId) {
707
- const node = (0, import_react5.useInternalNode)(sourceId);
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) {
708
933
  const role = node == null ? void 0 : node.data.editorNode.params.role;
709
934
  return typeof role === "string" && role.length > 0 ? role : void 0;
710
935
  }
711
936
  function FlowEdge(props) {
712
- var _a;
713
- const { id, source, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data } = props;
714
- 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)({
715
949
  sourceX,
716
950
  sourceY,
717
951
  sourcePosition,
@@ -719,25 +953,39 @@ function FlowEdge(props) {
719
953
  targetY,
720
954
  targetPosition
721
955
  });
956
+ const path = (_a = loop == null ? void 0 : loop.path) != null ? _a : curve;
722
957
  const t = 0.62;
723
- const labelX = sourceX + (targetX - sourceX) * t;
724
- const labelY = sourceY + (targetY - sourceY) * t;
725
- 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;
726
964
  const isValue = (data == null ? void 0 : data.stream) === "value";
727
965
  const flowing = useEdgeFlowing(id);
728
- const role = useSourceRole(source);
729
- const className = [
966
+ const pulse = useEdgePulse(id);
967
+ const className2 = [
730
968
  "hl-edge",
731
969
  isError ? "is-error" : "",
732
970
  isValue ? "is-value" : "is-live",
733
- flowing ? "is-flowing" : ""
971
+ flowing ? "is-flowing" : "",
972
+ selected === true ? "is-selected" : ""
734
973
  ].filter(Boolean).join(" ");
735
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
736
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("g", { className, children: [
737
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react5.BaseEdge, { id, path, className: "hl-edge-base" }),
738
- /* @__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)
739
979
  ] }),
740
- 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)(
741
989
  "div",
742
990
  {
743
991
  className: "hl-edge-label hl-mono",
@@ -759,49 +1007,257 @@ function createHlangFlowTypes() {
759
1007
  }
760
1008
 
761
1009
  // src/shell/EditorShell.tsx
762
- 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");
763
1174
  function EditorShell({
764
1175
  toolbar,
765
1176
  left,
766
1177
  right,
767
1178
  canvas,
768
1179
  footer,
1180
+ footerResize,
769
1181
  leftCollapsed = false,
770
1182
  rightCollapsed = false
771
1183
  }) {
772
- const className = [
1184
+ const shellRef = (0, import_react7.useRef)(null);
1185
+ const toolbarRef = (0, import_react7.useRef)(null);
1186
+ const className2 = [
773
1187
  "hl-shell",
774
1188
  leftCollapsed ? "hl-left-collapsed" : "",
775
1189
  rightCollapsed ? "hl-right-collapsed" : ""
776
1190
  ].filter(Boolean).join(" ");
777
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className, children: [
778
- toolbar !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "hl-region-toolbar", children: toolbar }),
779
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "hl-region-body", children: [
780
- left !== void 0 && !leftCollapsed && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("aside", { className: "hl-region-left", children: left }),
781
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("main", { className: "hl-region-canvas", children: canvas }),
782
- 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 })
783
1197
  ] }),
784
- 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 }))
785
1199
  ] });
786
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
+ }
787
1243
 
788
1244
  // src/toolbar/Toolbar.tsx
789
1245
  var import_editor_react = require("@hlang-org/editor-react");
790
- var import_jsx_runtime14 = require("react/jsx-runtime");
1246
+ var import_jsx_runtime16 = require("react/jsx-runtime");
791
1247
  function Toolbar({ title, center, actions }) {
792
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("header", { className: "hl-toolbar", children: [
793
- /* @__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: [
794
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "hl-toolbar-glyph", "aria-hidden": "true" }),
795
- /* @__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" })
796
1252
  ] }) }),
797
- center !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "hl-toolbar-center", children: center }),
798
- /* @__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 })
799
1255
  ] });
800
1256
  }
801
1257
  function UndoRedo({ core }) {
802
1258
  const history = (0, import_editor_react.useHistory)(core);
803
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
804
- /* @__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)(
805
1261
  "button",
806
1262
  {
807
1263
  type: "button",
@@ -812,7 +1268,7 @@ function UndoRedo({ core }) {
812
1268
  children: "Undo"
813
1269
  }
814
1270
  ),
815
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1271
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
816
1272
  "button",
817
1273
  {
818
1274
  type: "button",
@@ -831,7 +1287,7 @@ function RunButton({
831
1287
  runLabel = "Run",
832
1288
  stopLabel = "Stop"
833
1289
  }) {
834
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1290
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
835
1291
  "button",
836
1292
  {
837
1293
  type: "button",
@@ -839,7 +1295,7 @@ function RunButton({
839
1295
  onClick: onRun,
840
1296
  "aria-pressed": running,
841
1297
  children: [
842
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1298
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
843
1299
  "span",
844
1300
  {
845
1301
  className: `hl-toolbar-run-icon${running ? " is-stop" : ""}`,
@@ -852,22 +1308,22 @@ function RunButton({
852
1308
  );
853
1309
  }
854
1310
  function ToolbarStat({ label, value }) {
855
- 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 });
856
1312
  }
857
1313
  function ToolbarGhostButton({
858
1314
  children,
859
1315
  onClick,
860
1316
  title
861
1317
  }) {
862
- 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 });
863
1319
  }
864
1320
 
865
1321
  // src/toolbar/ToolCluster.tsx
866
- var import_jsx_runtime15 = require("react/jsx-runtime");
1322
+ var import_jsx_runtime17 = require("react/jsx-runtime");
867
1323
  function ToolCluster({ items }) {
868
- 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: [
869
- item.separatorBefore === true && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "hl-toolcluster-sep", "aria-hidden": true }),
870
- /* @__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)(
871
1327
  "button",
872
1328
  {
873
1329
  type: "button",
@@ -884,31 +1340,31 @@ function ToolCluster({ items }) {
884
1340
  }
885
1341
 
886
1342
  // src/canvas/CanvasChrome.tsx
887
- var import_react6 = require("@xyflow/react");
888
- var import_jsx_runtime16 = require("react/jsx-runtime");
1343
+ var import_react8 = require("@xyflow/react");
1344
+ var import_jsx_runtime18 = require("react/jsx-runtime");
889
1345
  function CanvasChrome({ background = true }) {
890
- 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" }) });
891
1347
  }
892
1348
 
893
1349
  // src/canvas/CanvasDock.tsx
894
- var import_jsx_runtime17 = require("react/jsx-runtime");
1350
+ var import_jsx_runtime19 = require("react/jsx-runtime");
895
1351
  function CursorIcon() {
896
- 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" }) });
897
1353
  }
898
1354
  function HandIcon() {
899
- 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" }) });
900
1356
  }
901
1357
  function PlusIcon() {
902
- 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" }) });
903
1359
  }
904
1360
  function FitIcon() {
905
- 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" }) });
906
1362
  }
907
1363
  function UndoIcon() {
908
- 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" }) });
909
1365
  }
910
1366
  function RedoIcon() {
911
- 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" }) });
912
1368
  }
913
1369
  function CanvasDock({
914
1370
  zoom,
@@ -924,8 +1380,8 @@ function CanvasDock({
924
1380
  canRedo
925
1381
  }) {
926
1382
  const pct = `${String(Math.round(zoom * 100))}%`;
927
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "hl-dock", role: "toolbar", "aria-label": "Canvas tools", children: [
928
- /* @__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)(
929
1385
  "button",
930
1386
  {
931
1387
  className: "hl-dock-btn",
@@ -933,10 +1389,10 @@ function CanvasDock({
933
1389
  disabled: !canUndo,
934
1390
  title: "Undo (\u2318Z)",
935
1391
  "aria-label": "Undo",
936
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(UndoIcon, {})
1392
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(UndoIcon, {})
937
1393
  }
938
1394
  ),
939
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1395
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
940
1396
  "button",
941
1397
  {
942
1398
  className: "hl-dock-btn",
@@ -944,48 +1400,48 @@ function CanvasDock({
944
1400
  disabled: !canRedo,
945
1401
  title: "Redo (\u2318\u21E7Z)",
946
1402
  "aria-label": "Redo",
947
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(RedoIcon, {})
1403
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(RedoIcon, {})
948
1404
  }
949
1405
  ),
950
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "hl-dock-sep", "aria-hidden": true }),
951
- /* @__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)(
952
1408
  "button",
953
1409
  {
954
1410
  className: "hl-dock-btn" + (mode === "select" ? " is-active" : ""),
955
1411
  onClick: () => onModeChange("select"),
956
1412
  title: "Select (V)",
957
1413
  "aria-pressed": mode === "select",
958
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CursorIcon, {})
1414
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CursorIcon, {})
959
1415
  }
960
1416
  ),
961
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1417
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
962
1418
  "button",
963
1419
  {
964
1420
  className: "hl-dock-btn" + (mode === "pan" ? " is-active" : ""),
965
1421
  onClick: () => onModeChange("pan"),
966
1422
  title: "Pan (Space)",
967
1423
  "aria-pressed": mode === "pan",
968
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(HandIcon, {})
1424
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(HandIcon, {})
969
1425
  }
970
1426
  ),
971
- /* @__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, {}) }),
972
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "hl-dock-sep", "aria-hidden": true }),
973
- /* @__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" }) }),
974
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { className: "hl-dock-zoom hl-mono", onClick: onFitView, title: "Fit view", children: pct }),
975
- /* @__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: "+" }) }),
976
- /* @__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, {}) })
977
1433
  ] });
978
1434
  }
979
1435
 
980
1436
  // src/canvas/EmptyState.tsx
981
- var import_jsx_runtime18 = require("react/jsx-runtime");
1437
+ var import_jsx_runtime20 = require("react/jsx-runtime");
982
1438
  function EmptyState({
983
1439
  title = "An empty canvas.",
984
1440
  hint = "add a source \xB7 wire it to a sink \xB7 run"
985
1441
  }) {
986
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "hl-empty", "aria-hidden": "true", children: [
987
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "hl-empty-title", children: title }),
988
- /* @__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 })
989
1445
  ] });
990
1446
  }
991
1447
 
@@ -1021,11 +1477,11 @@ function deriveParamSchema(descriptor, node) {
1021
1477
  }
1022
1478
 
1023
1479
  // src/hooks/useNodeParams.ts
1024
- var import_react7 = require("react");
1480
+ var import_react9 = require("react");
1025
1481
  var import_editor_core = require("@hlang-org/editor-core");
1026
1482
  function useNodeParams(core, nodeId) {
1027
- const versionRef = (0, import_react7.useRef)(0);
1028
- const subscribe = (0, import_react7.useCallback)(
1483
+ const versionRef = (0, import_react9.useRef)(0);
1484
+ const subscribe = (0, import_react9.useCallback)(
1029
1485
  (onStoreChange) => {
1030
1486
  const disposable = core.events.on("nodeChanged", (event) => {
1031
1487
  if (nodeId !== void 0 && event.node.id === nodeId) {
@@ -1039,9 +1495,9 @@ function useNodeParams(core, nodeId) {
1039
1495
  },
1040
1496
  [core, nodeId]
1041
1497
  );
1042
- const getSnapshot = (0, import_react7.useCallback)(() => versionRef.current, []);
1043
- const version = (0, import_react7.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1044
- 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)(
1045
1501
  (next) => {
1046
1502
  if (nodeId === void 0) {
1047
1503
  return;
@@ -1050,7 +1506,7 @@ function useNodeParams(core, nodeId) {
1050
1506
  },
1051
1507
  [core, nodeId]
1052
1508
  );
1053
- const setParam = (0, import_react7.useCallback)(
1509
+ const setParam = (0, import_react9.useCallback)(
1054
1510
  (key, value) => {
1055
1511
  if (nodeId === void 0) {
1056
1512
  return;
@@ -1063,7 +1519,7 @@ function useNodeParams(core, nodeId) {
1063
1519
  },
1064
1520
  [core, nodeId, setParams]
1065
1521
  );
1066
- return (0, import_react7.useMemo)(
1522
+ return (0, import_react9.useMemo)(
1067
1523
  () => ({
1068
1524
  node: nodeId === void 0 ? void 0 : core.model.getNode(nodeId),
1069
1525
  setParam,
@@ -1075,19 +1531,58 @@ function useNodeParams(core, nodeId) {
1075
1531
  );
1076
1532
  }
1077
1533
 
1534
+ // src/hooks/useEdgeBounds.ts
1535
+ var import_react10 = require("react");
1536
+ var import_editor_core2 = require("@hlang-org/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
+
1078
1573
  // src/panels/ParamForm.tsx
1079
- var import_react8 = require("react");
1080
- var import_jsx_runtime19 = require("react/jsx-runtime");
1574
+ var import_react11 = require("react");
1575
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1081
1576
  function ParamForm({ fields, onCommit }) {
1082
- 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)) });
1083
1578
  }
1084
1579
  function FieldRow({
1085
1580
  field,
1086
1581
  onCommit
1087
1582
  }) {
1088
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { className: "hl-field", children: [
1089
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "hl-field-label", children: field.label }),
1090
- /* @__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 })
1091
1586
  ] });
1092
1587
  }
1093
1588
  function FieldControl({
@@ -1096,7 +1591,7 @@ function FieldControl({
1096
1591
  }) {
1097
1592
  switch (field.type) {
1098
1593
  case "toggle":
1099
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1594
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1100
1595
  "button",
1101
1596
  {
1102
1597
  type: "button",
@@ -1105,17 +1600,17 @@ function FieldControl({
1105
1600
  className: `hl-toggle${field.value ? " is-on" : ""}`,
1106
1601
  disabled: field.disabled,
1107
1602
  onClick: () => onCommit(field.key, !field.value),
1108
- 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" })
1109
1604
  }
1110
1605
  );
1111
1606
  case "number":
1112
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(NumberControl, { field, onCommit });
1607
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(NumberControl, { field, onCommit });
1113
1608
  case "textarea":
1114
- 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 });
1115
1610
  case "text":
1116
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TextControl, { field, onCommit });
1611
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TextControl, { field, onCommit });
1117
1612
  case "custom":
1118
- 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) });
1119
1614
  }
1120
1615
  }
1121
1616
  function TextControl({
@@ -1123,7 +1618,7 @@ function TextControl({
1123
1618
  onCommit,
1124
1619
  multiline = false
1125
1620
  }) {
1126
- const [draft, setDraft] = (0, import_react8.useState)(field.value);
1621
+ const [draft, setDraft] = (0, import_react11.useState)(field.value);
1127
1622
  const commit = () => {
1128
1623
  if (draft !== field.value) {
1129
1624
  onCommit(field.key, draft);
@@ -1137,7 +1632,7 @@ function TextControl({
1137
1632
  onBlur: commit
1138
1633
  };
1139
1634
  if (multiline) {
1140
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1635
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1141
1636
  "textarea",
1142
1637
  {
1143
1638
  ...shared,
@@ -1146,7 +1641,7 @@ function TextControl({
1146
1641
  }
1147
1642
  );
1148
1643
  }
1149
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1644
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1150
1645
  "input",
1151
1646
  {
1152
1647
  ...shared,
@@ -1164,14 +1659,14 @@ function NumberControl({
1164
1659
  field,
1165
1660
  onCommit
1166
1661
  }) {
1167
- const [draft, setDraft] = (0, import_react8.useState)(String(field.value));
1662
+ const [draft, setDraft] = (0, import_react11.useState)(String(field.value));
1168
1663
  const commit = () => {
1169
1664
  const parsed = Number(draft);
1170
1665
  if (!Number.isNaN(parsed) && parsed !== field.value) {
1171
1666
  onCommit(field.key, parsed);
1172
1667
  }
1173
1668
  };
1174
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1669
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1175
1670
  "input",
1176
1671
  {
1177
1672
  className: "hl-input hl-mono",
@@ -1202,40 +1697,267 @@ function describeCustom(value) {
1202
1697
  return String(value);
1203
1698
  }
1204
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
+
1205
1923
  // src/panels/InspectorPanel.tsx
1206
- var import_jsx_runtime20 = require("react/jsx-runtime");
1924
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1207
1925
  function InspectorPanel({ core, emptyState }) {
1208
1926
  const selection = (0, import_editor_react2.useSelection)(core);
1209
1927
  const selectedNodes2 = selection.selectedNodes;
1210
1928
  const singleId = selectedNodes2.length === 1 ? selectedNodes2[0] : void 0;
1211
1929
  const params = useNodeParams(core, singleId);
1212
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
+ }
1213
1935
  if (node === void 0) {
1214
- 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: [
1215
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-inspector-blank-badge", "aria-hidden": true }),
1216
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "hl-inspector-blank-title", children: selectedNodes2.length > 1 ? `${String(selectedNodes2.length)} nodes selected` : "Nothing selected" }),
1217
- /* @__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." })
1218
1940
  ] }) });
1219
1941
  }
1220
1942
  const descriptor = core.registry.get(node.code);
1221
1943
  const fields = descriptor === void 0 ? [] : deriveParamSchema(descriptor, node);
1222
1944
  const imageUrl = typeof node.params.imageUrl === "string" && node.params.imageUrl.length > 0 ? node.params.imageUrl : void 0;
1223
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "hl-inspector", children: [
1224
- /* @__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 }) }),
1225
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("header", { className: "hl-inspector-head", children: [
1226
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: `hl-inspector-kind hl-${node.kind.toLowerCase()}`, children: node.kind }),
1227
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "hl-inspector-titles", children: [
1228
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-inspector-name", children: node.name }),
1229
- /* @__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: [
1230
1952
  node.code,
1231
1953
  " \xB7 ",
1232
1954
  KIND_ROLE[node.kind]
1233
1955
  ] })
1234
1956
  ] })
1235
1957
  ] }),
1236
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("section", { className: "hl-inspector-section", children: [
1237
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "hl-inspector-section-title", children: "Parameters" }),
1238
- 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)(
1239
1961
  ParamForm,
1240
1962
  {
1241
1963
  fields,
@@ -1243,16 +1965,17 @@ function InspectorPanel({ core, emptyState }) {
1243
1965
  }
1244
1966
  )
1245
1967
  ] }),
1246
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("section", { className: "hl-inspector-section", children: [
1247
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "hl-inspector-section-title", children: "Ports" }),
1248
- /* @__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)(
1249
1972
  "li",
1250
1973
  {
1251
1974
  className: `hl-inspector-port${port.isError ? " is-error" : ""}`,
1252
1975
  children: [
1253
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: `hl-port-dot hl-dir-${port.direction.toLowerCase()}` }),
1254
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-mono", children: port.name }),
1255
- /* @__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 })
1256
1979
  ]
1257
1980
  },
1258
1981
  port.id
@@ -1262,14 +1985,14 @@ function InspectorPanel({ core, emptyState }) {
1262
1985
  }
1263
1986
 
1264
1987
  // src/panels/OutlineTree.tsx
1265
- var import_react9 = require("react");
1266
- var import_editor_core2 = require("@hlang-org/editor-core");
1988
+ var import_react13 = require("react");
1989
+ var import_editor_core3 = require("@hlang-org/editor-core");
1267
1990
  var import_editor_react3 = require("@hlang-org/editor-react");
1268
- var import_jsx_runtime21 = require("react/jsx-runtime");
1991
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1269
1992
  function useTopologyComponents(core) {
1270
- const [tick, setTick] = (0, import_react9.useState)(0);
1271
- const bump = (0, import_react9.useCallback)(() => setTick((t) => t + 1), []);
1272
- (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)(() => {
1273
1996
  const kinds = ["nodeAdded", "nodeRemoved", "nodeChanged", "edgeAdded", "edgeRemoved"];
1274
1997
  const disposers = kinds.map((k) => core.events.on(k, bump));
1275
1998
  bump();
@@ -1279,9 +2002,9 @@ function useTopologyComponents(core) {
1279
2002
  }
1280
2003
  };
1281
2004
  }, [core, bump]);
1282
- return (0, import_react9.useMemo)(() => {
2005
+ return (0, import_react13.useMemo)(() => {
1283
2006
  void tick;
1284
- const topo = new import_editor_core2.GraphTopology();
2007
+ const topo = new import_editor_core3.GraphTopology();
1285
2008
  topo.reset(core.model.getNodes(), core.model.getEdges());
1286
2009
  return topo.components();
1287
2010
  }, [core, tick]);
@@ -1291,15 +2014,15 @@ function OutlineTree({ core, onFocus }) {
1291
2014
  const selection = (0, import_editor_react3.useSelection)(core);
1292
2015
  const viewState = (0, import_editor_react3.useViewState)(core);
1293
2016
  const diagnostics = (0, import_editor_react3.useDiagnostics)(core);
1294
- const [collapsed, setCollapsed] = (0, import_react9.useState)(/* @__PURE__ */ new Set());
1295
- const nodeById = (0, import_react9.useMemo)(() => {
2017
+ const [collapsed, setCollapsed] = (0, import_react13.useState)(/* @__PURE__ */ new Set());
2018
+ const nodeById = (0, import_react13.useMemo)(() => {
1296
2019
  const m = /* @__PURE__ */ new Map();
1297
2020
  for (const n of core.model.getNodes()) {
1298
2021
  m.set(n.id, n);
1299
2022
  }
1300
2023
  return m;
1301
2024
  }, [core, components]);
1302
- const toggleGroup = (0, import_react9.useCallback)((id) => {
2025
+ const toggleGroup = (0, import_react13.useCallback)((id) => {
1303
2026
  setCollapsed((prev) => {
1304
2027
  const next = new Set(prev);
1305
2028
  if (next.has(id)) {
@@ -1310,7 +2033,7 @@ function OutlineTree({ core, onFocus }) {
1310
2033
  return next;
1311
2034
  });
1312
2035
  }, []);
1313
- const select = (0, import_react9.useCallback)(
2036
+ const select = (0, import_react13.useCallback)(
1314
2037
  (id) => {
1315
2038
  selection.selectNodes([id], "replace");
1316
2039
  onFocus == null ? void 0 : onFocus(id);
@@ -1318,80 +2041,80 @@ function OutlineTree({ core, onFocus }) {
1318
2041
  [selection, onFocus]
1319
2042
  );
1320
2043
  if (components.length === 0) {
1321
- 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" });
1322
2045
  }
1323
2046
  const groups = [...components].sort((a, b) => b.nodeIds.size - a.nodeIds.size);
1324
- 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) => {
1325
2048
  const members = [...group.nodeIds].map((id) => nodeById.get(id)).filter((n) => n !== void 0);
1326
2049
  const open = !collapsed.has(group.id);
1327
2050
  const groupName = `Flow ${String(gi + 1)}`;
1328
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "hl-outline-group", role: "treeitem", "aria-expanded": open, children: [
1329
- /* @__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)(
1330
2053
  "button",
1331
2054
  {
1332
2055
  className: "hl-outline-grouphead",
1333
2056
  onClick: () => toggleGroup(group.id),
1334
2057
  children: [
1335
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-caret" + (open ? " is-open" : ""), "aria-hidden": true, children: "\u25B8" }),
1336
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-groupname", children: groupName }),
1337
- /* @__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 })
1338
2061
  ]
1339
2062
  }
1340
2063
  ),
1341
- 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) => {
1342
2065
  const active = selection.isSelected(node.id);
1343
2066
  const isHidden = viewState.isHidden(node.id);
1344
2067
  const isLocked = viewState.isLocked(node.id);
1345
2068
  const { imageUrl } = node.params;
1346
2069
  const thumbnail = typeof imageUrl === "string" && imageUrl.length > 0 ? imageUrl : void 0;
1347
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2070
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1348
2071
  "div",
1349
2072
  {
1350
2073
  className: "hl-outline-row" + (active ? " is-active" : "") + (isHidden ? " is-hidden" : "") + (isLocked ? " is-locked" : ""),
1351
2074
  children: [
1352
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("button", { className: "hl-outline-main", onClick: () => select(node.id), children: [
1353
- /* @__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)(
1354
2077
  "span",
1355
2078
  {
1356
2079
  className: `hl-outline-icon hl-kind-${node.kind.toLowerCase()}`,
1357
2080
  "aria-hidden": true,
1358
- children: thumbnail !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2081
+ children: thumbnail !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1359
2082
  "img",
1360
2083
  {
1361
2084
  className: "hl-outline-thumb",
1362
2085
  src: thumbnail,
1363
2086
  alt: ""
1364
2087
  }
1365
- ) : /* @__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 })
1366
2089
  }
1367
2090
  ),
1368
- /* @__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 }),
1369
2092
  (() => {
1370
2093
  const diag = diagnostics.worstOf(node.id);
1371
- 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 });
1372
2095
  })(),
1373
- 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 }) }),
1374
- 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 }) })
1375
2098
  ] }),
1376
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "hl-outline-controls", children: [
1377
- /* @__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)(
1378
2101
  "button",
1379
2102
  {
1380
2103
  className: "hl-outline-ctl" + (isHidden ? " is-active-ctl" : ""),
1381
2104
  onClick: () => viewState.toggleHidden(node.id),
1382
2105
  "aria-label": isHidden ? "Show layer" : "Hide layer",
1383
2106
  title: isHidden ? "Show" : "Hide",
1384
- 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, {})
1385
2108
  }
1386
2109
  ),
1387
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2110
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1388
2111
  "button",
1389
2112
  {
1390
2113
  className: "hl-outline-ctl" + (isLocked ? " is-active-ctl" : ""),
1391
2114
  onClick: () => viewState.toggleLocked(node.id),
1392
2115
  "aria-label": isLocked ? "Unlock layer" : "Lock layer",
1393
2116
  title: isLocked ? "Unlock" : "Lock",
1394
- 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, {})
1395
2118
  }
1396
2119
  )
1397
2120
  ] })
@@ -1404,21 +2127,21 @@ function OutlineTree({ core, onFocus }) {
1404
2127
  }
1405
2128
 
1406
2129
  // src/panels/PreviewPanel.tsx
1407
- var import_jsx_runtime22 = require("react/jsx-runtime");
2130
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1408
2131
  function PreviewPanel({ imageUrl, title = "Preview", status }) {
1409
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("section", { className: "hl-preview", "aria-label": "Preview", children: [
1410
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "hl-preview-head", children: [
1411
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "hl-preview-title", children: title }),
1412
- 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 })
1413
2136
  ] }),
1414
- /* @__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" }) })
1415
2138
  ] });
1416
2139
  }
1417
2140
 
1418
2141
  // src/panels/RailTabs.tsx
1419
- var import_jsx_runtime23 = require("react/jsx-runtime");
2142
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1420
2143
  function RailTabs({ tabs, active, onChange }) {
1421
- 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)(
1422
2145
  "button",
1423
2146
  {
1424
2147
  role: "tab",
@@ -1432,20 +2155,20 @@ function RailTabs({ tabs, active, onChange }) {
1432
2155
  }
1433
2156
 
1434
2157
  // src/panels/AssetGrid.tsx
1435
- var import_jsx_runtime24 = require("react/jsx-runtime");
2158
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1436
2159
  function AssetGrid({ items, onSelect, emptyHint = "No assets yet" }) {
1437
2160
  if (items.length === 0) {
1438
- 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 });
1439
2162
  }
1440
- 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) => {
1441
2164
  var _a;
1442
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2165
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1443
2166
  "button",
1444
2167
  {
1445
2168
  className: "hl-assetgrid-cell",
1446
2169
  onClick: () => onSelect == null ? void 0 : onSelect(item.id),
1447
2170
  title: item.label,
1448
- 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" })
1449
2172
  },
1450
2173
  item.id
1451
2174
  );
@@ -1453,10 +2176,10 @@ function AssetGrid({ items, onSelect, emptyHint = "No assets yet" }) {
1453
2176
  }
1454
2177
 
1455
2178
  // src/panels/PositionPanel.tsx
1456
- var import_react10 = require("react");
1457
- var import_editor_core3 = require("@hlang-org/editor-core");
2179
+ var import_react14 = require("react");
2180
+ var import_editor_core4 = require("@hlang-org/editor-core");
1458
2181
  var import_editor_react4 = require("@hlang-org/editor-react");
1459
- var import_jsx_runtime25 = require("react/jsx-runtime");
2182
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1460
2183
  function selectedNodes(core) {
1461
2184
  const ids = core.selection.getSelectedNodes();
1462
2185
  const out = [];
@@ -1471,8 +2194,8 @@ function selectedNodes(core) {
1471
2194
  function PositionPanel({ core, livePosition }) {
1472
2195
  var _a;
1473
2196
  (0, import_editor_react4.useSelection)(core);
1474
- const [, bump] = (0, import_react10.useReducer)((n) => n + 1, 0);
1475
- (0, import_react10.useEffect)(() => {
2197
+ const [, bump] = (0, import_react14.useReducer)((n) => n + 1, 0);
2198
+ (0, import_react14.useEffect)(() => {
1476
2199
  const kinds = ["nodeChanged", "nodeAdded", "nodeRemoved"];
1477
2200
  const disposers = kinds.map((k) => core.events.on(k, bump));
1478
2201
  return () => {
@@ -1484,7 +2207,7 @@ function PositionPanel({ core, livePosition }) {
1484
2207
  const nodes = selectedNodes(core);
1485
2208
  const single = nodes.length === 1 ? nodes[0] : void 0;
1486
2209
  const shownPos = single !== void 0 ? (_a = livePosition == null ? void 0 : livePosition(single.id)) != null ? _a : single.position : void 0;
1487
- const moveAll = (0, import_react10.useCallback)(
2210
+ const moveAll = (0, import_react14.useCallback)(
1488
2211
  (fn) => {
1489
2212
  const targets = selectedNodes(core);
1490
2213
  if (targets.length === 0) {
@@ -1492,13 +2215,13 @@ function PositionPanel({ core, livePosition }) {
1492
2215
  }
1493
2216
  core.history.transact("Align", () => {
1494
2217
  for (const n of targets) {
1495
- 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)));
1496
2219
  }
1497
2220
  });
1498
2221
  },
1499
2222
  [core]
1500
2223
  );
1501
- const align = (0, import_react10.useCallback)(
2224
+ const align = (0, import_react14.useCallback)(
1502
2225
  (kind) => {
1503
2226
  const targets = selectedNodes(core);
1504
2227
  if (targets.length === 0) {
@@ -1531,13 +2254,13 @@ function PositionPanel({ core, livePosition }) {
1531
2254
  },
1532
2255
  [core, moveAll]
1533
2256
  );
1534
- const setCoord = (0, import_react10.useCallback)(
2257
+ const setCoord = (0, import_react14.useCallback)(
1535
2258
  (axis, value) => {
1536
2259
  if (single === void 0 || Number.isNaN(value)) {
1537
2260
  return;
1538
2261
  }
1539
2262
  const next = axis === "x" ? { x: value, y: single.position.y } : { x: single.position.x, y: value };
1540
- core.commands.execute(new import_editor_core3.MoveNodeCommand(single.id, next));
2263
+ core.commands.execute(new import_editor_core4.MoveNodeCommand(single.id, next));
1541
2264
  },
1542
2265
  [core, single]
1543
2266
  );
@@ -1550,9 +2273,9 @@ function PositionPanel({ core, livePosition }) {
1550
2273
  { kind: "vcenter", label: "Align middle", d: "M4 12h16M9 8v8M15 9v6" },
1551
2274
  { kind: "bottom", label: "Align bottom", d: "M4 19h16M8 7v8M16 12v3" }
1552
2275
  ];
1553
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("section", { className: "hl-position", children: [
1554
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("h3", { className: "hl-position-title hl-mono", children: "Position" }),
1555
- /* @__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)(
1556
2279
  "button",
1557
2280
  {
1558
2281
  className: "hl-position-alignbtn",
@@ -1560,13 +2283,13 @@ function PositionPanel({ core, livePosition }) {
1560
2283
  disabled,
1561
2284
  title: b.label,
1562
2285
  "aria-label": b.label,
1563
- 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 }) })
1564
2287
  },
1565
2288
  b.kind
1566
2289
  )) }),
1567
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-position-coords", children: [
1568
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("label", { className: "hl-position-field", children: [
1569
- /* @__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)(
1570
2293
  "input",
1571
2294
  {
1572
2295
  type: "number",
@@ -1577,10 +2300,10 @@ function PositionPanel({ core, livePosition }) {
1577
2300
  "aria-label": "X position"
1578
2301
  }
1579
2302
  ),
1580
- /* @__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" })
1581
2304
  ] }),
1582
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("label", { className: "hl-position-field", children: [
1583
- /* @__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)(
1584
2307
  "input",
1585
2308
  {
1586
2309
  type: "number",
@@ -1591,24 +2314,24 @@ function PositionPanel({ core, livePosition }) {
1591
2314
  "aria-label": "Y position"
1592
2315
  }
1593
2316
  ),
1594
- /* @__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" })
1595
2318
  ] })
1596
2319
  ] })
1597
2320
  ] });
1598
2321
  }
1599
2322
 
1600
2323
  // src/command-palette/NodeSearch.tsx
1601
- var import_react11 = require("react");
1602
- var import_jsx_runtime26 = require("react/jsx-runtime");
2324
+ var import_react15 = require("react");
2325
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1603
2326
  var PANEL_WIDTH = 320;
1604
2327
  var PANEL_MAX_HEIGHT = 360;
1605
2328
  function NodeSearch({ controller }) {
1606
2329
  const { state } = controller;
1607
- const [query, setQuery] = (0, import_react11.useState)("");
1608
- const [active, setActive] = (0, import_react11.useState)(0);
1609
- 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);
1610
2333
  const isOpen = state !== void 0;
1611
- (0, import_react11.useEffect)(() => {
2334
+ (0, import_react15.useEffect)(() => {
1612
2335
  var _a;
1613
2336
  if (isOpen) {
1614
2337
  setQuery("");
@@ -1616,12 +2339,12 @@ function NodeSearch({ controller }) {
1616
2339
  (_a = inputRef.current) == null ? void 0 : _a.focus();
1617
2340
  }
1618
2341
  }, [isOpen]);
1619
- const results = (0, import_react11.useMemo)(
2342
+ const results = (0, import_react15.useMemo)(
1620
2343
  () => state !== void 0 ? controller.results(query) : [],
1621
2344
  [controller, state, query]
1622
2345
  );
1623
2346
  const clampedActive = results.length === 0 ? 0 : Math.min(active, results.length - 1);
1624
- const choose = (0, import_react11.useCallback)(
2347
+ const choose = (0, import_react15.useCallback)(
1625
2348
  (view) => {
1626
2349
  if (view !== void 0) {
1627
2350
  controller.commit(view);
@@ -1629,7 +2352,7 @@ function NodeSearch({ controller }) {
1629
2352
  },
1630
2353
  [controller]
1631
2354
  );
1632
- const onKeyDown = (0, import_react11.useCallback)(
2355
+ const onKeyDown = (0, import_react15.useCallback)(
1633
2356
  (event) => {
1634
2357
  switch (event.key) {
1635
2358
  case "ArrowDown":
@@ -1659,8 +2382,8 @@ function NodeSearch({ controller }) {
1659
2382
  }
1660
2383
  const style = clampToViewport(state.screen.x, state.screen.y);
1661
2384
  const connectMode = state.pending !== void 0;
1662
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
1663
- /* @__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)(
1664
2387
  "div",
1665
2388
  {
1666
2389
  className: "hl-node-search-scrim",
@@ -1669,10 +2392,10 @@ function NodeSearch({ controller }) {
1669
2392
  }
1670
2393
  }
1671
2394
  ),
1672
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "hl-node-search", style, role: "dialog", "aria-label": "Create node", children: [
1673
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "hl-node-search-field", children: [
1674
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-node-search-glyph", "aria-hidden": true, children: connectMode ? "\u2933" : "\uFF0B" }),
1675
- /* @__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)(
1676
2399
  "input",
1677
2400
  {
1678
2401
  ref: inputRef,
@@ -1690,12 +2413,12 @@ function NodeSearch({ controller }) {
1690
2413
  }
1691
2414
  )
1692
2415
  ] }),
1693
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("ul", { className: "hl-node-search-list", role: "listbox", children: [
1694
- 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" }),
1695
2418
  results.map((view, index) => {
1696
2419
  const descriptor = view.result.value;
1697
2420
  const kind = descriptor.kind;
1698
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2421
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1699
2422
  "li",
1700
2423
  {
1701
2424
  role: "option",
@@ -1707,15 +2430,15 @@ function NodeSearch({ controller }) {
1707
2430
  choose(view);
1708
2431
  },
1709
2432
  children: [
1710
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2433
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1711
2434
  "span",
1712
2435
  {
1713
2436
  className: `hl-node-search-kind hl-mono hl-kind-${kind.toLowerCase()}`,
1714
2437
  children: kind
1715
2438
  }
1716
2439
  ),
1717
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-node-search-label", children: highlight(descriptor.displayName, view.result.match) }),
1718
- 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 })
1719
2442
  ]
1720
2443
  },
1721
2444
  descriptor.code
@@ -1729,7 +2452,7 @@ function highlight(text, match) {
1729
2452
  const hit = new Set(match.positions);
1730
2453
  const chars = Array.from(text);
1731
2454
  return chars.map(
1732
- (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)
1733
2456
  );
1734
2457
  }
1735
2458
  function clampToViewport(x, y) {
@@ -1742,8 +2465,8 @@ function clampToViewport(x, y) {
1742
2465
  }
1743
2466
 
1744
2467
  // src/command-palette/useNodeSearch.ts
1745
- var import_react12 = require("react");
1746
- var import_editor_core4 = require("@hlang-org/editor-core");
2468
+ var import_react16 = require("react");
2469
+ var import_editor_core5 = require("@hlang-org/editor-core");
1747
2470
 
1748
2471
  // src/command-palette/fuzzy.ts
1749
2472
  function fuzzyScore(query, target) {
@@ -1833,21 +2556,21 @@ function firstCompletingPort(ports, pending, validator) {
1833
2556
 
1834
2557
  // src/command-palette/useNodeSearch.ts
1835
2558
  function useNodeSearch(core) {
1836
- const [state, setState] = (0, import_react12.useState)(void 0);
1837
- 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) => {
1838
2561
  setState({ screen, flow });
1839
2562
  }, []);
1840
- const openFromConnection = (0, import_react12.useCallback)(
2563
+ const openFromConnection = (0, import_react16.useCallback)(
1841
2564
  (screen, flow, pending) => {
1842
2565
  setState({ screen, flow, pending });
1843
2566
  },
1844
2567
  []
1845
2568
  );
1846
- const close = (0, import_react12.useCallback)(() => {
2569
+ const close = (0, import_react16.useCallback)(() => {
1847
2570
  setState(void 0);
1848
2571
  }, []);
1849
2572
  const descriptors = core.registry.list();
1850
- const results = (0, import_react12.useCallback)(
2573
+ const results = (0, import_react16.useCallback)(
1851
2574
  (query) => {
1852
2575
  if ((state == null ? void 0 : state.pending) !== void 0) {
1853
2576
  return connectableCandidates(
@@ -1861,7 +2584,7 @@ function useNodeSearch(core) {
1861
2584
  },
1862
2585
  [state, descriptors, core]
1863
2586
  );
1864
- const commit = (0, import_react12.useCallback)(
2587
+ const commit = (0, import_react16.useCallback)(
1865
2588
  (view) => {
1866
2589
  if (state === void 0) {
1867
2590
  return;
@@ -1872,40 +2595,40 @@ function useNodeSearch(core) {
1872
2595
  const pending = state.pending;
1873
2596
  const connectPortId = view.connectPortId;
1874
2597
  core.history.transact("Create & Connect Node", () => {
1875
- core.commands.execute(new import_editor_core4.AddNodeCommand(node));
2598
+ core.commands.execute(new import_editor_core5.AddNodeCommand(node));
1876
2599
  const edgeId = core.idGenerator.nextEdgeId();
1877
2600
  const newEndpoint = { nodeId: node.id, portId: connectPortId };
1878
2601
  const [source, target] = pending.direction === "from-source" ? [pending.fromEndpoint, newEndpoint] : [newEndpoint, pending.fromEndpoint];
1879
- core.commands.execute(new import_editor_core4.ConnectCommand(edgeId, source, target));
2602
+ core.commands.execute(new import_editor_core5.ConnectCommand(edgeId, source, target));
1880
2603
  });
1881
2604
  } else {
1882
- core.commands.execute(new import_editor_core4.AddNodeCommand(node));
2605
+ core.commands.execute(new import_editor_core5.AddNodeCommand(node));
1883
2606
  }
1884
2607
  setState(void 0);
1885
2608
  },
1886
2609
  [state, core]
1887
2610
  );
1888
- return (0, import_react12.useMemo)(
2611
+ return (0, import_react16.useMemo)(
1889
2612
  () => ({ state, openBlank, openFromConnection, close, results, commit }),
1890
2613
  [state, openBlank, openFromConnection, close, results, commit]
1891
2614
  );
1892
2615
  }
1893
2616
 
1894
2617
  // src/command-palette/useNodeSearchTriggers.ts
1895
- var import_react13 = require("react");
1896
- var import_react14 = require("@xyflow/react");
2618
+ var import_react17 = require("react");
2619
+ var import_react18 = require("@xyflow/react");
1897
2620
  var DEFAULT_DOUBLE_CLICK_MS = 350;
1898
2621
  function useNodeSearchTriggers(core, search, options = {}) {
1899
2622
  var _a, _b;
1900
- const flow = (0, import_react14.useReactFlow)();
2623
+ const flow = (0, import_react18.useReactFlow)();
1901
2624
  const doubleClickMs = (_a = options.doubleClickMs) != null ? _a : DEFAULT_DOUBLE_CLICK_MS;
1902
2625
  const now = (_b = options.now) != null ? _b : defaultNow;
1903
- const startRef = (0, import_react13.useRef)(null);
1904
- const lastPaneClickRef = (0, import_react13.useRef)(Number.NEGATIVE_INFINITY);
1905
- 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) => {
1906
2629
  startRef.current = params;
1907
2630
  }, []);
1908
- const onConnectEnd = (0, import_react13.useCallback)(
2631
+ const onConnectEnd = (0, import_react17.useCallback)(
1909
2632
  (event, connectionState) => {
1910
2633
  const start = startRef.current;
1911
2634
  startRef.current = null;
@@ -1938,7 +2661,7 @@ function useNodeSearchTriggers(core, search, options = {}) {
1938
2661
  },
1939
2662
  [core, flow, search]
1940
2663
  );
1941
- const onPaneClick = (0, import_react13.useCallback)(
2664
+ const onPaneClick = (0, import_react17.useCallback)(
1942
2665
  (event) => {
1943
2666
  const t = now();
1944
2667
  const isDouble = t - lastPaneClickRef.current <= doubleClickMs;
@@ -1970,7 +2693,7 @@ function pointerOf(event) {
1970
2693
  }
1971
2694
 
1972
2695
  // src/hooks/useEditorHotkeys.ts
1973
- var import_react15 = require("react");
2696
+ var import_react19 = require("react");
1974
2697
  var import_editor_react5 = require("@hlang-org/editor-react");
1975
2698
  function isEditableTarget(target) {
1976
2699
  if (!(target instanceof HTMLElement)) {
@@ -1989,7 +2712,7 @@ function withinScope(target, scope) {
1989
2712
  function useEditorHotkeys(core, options = {}) {
1990
2713
  const clipboard = (0, import_editor_react5.useClipboard)(core);
1991
2714
  const { scope } = options;
1992
- (0, import_react15.useEffect)(() => {
2715
+ (0, import_react19.useEffect)(() => {
1993
2716
  const onKeyDown = (event) => {
1994
2717
  if (!withinScope(event.target, scope) || isEditableTarget(event.target)) {
1995
2718
  return;
@@ -2052,33 +2775,33 @@ function useEditorHotkeys(core, options = {}) {
2052
2775
  }
2053
2776
 
2054
2777
  // src/context-menu/NodeContextMenu.tsx
2055
- var import_react16 = require("react");
2056
- var import_jsx_runtime27 = require("react/jsx-runtime");
2778
+ var import_react20 = require("react");
2779
+ var import_jsx_runtime32 = require("react/jsx-runtime");
2057
2780
  var PANEL_WIDTH2 = 208;
2058
2781
  var ITEM_HEIGHT = 34;
2059
2782
  var VIEWPORT_MARGIN = 8;
2060
2783
  var MENU_VPAD = 12;
2061
2784
  function NodeContextMenu({ controller, items }) {
2062
2785
  const { state } = controller;
2063
- const [active, setActive] = (0, import_react16.useState)(0);
2064
- const menuRef = (0, import_react16.useRef)(null);
2786
+ const [active, setActive] = (0, import_react20.useState)(0);
2787
+ const menuRef = (0, import_react20.useRef)(null);
2065
2788
  const isOpen = state !== void 0;
2066
- (0, import_react16.useEffect)(() => {
2789
+ (0, import_react20.useEffect)(() => {
2067
2790
  var _a;
2068
2791
  if (isOpen) {
2069
2792
  setActive(0);
2070
2793
  (_a = menuRef.current) == null ? void 0 : _a.focus();
2071
2794
  }
2072
2795
  }, [isOpen]);
2073
- const rows = (0, import_react16.useMemo)(
2796
+ const rows = (0, import_react20.useMemo)(
2074
2797
  () => state !== void 0 ? items(state.nodeId) : [],
2075
2798
  [items, state]
2076
2799
  );
2077
- const enabledIndices = (0, import_react16.useMemo)(
2800
+ const enabledIndices = (0, import_react20.useMemo)(
2078
2801
  () => rows.map((r, i) => r.disabled ? -1 : i).filter((i) => i >= 0),
2079
2802
  [rows]
2080
2803
  );
2081
- const move = (0, import_react16.useCallback)(
2804
+ const move = (0, import_react20.useCallback)(
2082
2805
  (delta) => {
2083
2806
  if (enabledIndices.length === 0) {
2084
2807
  return;
@@ -2092,7 +2815,7 @@ function NodeContextMenu({ controller, items }) {
2092
2815
  },
2093
2816
  [enabledIndices]
2094
2817
  );
2095
- const choose = (0, import_react16.useCallback)(
2818
+ const choose = (0, import_react20.useCallback)(
2096
2819
  (item) => {
2097
2820
  if (item === void 0 || item.disabled) {
2098
2821
  return;
@@ -2102,7 +2825,7 @@ function NodeContextMenu({ controller, items }) {
2102
2825
  },
2103
2826
  [controller]
2104
2827
  );
2105
- const onKeyDown = (0, import_react16.useCallback)(
2828
+ const onKeyDown = (0, import_react20.useCallback)(
2106
2829
  (event) => {
2107
2830
  switch (event.key) {
2108
2831
  case "ArrowDown":
@@ -2131,8 +2854,8 @@ function NodeContextMenu({ controller, items }) {
2131
2854
  return null;
2132
2855
  }
2133
2856
  const style = clampToViewport2(state.screen.x, state.screen.y, rows.length);
2134
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
2135
- /* @__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)(
2136
2859
  "div",
2137
2860
  {
2138
2861
  className: "hl-ctx-scrim",
@@ -2145,7 +2868,7 @@ function NodeContextMenu({ controller, items }) {
2145
2868
  }
2146
2869
  }
2147
2870
  ),
2148
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2871
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2149
2872
  "div",
2150
2873
  {
2151
2874
  ref: menuRef,
@@ -2155,7 +2878,7 @@ function NodeContextMenu({ controller, items }) {
2155
2878
  "aria-label": "Node actions",
2156
2879
  tabIndex: -1,
2157
2880
  onKeyDown,
2158
- children: rows.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
2881
+ children: rows.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
2159
2882
  "button",
2160
2883
  {
2161
2884
  type: "button",
@@ -2168,9 +2891,9 @@ function NodeContextMenu({ controller, items }) {
2168
2891
  choose(item);
2169
2892
  },
2170
2893
  children: [
2171
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-ctx-item-icon", "aria-hidden": true, children: item.icon }),
2172
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-ctx-item-label", children: item.label }),
2173
- 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 })
2174
2897
  ]
2175
2898
  },
2176
2899
  item.id
@@ -2190,21 +2913,21 @@ function clampToViewport2(x, y, rowCount) {
2190
2913
  }
2191
2914
 
2192
2915
  // src/context-menu/useNodeContextMenu.ts
2193
- var import_react17 = require("react");
2916
+ var import_react21 = require("react");
2194
2917
  function useNodeContextMenu() {
2195
- const [state, setState] = (0, import_react17.useState)(void 0);
2196
- 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) => {
2197
2920
  setState({ screen, nodeId });
2198
2921
  }, []);
2199
- const close = (0, import_react17.useCallback)(() => {
2922
+ const close = (0, import_react21.useCallback)(() => {
2200
2923
  setState(void 0);
2201
2924
  }, []);
2202
- return (0, import_react17.useMemo)(() => ({ state, openForNode, close }), [state, openForNode, close]);
2925
+ return (0, import_react21.useMemo)(() => ({ state, openForNode, close }), [state, openForNode, close]);
2203
2926
  }
2204
2927
 
2205
2928
  // src/overlays/Cheatsheet.tsx
2206
- var import_react18 = require("react");
2207
- var import_jsx_runtime28 = require("react/jsx-runtime");
2929
+ var import_react22 = require("react");
2930
+ var import_jsx_runtime33 = require("react/jsx-runtime");
2208
2931
  var DEFAULT_GROUPS = [
2209
2932
  {
2210
2933
  title: "Edit",
@@ -2240,8 +2963,8 @@ var DEFAULT_GROUPS = [
2240
2963
  }
2241
2964
  ];
2242
2965
  function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2243
- const dialogRef = (0, import_react18.useRef)(null);
2244
- (0, import_react18.useEffect)(() => {
2966
+ const dialogRef = (0, import_react22.useRef)(null);
2967
+ (0, import_react22.useEffect)(() => {
2245
2968
  var _a;
2246
2969
  if (!open) {
2247
2970
  return;
@@ -2258,9 +2981,9 @@ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2258
2981
  if (!open) {
2259
2982
  return null;
2260
2983
  }
2261
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
2262
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "hl-cheat-scrim", onPointerDown: onClose }),
2263
- /* @__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)(
2264
2987
  "div",
2265
2988
  {
2266
2989
  ref: dialogRef,
@@ -2270,18 +2993,18 @@ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2270
2993
  "aria-label": "Keyboard shortcuts",
2271
2994
  tabIndex: -1,
2272
2995
  children: [
2273
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "hl-cheat-head", children: [
2274
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "hl-cheat-title", children: "Shortcuts" }),
2275
- /* @__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" })
2276
2999
  ] }),
2277
- /* @__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: [
2278
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h3", { className: "hl-cheat-group-title hl-mono", children: group.title }),
2279
- /* @__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) => (
2280
3003
  // A row is a binding: one action can have several (two ways to pan),
2281
3004
  // so neither the label nor the keys alone identify it.
2282
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("li", { className: "hl-cheat-row", children: [
2283
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "hl-cheat-label", children: row.label }),
2284
- /* @__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 })
2285
3008
  ] }, `${row.keys}\0${row.label}`)
2286
3009
  )) })
2287
3010
  ] }, group.title)) })
@@ -2292,13 +3015,13 @@ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2292
3015
  }
2293
3016
 
2294
3017
  // src/overlays/Toast.tsx
2295
- var import_react19 = require("react");
2296
- var import_jsx_runtime29 = require("react/jsx-runtime");
2297
- 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 });
2298
3021
  function ToastProvider({ children, duration = 2200 }) {
2299
- const [items, setItems] = (0, import_react19.useState)([]);
2300
- const seq = (0, import_react19.useRef)(0);
2301
- 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)(
2302
3025
  (message) => {
2303
3026
  seq.current += 1;
2304
3027
  const id = seq.current;
@@ -2309,22 +3032,22 @@ function ToastProvider({ children, duration = 2200 }) {
2309
3032
  },
2310
3033
  [duration]
2311
3034
  );
2312
- const api = (0, import_react19.useMemo)(() => ({ show }), [show]);
2313
- 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: [
2314
3037
  children,
2315
- /* @__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)) })
2316
3039
  ] });
2317
3040
  }
2318
3041
  function useToast() {
2319
- return (0, import_react19.useContext)(ToastCtx);
3042
+ return (0, import_react23.useContext)(ToastCtx);
2320
3043
  }
2321
3044
 
2322
3045
  // src/hooks/useLiveSignals.ts
2323
- var import_react20 = require("react");
3046
+ var import_react24 = require("react");
2324
3047
  function useLiveSignals(core, handle, explicitStore) {
2325
3048
  const contextStore = useLiveSignalStore();
2326
3049
  const store = explicitStore != null ? explicitStore : contextStore;
2327
- (0, import_react20.useEffect)(() => {
3050
+ (0, import_react24.useEffect)(() => {
2328
3051
  if (store === null) {
2329
3052
  return;
2330
3053
  }
@@ -2333,6 +3056,8 @@ function useLiveSignals(core, handle, explicitStore) {
2333
3056
  id: edge.id,
2334
3057
  sourceNodeId: edge.source.nodeId,
2335
3058
  sourcePortId: edge.source.portId,
3059
+ targetNodeId: edge.target.nodeId,
3060
+ targetPortId: edge.target.portId,
2336
3061
  isError: edge.source.portId === "@node/error"
2337
3062
  }));
2338
3063
  store.setEdgeIndex(entries);
@@ -2345,7 +3070,8 @@ function useLiveSignals(core, handle, explicitStore) {
2345
3070
  removed.dispose();
2346
3071
  };
2347
3072
  }, [core, store]);
2348
- (0, import_react20.useEffect)(() => {
3073
+ (0, import_react24.useEffect)(() => {
3074
+ var _a, _b, _c;
2349
3075
  if (store === null) {
2350
3076
  return;
2351
3077
  }
@@ -2353,15 +3079,407 @@ function useLiveSignals(core, handle, explicitStore) {
2353
3079
  store.reset();
2354
3080
  return;
2355
3081
  }
2356
- 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));
2357
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
+ });
2358
3089
  return () => {
2359
- activitySub.dispose();
3090
+ signalSub.dispose();
2360
3091
  errorSub.dispose();
3092
+ finishedSub == null ? void 0 : finishedSub.dispose();
2361
3093
  store.reset();
2362
3094
  };
2363
3095
  }, [store, handle]);
2364
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-org/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
+ }
2365
3483
  // Annotate the CommonJS export names for ESM import in node:
2366
3484
  0 && (module.exports = {
2367
3485
  AssetGrid,
@@ -2369,30 +3487,45 @@ function useLiveSignals(core, handle, explicitStore) {
2369
3487
  CanvasChrome,
2370
3488
  CanvasDock,
2371
3489
  Cheatsheet,
3490
+ ConnectionInspector,
3491
+ DEFAULT_EDGE_CAPACITY,
3492
+ DEFAULT_MIN_CANVAS_HEIGHT,
2372
3493
  DiagnosticBadge,
2373
3494
  EditorShell,
2374
3495
  EditorUIThemeProvider,
2375
3496
  EmptyState,
3497
+ FOLLOW_SLACK_PX,
3498
+ FOOTER_KEY_STEP,
2376
3499
  FlowEdge,
3500
+ FooterResizer,
2377
3501
  HlangNode,
2378
3502
  InspectorPanel,
2379
3503
  KIND_ROLE,
2380
3504
  KIND_TOKEN,
3505
+ LivePulse,
2381
3506
  LiveSignalProvider,
2382
3507
  LiveSignalStore,
2383
3508
  NodeBodySlot,
2384
3509
  NodeChrome,
2385
3510
  NodeContextMenu,
2386
3511
  NodeInteractionProvider,
3512
+ NodeRunStats,
2387
3513
  NodeSearch,
2388
3514
  OutlineTree,
3515
+ PREVIEW_LENGTH,
2389
3516
  ParamForm,
2390
3517
  PillNodeChrome,
2391
3518
  PortRow,
2392
3519
  PositionPanel,
2393
3520
  PreviewPanel,
3521
+ RUN_LOG_CAPACITY,
2394
3522
  RailTabs,
2395
3523
  RunButton,
3524
+ RunLog,
3525
+ RunTrace,
3526
+ RunTraceProvider,
3527
+ SELF_LOOP_CLEARANCE,
3528
+ SELF_LOOP_RADIUS,
2396
3529
  SignalBar,
2397
3530
  TOKEN_NAMES,
2398
3531
  ToastProvider,
@@ -2401,26 +3534,47 @@ function useLiveSignals(core, handle, explicitStore) {
2401
3534
  ToolbarGhostButton,
2402
3535
  ToolbarStat,
2403
3536
  UndoRedo,
3537
+ boundsFields,
3538
+ capacityLabel,
3539
+ clampFooter,
2404
3540
  connectableCandidates,
2405
3541
  createHlangFlowTypes,
2406
3542
  createTheme,
3543
+ defaultLiveDeps,
2407
3544
  deriveParamSchema,
2408
3545
  descriptorCandidates,
3546
+ footerBounds,
3547
+ formatDuration,
3548
+ formatElapsed,
2409
3549
  fuzzyRank,
2410
3550
  fuzzyScore,
3551
+ involves,
2411
3552
  isEditableTarget,
3553
+ nextBounds,
3554
+ previewValue,
3555
+ resizeByKey,
2412
3556
  searchNodes,
3557
+ selfLoopPath,
3558
+ toggleCollapsed,
2413
3559
  tokenVar,
3560
+ useEdgeBounds,
2414
3561
  useEdgeFlowing,
3562
+ useEdgePulse,
2415
3563
  useEditorHotkeys,
3564
+ useFollowScroll,
3565
+ useFooterResize,
2416
3566
  useLiveSignalStore,
2417
3567
  useLiveSignals,
2418
3568
  useNodeContextMenu,
2419
3569
  useNodeInteraction,
2420
3570
  useNodeLiveState,
2421
3571
  useNodeParams,
3572
+ useNodePulse,
2422
3573
  useNodeSearch,
2423
3574
  useNodeSearchTriggers,
3575
+ useRunTrace,
3576
+ useRunTraceStore,
3577
+ useRunTraceVersion,
2424
3578
  useToast
2425
3579
  });
2426
3580
  //# sourceMappingURL=index.js.map