@hlang-tech/editor-ui 0.4.0 → 0.6.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,6 +25,7 @@ __export(index_exports, {
25
25
  CanvasChrome: () => CanvasChrome,
26
26
  CanvasDock: () => CanvasDock,
27
27
  Cheatsheet: () => Cheatsheet,
28
+ ClearIcon: () => ClearIcon,
28
29
  ConnectionInspector: () => ConnectionInspector,
29
30
  DEFAULT_EDGE_CAPACITY: () => DEFAULT_EDGE_CAPACITY,
30
31
  DEFAULT_MIN_CANVAS_HEIGHT: () => DEFAULT_MIN_CANVAS_HEIGHT,
@@ -34,7 +35,9 @@ __export(index_exports, {
34
35
  EmptyState: () => EmptyState,
35
36
  FOLLOW_SLACK_PX: () => FOLLOW_SLACK_PX,
36
37
  FOOTER_KEY_STEP: () => FOOTER_KEY_STEP,
38
+ FilterIcon: () => FilterIcon,
37
39
  FlowEdge: () => FlowEdge,
40
+ FollowIcon: () => FollowIcon,
38
41
  FooterResizer: () => FooterResizer,
39
42
  HlangNode: () => HlangNode,
40
43
  InspectorPanel: () => InspectorPanel,
@@ -51,6 +54,8 @@ __export(index_exports, {
51
54
  NodeSearch: () => NodeSearch,
52
55
  OutlineTree: () => OutlineTree,
53
56
  PREVIEW_LENGTH: () => PREVIEW_LENGTH,
57
+ PanelAction: () => PanelAction,
58
+ PanelHead: () => PanelHead,
54
59
  ParamForm: () => ParamForm,
55
60
  PillNodeChrome: () => PillNodeChrome,
56
61
  PortRow: () => PortRow,
@@ -58,6 +63,7 @@ __export(index_exports, {
58
63
  PreviewPanel: () => PreviewPanel,
59
64
  RUN_LOG_CAPACITY: () => RUN_LOG_CAPACITY,
60
65
  RailTabs: () => RailTabs,
66
+ ReadOnlyBadge: () => ReadOnlyBadge,
61
67
  RunButton: () => RunButton,
62
68
  RunLog: () => RunLog,
63
69
  RunTrace: () => RunTrace,
@@ -669,6 +675,15 @@ function ErrorIcon({ size = 13 }) {
669
675
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 16v.5" }, "d")
670
676
  ]);
671
677
  }
678
+ function FilterIcon({ size = 14 }) {
679
+ return svg(size, [/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M4 5h16l-6 7.5V19l-4-2v-4.5z" }, "f")]);
680
+ }
681
+ function FollowIcon({ size = 14 }) {
682
+ return svg(size, [/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 4v11M7 10.5l5 5 5-5" }, "a"), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M5 20h14" }, "l")]);
683
+ }
684
+ function ClearIcon({ size = 14 }) {
685
+ return svg(size, [/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "12", r: "8" }, "c"), /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M6.5 17.5l11-11" }, "s")]);
686
+ }
672
687
 
673
688
  // src/panels/DiagnosticBadge.tsx
674
689
  var import_jsx_runtime9 = require("react/jsx-runtime");
@@ -688,8 +703,7 @@ function DiagnosticBadge({ diagnostic, variant, size = 12 }) {
688
703
  var import_react3 = require("react");
689
704
  var import_jsx_runtime10 = require("react/jsx-runtime");
690
705
  var NoopInteraction = {
691
- diagnosticFor: () => void 0,
692
- rename: () => void 0
706
+ diagnosticFor: () => void 0
693
707
  };
694
708
  var NodeInteractionCtx = (0, import_react3.createContext)(NoopInteraction);
695
709
  function NodeInteractionProvider({
@@ -721,10 +735,11 @@ function PillNodeChrome({ node, selected }) {
721
735
  });
722
736
  }
723
737
  }, [editing, node.name]);
738
+ const { rename } = interaction;
724
739
  const commit = (0, import_react4.useCallback)(() => {
725
740
  setEditing(false);
726
- interaction.rename(node.id, draft);
727
- }, [interaction, node.id, draft]);
741
+ rename == null ? void 0 : rename(node.id, draft);
742
+ }, [rename, node.id, draft]);
728
743
  const onKeyDown = (0, import_react4.useCallback)(
729
744
  (e) => {
730
745
  if (e.key === "Enter") {
@@ -739,6 +754,9 @@ function PillNodeChrome({ node, selected }) {
739
754
  );
740
755
  const lastClick = (0, import_react4.useRef)(0);
741
756
  const onNameClick = (0, import_react4.useCallback)(() => {
757
+ if (rename === void 0) {
758
+ return;
759
+ }
742
760
  const now = performance.now();
743
761
  if (now - lastClick.current < DOUBLE_CLICK_MS) {
744
762
  setEditing(true);
@@ -746,7 +764,7 @@ function PillNodeChrome({ node, selected }) {
746
764
  } else {
747
765
  lastClick.current = now;
748
766
  }
749
- }, []);
767
+ }, [rename]);
750
768
  const inputs = node.ports.filter((p) => p.direction === "I" && !p.isError);
751
769
  const outputs = node.ports.filter((p) => p.direction === "O" && !p.isError);
752
770
  const className2 = [
@@ -781,7 +799,7 @@ function PillNodeChrome({ node, selected }) {
781
799
  onClick: () => {
782
800
  onNameClick();
783
801
  },
784
- title: "Double-click to rename",
802
+ title: rename === void 0 ? void 0 : "Double-click to rename",
785
803
  children: node.name
786
804
  }
787
805
  ),
@@ -1318,12 +1336,21 @@ function ToolbarGhostButton({
1318
1336
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: "hl-toolbar-btn", onClick, title, children });
1319
1337
  }
1320
1338
 
1321
- // src/toolbar/ToolCluster.tsx
1339
+ // src/toolbar/ReadOnlyBadge.tsx
1322
1340
  var import_jsx_runtime17 = require("react/jsx-runtime");
1341
+ function ReadOnlyBadge({ label = "View only", title }) {
1342
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "hl-readonly-badge", title: title != null ? title : label, children: [
1343
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(LockIcon, { size: 12 }),
1344
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "hl-readonly-badge-label", children: label })
1345
+ ] });
1346
+ }
1347
+
1348
+ // src/toolbar/ToolCluster.tsx
1349
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1323
1350
  function ToolCluster({ items }) {
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)(
1351
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "hl-toolcluster", role: "toolbar", "aria-label": "Canvas tools", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "hl-toolcluster-slot", children: [
1352
+ item.separatorBefore === true && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "hl-toolcluster-sep", "aria-hidden": true }),
1353
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1327
1354
  "button",
1328
1355
  {
1329
1356
  type: "button",
@@ -1341,30 +1368,30 @@ function ToolCluster({ items }) {
1341
1368
 
1342
1369
  // src/canvas/CanvasChrome.tsx
1343
1370
  var import_react8 = require("@xyflow/react");
1344
- var import_jsx_runtime18 = require("react/jsx-runtime");
1371
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1345
1372
  function CanvasChrome({ background = true }) {
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" }) });
1373
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_jsx_runtime19.Fragment, { children: background && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react8.Background, { variant: import_react8.BackgroundVariant.Dots, gap: 26, size: 1, color: "#242830" }) });
1347
1374
  }
1348
1375
 
1349
1376
  // src/canvas/CanvasDock.tsx
1350
- var import_jsx_runtime19 = require("react/jsx-runtime");
1377
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1351
1378
  function CursorIcon() {
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" }) });
1379
+ return /* @__PURE__ */ (0, import_jsx_runtime20.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_runtime20.jsx)("path", { d: "M5 3l6 16 2-6 6-2z" }) });
1353
1380
  }
1354
1381
  function HandIcon() {
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" }) });
1382
+ return /* @__PURE__ */ (0, import_jsx_runtime20.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_runtime20.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" }) });
1356
1383
  }
1357
1384
  function PlusIcon() {
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" }) });
1385
+ return /* @__PURE__ */ (0, import_jsx_runtime20.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_runtime20.jsx)("path", { d: "M12 5v14M5 12h14" }) });
1359
1386
  }
1360
1387
  function FitIcon() {
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" }) });
1388
+ return /* @__PURE__ */ (0, import_jsx_runtime20.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_runtime20.jsx)("path", { d: "M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5" }) });
1362
1389
  }
1363
1390
  function UndoIcon() {
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" }) });
1391
+ return /* @__PURE__ */ (0, import_jsx_runtime20.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_runtime20.jsx)("path", { d: "M9 7L4 12l5 5M4 12h11a5 5 0 010 10h-1" }) });
1365
1392
  }
1366
1393
  function RedoIcon() {
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" }) });
1394
+ return /* @__PURE__ */ (0, import_jsx_runtime20.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_runtime20.jsx)("path", { d: "M15 7l5 5-5 5M20 12H9a5 5 0 000 10h1" }) });
1368
1395
  }
1369
1396
  function CanvasDock({
1370
1397
  zoom,
@@ -1377,76 +1404,79 @@ function CanvasDock({
1377
1404
  onUndo,
1378
1405
  onRedo,
1379
1406
  canUndo,
1380
- canRedo
1407
+ canRedo,
1408
+ readOnly = false
1381
1409
  }) {
1382
1410
  const pct = `${String(Math.round(zoom * 100))}%`;
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)(
1385
- "button",
1386
- {
1387
- className: "hl-dock-btn",
1388
- onClick: onUndo,
1389
- disabled: !canUndo,
1390
- title: "Undo (\u2318Z)",
1391
- "aria-label": "Undo",
1392
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(UndoIcon, {})
1393
- }
1394
- ),
1395
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1396
- "button",
1397
- {
1398
- className: "hl-dock-btn",
1399
- onClick: onRedo,
1400
- disabled: !canRedo,
1401
- title: "Redo (\u2318\u21E7Z)",
1402
- "aria-label": "Redo",
1403
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(RedoIcon, {})
1404
- }
1405
- ),
1406
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "hl-dock-sep", "aria-hidden": true }),
1407
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1408
- "button",
1409
- {
1410
- className: "hl-dock-btn" + (mode === "select" ? " is-active" : ""),
1411
- onClick: () => onModeChange("select"),
1412
- title: "Select (V)",
1413
- "aria-pressed": mode === "select",
1414
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CursorIcon, {})
1415
- }
1416
- ),
1417
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1418
- "button",
1419
- {
1420
- className: "hl-dock-btn" + (mode === "pan" ? " is-active" : ""),
1421
- onClick: () => onModeChange("pan"),
1422
- title: "Pan (Space)",
1423
- "aria-pressed": mode === "pan",
1424
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(HandIcon, {})
1425
- }
1426
- ),
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, {}) })
1411
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "hl-dock", role: "toolbar", "aria-label": "Canvas tools", children: [
1412
+ readOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
1413
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1414
+ "button",
1415
+ {
1416
+ className: "hl-dock-btn",
1417
+ onClick: onUndo,
1418
+ disabled: !canUndo,
1419
+ title: "Undo (\u2318Z)",
1420
+ "aria-label": "Undo",
1421
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(UndoIcon, {})
1422
+ }
1423
+ ),
1424
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1425
+ "button",
1426
+ {
1427
+ className: "hl-dock-btn",
1428
+ onClick: onRedo,
1429
+ disabled: !canRedo,
1430
+ title: "Redo (\u2318\u21E7Z)",
1431
+ "aria-label": "Redo",
1432
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(RedoIcon, {})
1433
+ }
1434
+ ),
1435
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-dock-sep", "aria-hidden": true }),
1436
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1437
+ "button",
1438
+ {
1439
+ className: "hl-dock-btn" + (mode === "select" ? " is-active" : ""),
1440
+ onClick: () => onModeChange("select"),
1441
+ title: "Select (V)",
1442
+ "aria-pressed": mode === "select",
1443
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(CursorIcon, {})
1444
+ }
1445
+ ),
1446
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1447
+ "button",
1448
+ {
1449
+ className: "hl-dock-btn" + (mode === "pan" ? " is-active" : ""),
1450
+ onClick: () => onModeChange("pan"),
1451
+ title: "Pan (Space)",
1452
+ "aria-pressed": mode === "pan",
1453
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(HandIcon, {})
1454
+ }
1455
+ ),
1456
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { className: "hl-dock-btn hl-dock-add", onClick: onAdd, title: "Add node", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PlusIcon, {}) }),
1457
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-dock-sep", "aria-hidden": true })
1458
+ ] }),
1459
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { className: "hl-dock-btn", onClick: onZoomOut, title: "Zoom out", "aria-label": "Zoom out", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-dock-glyph", children: "\u2212" }) }),
1460
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { className: "hl-dock-zoom hl-mono", onClick: onFitView, title: "Fit view", children: pct }),
1461
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { className: "hl-dock-btn", onClick: onZoomIn, title: "Zoom in", "aria-label": "Zoom in", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-dock-glyph", children: "+" }) }),
1462
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { className: "hl-dock-btn hl-dock-fit", onClick: onFitView, title: "Fit view", "aria-label": "Fit view", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(FitIcon, {}) })
1433
1463
  ] });
1434
1464
  }
1435
1465
 
1436
1466
  // src/canvas/EmptyState.tsx
1437
- var import_jsx_runtime20 = require("react/jsx-runtime");
1467
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1438
1468
  function EmptyState({
1439
1469
  title = "An empty canvas.",
1440
1470
  hint = "add a source \xB7 wire it to a sink \xB7 run"
1441
1471
  }) {
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 })
1472
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "hl-empty", "aria-hidden": "true", children: [
1473
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "hl-empty-title", children: title }),
1474
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "hl-empty-hint", children: hint })
1445
1475
  ] });
1446
1476
  }
1447
1477
 
1448
1478
  // src/panels/InspectorPanel.tsx
1449
- var import_editor_react2 = require("@hlang-tech/editor-react");
1479
+ var import_editor_react3 = require("@hlang-tech/editor-react");
1450
1480
 
1451
1481
  // src/schema/param-field.ts
1452
1482
  function humanizeKey(key) {
@@ -1531,6 +1561,9 @@ function useNodeParams(core, nodeId) {
1531
1561
  );
1532
1562
  }
1533
1563
 
1564
+ // src/panels/ConnectionInspector.tsx
1565
+ var import_editor_react2 = require("@hlang-tech/editor-react");
1566
+
1534
1567
  // src/hooks/useEdgeBounds.ts
1535
1568
  var import_react10 = require("react");
1536
1569
  var import_editor_core2 = require("@hlang-tech/editor-core");
@@ -1572,17 +1605,17 @@ function useEdgeBounds(core, edgeId) {
1572
1605
 
1573
1606
  // src/panels/ParamForm.tsx
1574
1607
  var import_react11 = require("react");
1575
- var import_jsx_runtime21 = require("react/jsx-runtime");
1576
- function ParamForm({ fields, onCommit }) {
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)) });
1608
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1609
+ function ParamForm({ fields, onCommit, disabled = false }) {
1610
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("fieldset", { className: "hl-form", disabled, children: fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FieldRow, { field, onCommit }, field.key)) });
1578
1611
  }
1579
1612
  function FieldRow({
1580
1613
  field,
1581
1614
  onCommit
1582
1615
  }) {
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 })
1616
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { className: "hl-field", children: [
1617
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "hl-field-label", children: field.label }),
1618
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FieldControl, { field, onCommit })
1586
1619
  ] });
1587
1620
  }
1588
1621
  function FieldControl({
@@ -1591,7 +1624,7 @@ function FieldControl({
1591
1624
  }) {
1592
1625
  switch (field.type) {
1593
1626
  case "toggle":
1594
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1627
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1595
1628
  "button",
1596
1629
  {
1597
1630
  type: "button",
@@ -1600,17 +1633,17 @@ function FieldControl({
1600
1633
  className: `hl-toggle${field.value ? " is-on" : ""}`,
1601
1634
  disabled: field.disabled,
1602
1635
  onClick: () => onCommit(field.key, !field.value),
1603
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-toggle-knob" })
1636
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "hl-toggle-knob" })
1604
1637
  }
1605
1638
  );
1606
1639
  case "number":
1607
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(NumberControl, { field, onCommit });
1640
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(NumberControl, { field, onCommit });
1608
1641
  case "textarea":
1609
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TextControl, { field, onCommit, multiline: true });
1642
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TextControl, { field, onCommit, multiline: true });
1610
1643
  case "text":
1611
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TextControl, { field, onCommit });
1644
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TextControl, { field, onCommit });
1612
1645
  case "custom":
1613
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-field-custom hl-mono", title: field.fieldType, children: describeCustom(field.value) });
1646
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "hl-field-custom hl-mono", title: field.fieldType, children: describeCustom(field.value) });
1614
1647
  }
1615
1648
  }
1616
1649
  function TextControl({
@@ -1632,7 +1665,7 @@ function TextControl({
1632
1665
  onBlur: commit
1633
1666
  };
1634
1667
  if (multiline) {
1635
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1668
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1636
1669
  "textarea",
1637
1670
  {
1638
1671
  ...shared,
@@ -1641,7 +1674,7 @@ function TextControl({
1641
1674
  }
1642
1675
  );
1643
1676
  }
1644
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1677
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1645
1678
  "input",
1646
1679
  {
1647
1680
  ...shared,
@@ -1666,7 +1699,7 @@ function NumberControl({
1666
1699
  onCommit(field.key, parsed);
1667
1700
  }
1668
1701
  };
1669
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1702
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1670
1703
  "input",
1671
1704
  {
1672
1705
  className: "hl-input hl-mono",
@@ -1698,9 +1731,10 @@ function describeCustom(value) {
1698
1731
  }
1699
1732
 
1700
1733
  // src/panels/ConnectionInspector.tsx
1701
- var import_jsx_runtime22 = require("react/jsx-runtime");
1734
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1702
1735
  function ConnectionInspector({ core, edgeId }) {
1703
1736
  const { edge, setBounds } = useEdgeBounds(core, edgeId);
1737
+ const readOnly = (0, import_editor_react2.useReadOnly)(core);
1704
1738
  if (edge === void 0) {
1705
1739
  return null;
1706
1740
  }
@@ -1708,25 +1742,26 @@ function ConnectionInspector({ core, edgeId }) {
1708
1742
  var _a, _b;
1709
1743
  return `${(_b = (_a = core.model.getNode(endpoint.nodeId)) == null ? void 0 : _a.name) != null ? _b : endpoint.nodeId}.${endpoint.portId}`;
1710
1744
  };
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: [
1745
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "hl-inspector", children: [
1746
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("header", { className: "hl-inspector-head", children: [
1747
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "hl-inspector-kind hl-connection", children: "\u2192" }),
1748
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "hl-inspector-titles", children: [
1749
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "hl-inspector-name", children: "Connection" }),
1750
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "hl-inspector-role hl-mono", children: [
1717
1751
  end(edge.source),
1718
1752
  " \u2192 ",
1719
1753
  end(edge.target)
1720
1754
  ] })
1721
1755
  ] })
1722
1756
  ] }),
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)(
1757
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("section", { className: "hl-inspector-section", children: [
1758
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h3", { className: "hl-inspector-section-title", children: "Back-pressure" }),
1759
+ /* @__PURE__ */ (0, import_jsx_runtime23.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." }),
1760
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1727
1761
  ParamForm,
1728
1762
  {
1729
1763
  fields: boundsFields(edge),
1764
+ disabled: readOnly,
1730
1765
  onCommit: (key, value) => {
1731
1766
  setBounds(nextBounds(edge, key, value));
1732
1767
  }
@@ -1852,10 +1887,10 @@ function previewValue(value, maxLength = PREVIEW_LENGTH) {
1852
1887
 
1853
1888
  // src/live/RunTraceContext.tsx
1854
1889
  var import_react12 = require("react");
1855
- var import_jsx_runtime23 = require("react/jsx-runtime");
1890
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1856
1891
  var RunTraceContext = (0, import_react12.createContext)(null);
1857
1892
  function RunTraceProvider({ trace, children }) {
1858
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(RunTraceContext.Provider, { value: trace, children });
1893
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(RunTraceContext.Provider, { value: trace, children });
1859
1894
  }
1860
1895
  function useRunTraceStore() {
1861
1896
  return (0, import_react12.useContext)(RunTraceContext);
@@ -1869,7 +1904,7 @@ function useRunTraceVersion(trace) {
1869
1904
  }
1870
1905
 
1871
1906
  // src/panels/NodeRunStats.tsx
1872
- var import_jsx_runtime24 = require("react/jsx-runtime");
1907
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1873
1908
  function NodeRunStats({ nodeId }) {
1874
1909
  const trace = useRunTraceStore();
1875
1910
  useRunTraceVersion(trace);
@@ -1877,105 +1912,107 @@ function NodeRunStats({ nodeId }) {
1877
1912
  return null;
1878
1913
  }
1879
1914
  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 })
1915
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("section", { className: "hl-inspector-section", children: [
1916
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("h3", { className: "hl-inspector-section-title", children: "Run" }),
1917
+ stats === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "hl-inspector-hint hl-runstats-hint", children: "No activity in this run." }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("dl", { className: "hl-runstats", children: [
1918
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-runstats-cell", children: [
1919
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dt", { children: "In" }),
1920
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dd", { className: "hl-mono", children: stats.received })
1886
1921
  ] }),
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 })
1922
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-runstats-cell", children: [
1923
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dt", { children: "Out" }),
1924
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dd", { className: "hl-mono", children: stats.sent })
1890
1925
  ] }),
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 })
1926
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: `hl-runstats-cell${stats.errors > 0 ? " is-error" : ""}`, children: [
1927
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dt", { children: "Errors" }),
1928
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dd", { className: "hl-mono", children: stats.errors })
1894
1929
  ] }),
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) })
1930
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-runstats-cell", children: [
1931
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dt", { children: "Avg" }),
1932
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dd", { className: "hl-mono", children: stats.received === 0 ? "\u2014" : formatDuration(stats.totalMs / stats.received) })
1898
1933
  ] }),
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) })
1934
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-runstats-cell", children: [
1935
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dt", { children: "Max" }),
1936
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dd", { className: "hl-mono", children: stats.received === 0 ? "\u2014" : formatDuration(stats.maxMs) })
1902
1937
  ] }),
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 })
1938
+ stats.dropped > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-runstats-cell is-warning", children: [
1939
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dt", { children: "Dropped" }),
1940
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dd", { className: "hl-mono", children: stats.dropped })
1906
1941
  ] }),
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" })
1942
+ stats.finished && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-runstats-cell is-done", children: [
1943
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dt", { children: "State" }),
1944
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dd", { children: "Finished" })
1910
1945
  ] }),
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) })
1946
+ stats.received > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-runstats-cell is-wide", children: [
1947
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dt", { children: "Last in" }),
1948
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dd", { className: "hl-mono", children: previewValue(stats.lastReceived) })
1914
1949
  ] }),
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) })
1950
+ stats.sent > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-runstats-cell is-wide", children: [
1951
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dt", { children: "Last out" }),
1952
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("dd", { className: "hl-mono", children: previewValue(stats.lastSent) })
1918
1953
  ] })
1919
1954
  ] })
1920
1955
  ] });
1921
1956
  }
1922
1957
 
1923
1958
  // src/panels/InspectorPanel.tsx
1924
- var import_jsx_runtime25 = require("react/jsx-runtime");
1959
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1925
1960
  function InspectorPanel({ core, emptyState }) {
1926
- const selection = (0, import_editor_react2.useSelection)(core);
1961
+ const selection = (0, import_editor_react3.useSelection)(core);
1962
+ const readOnly = (0, import_editor_react3.useReadOnly)(core);
1927
1963
  const selectedNodes2 = selection.selectedNodes;
1928
1964
  const singleId = selectedNodes2.length === 1 ? selectedNodes2[0] : void 0;
1929
1965
  const params = useNodeParams(core, singleId);
1930
1966
  const node = params.node;
1931
1967
  const singleEdgeId = selectedNodes2.length === 0 && selection.selectedEdges.length === 1 ? selection.selectedEdges[0] : void 0;
1932
1968
  if (node === void 0 && singleEdgeId !== void 0) {
1933
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ConnectionInspector, { core, edgeId: singleEdgeId });
1969
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ConnectionInspector, { core, edgeId: singleEdgeId });
1934
1970
  }
1935
1971
  if (node === void 0) {
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." })
1972
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "hl-inspector hl-inspector-empty", children: emptyState != null ? emptyState : /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "hl-inspector-blank", children: [
1973
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-inspector-blank-badge", "aria-hidden": true }),
1974
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "hl-inspector-blank-title", children: selectedNodes2.length > 1 ? `${String(selectedNodes2.length)} nodes selected` : "Nothing selected" }),
1975
+ /* @__PURE__ */ (0, import_jsx_runtime26.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." })
1940
1976
  ] }) });
1941
1977
  }
1942
1978
  const descriptor = core.registry.get(node.code);
1943
1979
  const fields = descriptor === void 0 ? [] : deriveParamSchema(descriptor, node);
1944
1980
  const imageUrl = typeof node.params.imageUrl === "string" && node.params.imageUrl.length > 0 ? node.params.imageUrl : void 0;
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: [
1981
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "hl-inspector", children: [
1982
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: `hl-inspector-preview hl-${node.kind.toLowerCase()}`, children: imageUrl !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("img", { className: "hl-inspector-preview-img", src: imageUrl, alt: node.name }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-inspector-preview-badge hl-mono", children: node.kind }) }),
1983
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("header", { className: "hl-inspector-head", children: [
1984
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: `hl-inspector-kind hl-${node.kind.toLowerCase()}`, children: node.kind }),
1985
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "hl-inspector-titles", children: [
1986
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-inspector-name", children: node.name }),
1987
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { className: "hl-inspector-role hl-mono", children: [
1952
1988
  node.code,
1953
1989
  " \xB7 ",
1954
1990
  KIND_ROLE[node.kind]
1955
1991
  ] })
1956
1992
  ] })
1957
1993
  ] }),
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)(
1994
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("section", { className: "hl-inspector-section", children: [
1995
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("h3", { className: "hl-inspector-section-title", children: "Parameters" }),
1996
+ fields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "hl-inspector-hint", children: "This node has no parameters." }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1961
1997
  ParamForm,
1962
1998
  {
1963
1999
  fields,
2000
+ disabled: readOnly,
1964
2001
  onCommit: (key, value) => params.setParam(key, value)
1965
2002
  }
1966
2003
  )
1967
2004
  ] }),
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)(
2005
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(NodeRunStats, { nodeId: node.id }),
2006
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("section", { className: "hl-inspector-section", children: [
2007
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("h3", { className: "hl-inspector-section-title", children: "Ports" }),
2008
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { className: "hl-inspector-ports", children: node.ports.map((port) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1972
2009
  "li",
1973
2010
  {
1974
2011
  className: `hl-inspector-port${port.isError ? " is-error" : ""}`,
1975
2012
  children: [
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 })
2013
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: `hl-port-dot hl-dir-${port.direction.toLowerCase()}` }),
2014
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-mono", children: port.name }),
2015
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-inspector-port-dir hl-mono", children: port.direction })
1979
2016
  ]
1980
2017
  },
1981
2018
  port.id
@@ -1987,8 +2024,8 @@ function InspectorPanel({ core, emptyState }) {
1987
2024
  // src/panels/OutlineTree.tsx
1988
2025
  var import_react13 = require("react");
1989
2026
  var import_editor_core3 = require("@hlang-tech/editor-core");
1990
- var import_editor_react3 = require("@hlang-tech/editor-react");
1991
- var import_jsx_runtime26 = require("react/jsx-runtime");
2027
+ var import_editor_react4 = require("@hlang-tech/editor-react");
2028
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1992
2029
  function useTopologyComponents(core) {
1993
2030
  const [tick, setTick] = (0, import_react13.useState)(0);
1994
2031
  const bump = (0, import_react13.useCallback)(() => setTick((t) => t + 1), []);
@@ -2011,9 +2048,9 @@ function useTopologyComponents(core) {
2011
2048
  }
2012
2049
  function OutlineTree({ core, onFocus }) {
2013
2050
  const components = useTopologyComponents(core);
2014
- const selection = (0, import_editor_react3.useSelection)(core);
2015
- const viewState = (0, import_editor_react3.useViewState)(core);
2016
- const diagnostics = (0, import_editor_react3.useDiagnostics)(core);
2051
+ const selection = (0, import_editor_react4.useSelection)(core);
2052
+ const viewState = (0, import_editor_react4.useViewState)(core);
2053
+ const diagnostics = (0, import_editor_react4.useDiagnostics)(core);
2017
2054
  const [collapsed, setCollapsed] = (0, import_react13.useState)(/* @__PURE__ */ new Set());
2018
2055
  const nodeById = (0, import_react13.useMemo)(() => {
2019
2056
  const m = /* @__PURE__ */ new Map();
@@ -2041,80 +2078,80 @@ function OutlineTree({ core, onFocus }) {
2041
2078
  [selection, onFocus]
2042
2079
  );
2043
2080
  if (components.length === 0) {
2044
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "hl-outline-empty hl-mono", children: "no layers yet" });
2081
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "hl-outline-empty hl-mono", children: "no layers yet" });
2045
2082
  }
2046
2083
  const groups = [...components].sort((a, b) => b.nodeIds.size - a.nodeIds.size);
2047
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "hl-outline", role: "tree", "aria-label": "Outline", children: groups.map((group, gi) => {
2084
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "hl-outline", role: "tree", "aria-label": "Outline", children: groups.map((group, gi) => {
2048
2085
  const members = [...group.nodeIds].map((id) => nodeById.get(id)).filter((n) => n !== void 0);
2049
2086
  const open = !collapsed.has(group.id);
2050
2087
  const groupName = `Flow ${String(gi + 1)}`;
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)(
2088
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "hl-outline-group", role: "treeitem", "aria-expanded": open, children: [
2089
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
2053
2090
  "button",
2054
2091
  {
2055
2092
  className: "hl-outline-grouphead",
2056
2093
  onClick: () => toggleGroup(group.id),
2057
2094
  children: [
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 })
2095
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-outline-caret" + (open ? " is-open" : ""), "aria-hidden": true, children: "\u25B8" }),
2096
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-outline-groupname", children: groupName }),
2097
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-outline-count hl-mono", children: group.nodeIds.size })
2061
2098
  ]
2062
2099
  }
2063
2100
  ),
2064
- open && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { className: "hl-outline-members", children: members.map((node) => {
2101
+ open && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("ul", { className: "hl-outline-members", children: members.map((node) => {
2065
2102
  const active = selection.isSelected(node.id);
2066
2103
  const isHidden = viewState.isHidden(node.id);
2067
2104
  const isLocked = viewState.isLocked(node.id);
2068
2105
  const { imageUrl } = node.params;
2069
2106
  const thumbnail = typeof imageUrl === "string" && imageUrl.length > 0 ? imageUrl : void 0;
2070
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2107
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
2071
2108
  "div",
2072
2109
  {
2073
2110
  className: "hl-outline-row" + (active ? " is-active" : "") + (isHidden ? " is-hidden" : "") + (isLocked ? " is-locked" : ""),
2074
2111
  children: [
2075
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("button", { className: "hl-outline-main", onClick: () => select(node.id), children: [
2076
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2112
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("button", { className: "hl-outline-main", onClick: () => select(node.id), children: [
2113
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2077
2114
  "span",
2078
2115
  {
2079
2116
  className: `hl-outline-icon hl-kind-${node.kind.toLowerCase()}`,
2080
2117
  "aria-hidden": true,
2081
- children: thumbnail !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2118
+ children: thumbnail !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2082
2119
  "img",
2083
2120
  {
2084
2121
  className: "hl-outline-thumb",
2085
2122
  src: thumbnail,
2086
2123
  alt: ""
2087
2124
  }
2088
- ) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(NodeIcon, { code: node.code, kind: node.kind, size: 13 })
2125
+ ) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(NodeIcon, { code: node.code, kind: node.kind, size: 13 })
2089
2126
  }
2090
2127
  ),
2091
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-outline-name", children: node.name }),
2128
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-outline-name", children: node.name }),
2092
2129
  (() => {
2093
2130
  const diag = diagnostics.worstOf(node.id);
2094
- return diag === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(DiagnosticBadge, { diagnostic: diag, variant: "hl-outline-diag", size: 11 });
2131
+ return diag === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(DiagnosticBadge, { diagnostic: diag, variant: "hl-outline-diag", size: 11 });
2095
2132
  })(),
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 }) })
2133
+ isLocked && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-outline-badge", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(LockIcon, { size: 11 }) }),
2134
+ isHidden && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-outline-badge", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EyeOffIcon, { size: 11 }) })
2098
2135
  ] }),
2099
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { className: "hl-outline-controls", children: [
2100
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2136
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "hl-outline-controls", children: [
2137
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2101
2138
  "button",
2102
2139
  {
2103
2140
  className: "hl-outline-ctl" + (isHidden ? " is-active-ctl" : ""),
2104
2141
  onClick: () => viewState.toggleHidden(node.id),
2105
2142
  "aria-label": isHidden ? "Show layer" : "Hide layer",
2106
2143
  title: isHidden ? "Show" : "Hide",
2107
- children: isHidden ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(EyeOffIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(EyeIcon, {})
2144
+ children: isHidden ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EyeOffIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EyeIcon, {})
2108
2145
  }
2109
2146
  ),
2110
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2147
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2111
2148
  "button",
2112
2149
  {
2113
2150
  className: "hl-outline-ctl" + (isLocked ? " is-active-ctl" : ""),
2114
2151
  onClick: () => viewState.toggleLocked(node.id),
2115
2152
  "aria-label": isLocked ? "Unlock layer" : "Lock layer",
2116
2153
  title: isLocked ? "Unlock" : "Lock",
2117
- children: isLocked ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(LockIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(UnlockIcon, {})
2154
+ children: isLocked ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(LockIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(UnlockIcon, {})
2118
2155
  }
2119
2156
  )
2120
2157
  ] })
@@ -2127,21 +2164,48 @@ function OutlineTree({ core, onFocus }) {
2127
2164
  }
2128
2165
 
2129
2166
  // src/panels/PreviewPanel.tsx
2130
- var import_jsx_runtime27 = require("react/jsx-runtime");
2167
+ var import_jsx_runtime28 = require("react/jsx-runtime");
2131
2168
  function PreviewPanel({ imageUrl, title = "Preview", 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 })
2169
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("section", { className: "hl-preview", "aria-label": "Preview", children: [
2170
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "hl-preview-head", children: [
2171
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "hl-preview-title", children: title }),
2172
+ status !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "hl-preview-status hl-mono", children: status })
2136
2173
  ] }),
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" }) })
2174
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "hl-preview-frame", children: imageUrl !== void 0 && imageUrl.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("img", { className: "hl-preview-img", src: imageUrl, alt: title, draggable: false }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "hl-preview-placeholder hl-mono", children: "no output yet" }) })
2175
+ ] });
2176
+ }
2177
+
2178
+ // src/panels/PanelHead.tsx
2179
+ var import_jsx_runtime29 = require("react/jsx-runtime");
2180
+ function PanelHead({ leading, meta, children }) {
2181
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("header", { className: "hl-panel-head", children: [
2182
+ leading,
2183
+ meta === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "hl-panel-meta hl-mono", children: meta }),
2184
+ children === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "hl-panel-actions", children })
2138
2185
  ] });
2139
2186
  }
2187
+ function PanelAction({ icon, label, onClick, pressed, disabled = false, title }) {
2188
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
2189
+ "button",
2190
+ {
2191
+ type: "button",
2192
+ className: `hl-panel-action${pressed === true ? " is-on" : ""}`,
2193
+ "aria-pressed": pressed,
2194
+ disabled,
2195
+ title: title != null ? title : label,
2196
+ onClick,
2197
+ children: [
2198
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "hl-panel-action-icon", "aria-hidden": true, children: icon }),
2199
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "hl-panel-action-label", children: label })
2200
+ ]
2201
+ }
2202
+ );
2203
+ }
2140
2204
 
2141
2205
  // src/panels/RailTabs.tsx
2142
- var import_jsx_runtime28 = require("react/jsx-runtime");
2206
+ var import_jsx_runtime30 = require("react/jsx-runtime");
2143
2207
  function RailTabs({ tabs, active, onChange }) {
2144
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "hl-railtabs", role: "tablist", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2208
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "hl-railtabs", role: "tablist", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2145
2209
  "button",
2146
2210
  {
2147
2211
  role: "tab",
@@ -2155,20 +2219,20 @@ function RailTabs({ tabs, active, onChange }) {
2155
2219
  }
2156
2220
 
2157
2221
  // src/panels/AssetGrid.tsx
2158
- var import_jsx_runtime29 = require("react/jsx-runtime");
2222
+ var import_jsx_runtime31 = require("react/jsx-runtime");
2159
2223
  function AssetGrid({ items, onSelect, emptyHint = "No assets yet" }) {
2160
2224
  if (items.length === 0) {
2161
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "hl-assetgrid-empty hl-mono", children: emptyHint });
2225
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "hl-assetgrid-empty hl-mono", children: emptyHint });
2162
2226
  }
2163
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "hl-assetgrid", children: items.map((item) => {
2227
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "hl-assetgrid", children: items.map((item) => {
2164
2228
  var _a;
2165
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2229
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2166
2230
  "button",
2167
2231
  {
2168
2232
  className: "hl-assetgrid-cell",
2169
2233
  onClick: () => onSelect == null ? void 0 : onSelect(item.id),
2170
2234
  title: item.label,
2171
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("img", { className: "hl-assetgrid-img", src: item.url, alt: (_a = item.label) != null ? _a : "", loading: "lazy" })
2235
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("img", { className: "hl-assetgrid-img", src: item.url, alt: (_a = item.label) != null ? _a : "", loading: "lazy" })
2172
2236
  },
2173
2237
  item.id
2174
2238
  );
@@ -2178,8 +2242,8 @@ function AssetGrid({ items, onSelect, emptyHint = "No assets yet" }) {
2178
2242
  // src/panels/PositionPanel.tsx
2179
2243
  var import_react14 = require("react");
2180
2244
  var import_editor_core4 = require("@hlang-tech/editor-core");
2181
- var import_editor_react4 = require("@hlang-tech/editor-react");
2182
- var import_jsx_runtime30 = require("react/jsx-runtime");
2245
+ var import_editor_react5 = require("@hlang-tech/editor-react");
2246
+ var import_jsx_runtime32 = require("react/jsx-runtime");
2183
2247
  function selectedNodes(core) {
2184
2248
  const ids = core.selection.getSelectedNodes();
2185
2249
  const out = [];
@@ -2193,7 +2257,8 @@ function selectedNodes(core) {
2193
2257
  }
2194
2258
  function PositionPanel({ core, livePosition }) {
2195
2259
  var _a;
2196
- (0, import_editor_react4.useSelection)(core);
2260
+ (0, import_editor_react5.useSelection)(core);
2261
+ const readOnly = (0, import_editor_react5.useReadOnly)(core);
2197
2262
  const [, bump] = (0, import_react14.useReducer)((n) => n + 1, 0);
2198
2263
  (0, import_react14.useEffect)(() => {
2199
2264
  const kinds = ["nodeChanged", "nodeAdded", "nodeRemoved"];
@@ -2264,7 +2329,7 @@ function PositionPanel({ core, livePosition }) {
2264
2329
  },
2265
2330
  [core, single]
2266
2331
  );
2267
- const disabled = nodes.length === 0;
2332
+ const disabled = readOnly || nodes.length === 0;
2268
2333
  const alignButtons = [
2269
2334
  { kind: "left", label: "Align left", d: "M5 4v16M9 8h8M9 16h5" },
2270
2335
  { kind: "hcenter", label: "Align centre", d: "M12 4v16M8 9h8M9 15h6" },
@@ -2273,9 +2338,9 @@ function PositionPanel({ core, livePosition }) {
2273
2338
  { kind: "vcenter", label: "Align middle", d: "M4 12h16M9 8v8M15 9v6" },
2274
2339
  { kind: "bottom", label: "Align bottom", d: "M4 19h16M8 7v8M16 12v3" }
2275
2340
  ];
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)(
2341
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("section", { className: "hl-position", children: [
2342
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("h3", { className: "hl-position-title hl-mono", children: "Position" }),
2343
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "hl-position-align", role: "group", "aria-label": "Align", children: alignButtons.map((b) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2279
2344
  "button",
2280
2345
  {
2281
2346
  className: "hl-position-alignbtn",
@@ -2283,38 +2348,38 @@ function PositionPanel({ core, livePosition }) {
2283
2348
  disabled,
2284
2349
  title: b.label,
2285
2350
  "aria-label": b.label,
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 }) })
2351
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.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_runtime32.jsx)("path", { d: b.d }) })
2287
2352
  },
2288
2353
  b.kind
2289
2354
  )) }),
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)(
2355
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "hl-position-coords", children: [
2356
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("label", { className: "hl-position-field", children: [
2357
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2293
2358
  "input",
2294
2359
  {
2295
2360
  type: "number",
2296
2361
  className: "hl-position-input hl-mono",
2297
2362
  value: shownPos !== void 0 ? Math.round(shownPos.x) : "",
2298
- disabled: single === void 0,
2363
+ disabled: readOnly || single === void 0,
2299
2364
  onChange: (e) => setCoord("x", Number(e.target.value)),
2300
2365
  "aria-label": "X position"
2301
2366
  }
2302
2367
  ),
2303
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "hl-position-axis hl-mono", children: "X" })
2368
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "hl-position-axis hl-mono", children: "X" })
2304
2369
  ] }),
2305
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("label", { className: "hl-position-field", children: [
2306
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2370
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("label", { className: "hl-position-field", children: [
2371
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2307
2372
  "input",
2308
2373
  {
2309
2374
  type: "number",
2310
2375
  className: "hl-position-input hl-mono",
2311
2376
  value: shownPos !== void 0 ? Math.round(shownPos.y) : "",
2312
- disabled: single === void 0,
2377
+ disabled: readOnly || single === void 0,
2313
2378
  onChange: (e) => setCoord("y", Number(e.target.value)),
2314
2379
  "aria-label": "Y position"
2315
2380
  }
2316
2381
  ),
2317
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "hl-position-axis hl-mono", children: "Y" })
2382
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "hl-position-axis hl-mono", children: "Y" })
2318
2383
  ] })
2319
2384
  ] })
2320
2385
  ] });
@@ -2322,7 +2387,7 @@ function PositionPanel({ core, livePosition }) {
2322
2387
 
2323
2388
  // src/command-palette/NodeSearch.tsx
2324
2389
  var import_react15 = require("react");
2325
- var import_jsx_runtime31 = require("react/jsx-runtime");
2390
+ var import_jsx_runtime33 = require("react/jsx-runtime");
2326
2391
  var PANEL_WIDTH = 320;
2327
2392
  var PANEL_MAX_HEIGHT = 360;
2328
2393
  function NodeSearch({ controller }) {
@@ -2382,8 +2447,8 @@ function NodeSearch({ controller }) {
2382
2447
  }
2383
2448
  const style = clampToViewport(state.screen.x, state.screen.y);
2384
2449
  const connectMode = state.pending !== void 0;
2385
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
2386
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2450
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
2451
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2387
2452
  "div",
2388
2453
  {
2389
2454
  className: "hl-node-search-scrim",
@@ -2392,10 +2457,10 @@ function NodeSearch({ controller }) {
2392
2457
  }
2393
2458
  }
2394
2459
  ),
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)(
2460
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "hl-node-search", style, role: "dialog", "aria-label": "Create node", children: [
2461
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "hl-node-search-field", children: [
2462
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "hl-node-search-glyph", "aria-hidden": true, children: connectMode ? "\u2933" : "\uFF0B" }),
2463
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2399
2464
  "input",
2400
2465
  {
2401
2466
  ref: inputRef,
@@ -2413,12 +2478,12 @@ function NodeSearch({ controller }) {
2413
2478
  }
2414
2479
  )
2415
2480
  ] }),
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" }),
2481
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("ul", { className: "hl-node-search-list", role: "listbox", children: [
2482
+ results.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("li", { className: "hl-node-search-empty hl-mono", children: "no matching nodes" }),
2418
2483
  results.map((view, index) => {
2419
2484
  const descriptor = view.result.value;
2420
2485
  const kind = descriptor.kind;
2421
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
2486
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
2422
2487
  "li",
2423
2488
  {
2424
2489
  role: "option",
@@ -2430,15 +2495,15 @@ function NodeSearch({ controller }) {
2430
2495
  choose(view);
2431
2496
  },
2432
2497
  children: [
2433
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2498
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2434
2499
  "span",
2435
2500
  {
2436
2501
  className: `hl-node-search-kind hl-mono hl-kind-${kind.toLowerCase()}`,
2437
2502
  children: kind
2438
2503
  }
2439
2504
  ),
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 })
2505
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "hl-node-search-label", children: highlight(descriptor.displayName, view.result.match) }),
2506
+ view.connectPortId !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "hl-node-search-port hl-mono", children: view.connectPortId })
2442
2507
  ]
2443
2508
  },
2444
2509
  descriptor.code
@@ -2452,7 +2517,7 @@ function highlight(text, match) {
2452
2517
  const hit = new Set(match.positions);
2453
2518
  const chars = Array.from(text);
2454
2519
  return chars.map(
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)
2520
+ (ch, i) => hit.has(i) ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("mark", { className: "hl-node-search-hit", children: ch }, i) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { children: ch }, i)
2456
2521
  );
2457
2522
  }
2458
2523
  function clampToViewport(x, y) {
@@ -2632,7 +2697,7 @@ function useNodeSearchTriggers(core, search, options = {}) {
2632
2697
  (event, connectionState) => {
2633
2698
  const start = startRef.current;
2634
2699
  startRef.current = null;
2635
- if (connectionState.toNode !== null || start === null) {
2700
+ if (core.mode.readOnly || connectionState.toNode !== null || start === null) {
2636
2701
  return;
2637
2702
  }
2638
2703
  if (start.nodeId === null || start.handleId === null || start.handleType === null) {
@@ -2663,6 +2728,9 @@ function useNodeSearchTriggers(core, search, options = {}) {
2663
2728
  );
2664
2729
  const onPaneClick = (0, import_react17.useCallback)(
2665
2730
  (event) => {
2731
+ if (core.mode.readOnly) {
2732
+ return;
2733
+ }
2666
2734
  const t = now();
2667
2735
  const isDouble = t - lastPaneClickRef.current <= doubleClickMs;
2668
2736
  lastPaneClickRef.current = t;
@@ -2673,7 +2741,7 @@ function useNodeSearchTriggers(core, search, options = {}) {
2673
2741
  const screen = { x: event.clientX, y: event.clientY };
2674
2742
  search.openBlank(screen, flow.screenToFlowPosition(screen));
2675
2743
  },
2676
- [flow, search, doubleClickMs, now]
2744
+ [core, flow, search, doubleClickMs, now]
2677
2745
  );
2678
2746
  return { onConnectStart, onConnectEnd, onPaneClick };
2679
2747
  }
@@ -2694,7 +2762,7 @@ function pointerOf(event) {
2694
2762
 
2695
2763
  // src/hooks/useEditorHotkeys.ts
2696
2764
  var import_react19 = require("react");
2697
- var import_editor_react5 = require("@hlang-tech/editor-react");
2765
+ var import_editor_react6 = require("@hlang-tech/editor-react");
2698
2766
  function isEditableTarget(target) {
2699
2767
  if (!(target instanceof HTMLElement)) {
2700
2768
  return false;
@@ -2710,7 +2778,7 @@ function withinScope(target, scope) {
2710
2778
  return root !== null && target instanceof Node && root.contains(target);
2711
2779
  }
2712
2780
  function useEditorHotkeys(core, options = {}) {
2713
- const clipboard = (0, import_editor_react5.useClipboard)(core);
2781
+ const clipboard = (0, import_editor_react6.useClipboard)(core);
2714
2782
  const { scope } = options;
2715
2783
  (0, import_react19.useEffect)(() => {
2716
2784
  const onKeyDown = (event) => {
@@ -2776,7 +2844,7 @@ function useEditorHotkeys(core, options = {}) {
2776
2844
 
2777
2845
  // src/context-menu/NodeContextMenu.tsx
2778
2846
  var import_react20 = require("react");
2779
- var import_jsx_runtime32 = require("react/jsx-runtime");
2847
+ var import_jsx_runtime34 = require("react/jsx-runtime");
2780
2848
  var PANEL_WIDTH2 = 208;
2781
2849
  var ITEM_HEIGHT = 34;
2782
2850
  var VIEWPORT_MARGIN = 8;
@@ -2854,8 +2922,8 @@ function NodeContextMenu({ controller, items }) {
2854
2922
  return null;
2855
2923
  }
2856
2924
  const style = clampToViewport2(state.screen.x, state.screen.y, rows.length);
2857
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
2858
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2925
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2926
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2859
2927
  "div",
2860
2928
  {
2861
2929
  className: "hl-ctx-scrim",
@@ -2868,7 +2936,7 @@ function NodeContextMenu({ controller, items }) {
2868
2936
  }
2869
2937
  }
2870
2938
  ),
2871
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2939
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2872
2940
  "div",
2873
2941
  {
2874
2942
  ref: menuRef,
@@ -2878,7 +2946,7 @@ function NodeContextMenu({ controller, items }) {
2878
2946
  "aria-label": "Node actions",
2879
2947
  tabIndex: -1,
2880
2948
  onKeyDown,
2881
- children: rows.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
2949
+ children: rows.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2882
2950
  "button",
2883
2951
  {
2884
2952
  type: "button",
@@ -2891,9 +2959,9 @@ function NodeContextMenu({ controller, items }) {
2891
2959
  choose(item);
2892
2960
  },
2893
2961
  children: [
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 })
2962
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "hl-ctx-item-icon", "aria-hidden": true, children: item.icon }),
2963
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "hl-ctx-item-label", children: item.label }),
2964
+ item.shortcut !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "hl-ctx-item-key hl-mono", children: item.shortcut })
2897
2965
  ]
2898
2966
  },
2899
2967
  item.id
@@ -2927,7 +2995,7 @@ function useNodeContextMenu() {
2927
2995
 
2928
2996
  // src/overlays/Cheatsheet.tsx
2929
2997
  var import_react22 = require("react");
2930
- var import_jsx_runtime33 = require("react/jsx-runtime");
2998
+ var import_jsx_runtime35 = require("react/jsx-runtime");
2931
2999
  var DEFAULT_GROUPS = [
2932
3000
  {
2933
3001
  title: "Edit",
@@ -2981,9 +3049,9 @@ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2981
3049
  if (!open) {
2982
3050
  return null;
2983
3051
  }
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)(
3052
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
3053
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "hl-cheat-scrim", onPointerDown: onClose }),
3054
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2987
3055
  "div",
2988
3056
  {
2989
3057
  ref: dialogRef,
@@ -2993,18 +3061,18 @@ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2993
3061
  "aria-label": "Keyboard shortcuts",
2994
3062
  tabIndex: -1,
2995
3063
  children: [
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" })
3064
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "hl-cheat-head", children: [
3065
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-cheat-title", children: "Shortcuts" }),
3066
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("button", { className: "hl-cheat-close", onClick: onClose, "aria-label": "Close", children: "\u2715" })
2999
3067
  ] }),
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) => (
3068
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "hl-cheat-grid", children: groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("section", { className: "hl-cheat-group", children: [
3069
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("h3", { className: "hl-cheat-group-title hl-mono", children: group.title }),
3070
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "hl-cheat-rows", children: group.rows.map((row) => (
3003
3071
  // A row is a binding: one action can have several (two ways to pan),
3004
3072
  // so neither the label nor the keys alone identify it.
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 })
3073
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("li", { className: "hl-cheat-row", children: [
3074
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-cheat-label", children: row.label }),
3075
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("kbd", { className: "hl-cheat-keys hl-mono", children: row.keys })
3008
3076
  ] }, `${row.keys}\0${row.label}`)
3009
3077
  )) })
3010
3078
  ] }, group.title)) })
@@ -3016,7 +3084,7 @@ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
3016
3084
 
3017
3085
  // src/overlays/Toast.tsx
3018
3086
  var import_react23 = require("react");
3019
- var import_jsx_runtime34 = require("react/jsx-runtime");
3087
+ var import_jsx_runtime36 = require("react/jsx-runtime");
3020
3088
  var ToastCtx = (0, import_react23.createContext)({ show: () => void 0 });
3021
3089
  function ToastProvider({ children, duration = 2200 }) {
3022
3090
  const [items, setItems] = (0, import_react23.useState)([]);
@@ -3033,9 +3101,9 @@ function ToastProvider({ children, duration = 2200 }) {
3033
3101
  [duration]
3034
3102
  );
3035
3103
  const api = (0, import_react23.useMemo)(() => ({ show }), [show]);
3036
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(ToastCtx.Provider, { value: api, children: [
3104
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(ToastCtx.Provider, { value: api, children: [
3037
3105
  children,
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)) })
3106
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "hl-toast-stack", "aria-live": "polite", children: items.map((t) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "hl-toast", children: t.message }, t.id)) })
3039
3107
  ] });
3040
3108
  }
3041
3109
  function useToast() {
@@ -3339,12 +3407,12 @@ function useFollowScroll() {
3339
3407
 
3340
3408
  // src/panels/RunLog.tsx
3341
3409
  var import_react27 = require("react");
3342
- var import_editor_react6 = require("@hlang-tech/editor-react");
3343
- var import_jsx_runtime35 = require("react/jsx-runtime");
3410
+ var import_editor_react7 = require("@hlang-tech/editor-react");
3411
+ var import_jsx_runtime37 = require("react/jsx-runtime");
3344
3412
  function RunLog({ core, onSelectNode, title = "Run log", leading }) {
3345
3413
  const trace = useRunTraceStore();
3346
3414
  useRunTraceVersion(trace);
3347
- const selection = (0, import_editor_react6.useSelection)(core);
3415
+ const selection = (0, import_editor_react7.useSelection)(core);
3348
3416
  const [onlySelected, setOnlySelected] = (0, import_react27.useState)(false);
3349
3417
  const { ref: bodyRef, onScroll, follow, setFollow } = useFollowScroll();
3350
3418
  const all = trace === null ? [] : trace.entries();
@@ -3362,55 +3430,54 @@ function RunLog({ core, onSelectNode, title = "Run log", leading }) {
3362
3430
  core.selection.selectNodes([nodeId], "replace");
3363
3431
  onSelectNode == null ? void 0 : onSelectNode(nodeId);
3364
3432
  };
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)) })
3433
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("section", { className: "hl-runlog", "aria-label": title, children: [
3434
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
3435
+ PanelHead,
3436
+ {
3437
+ leading: leading != null ? leading : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-panel-title", children: title }),
3438
+ meta: (entries.length === all.length ? `${String(all.length)} events` : `${String(entries.length)} of ${String(all.length)} events`) + (trace.dropped > 0 ? ` \xB7 ${String(trace.dropped)} earlier not kept` : ""),
3439
+ children: [
3440
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3441
+ PanelAction,
3442
+ {
3443
+ icon: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(FilterIcon, {}),
3444
+ label: onlySelected && selectedId !== void 0 ? `Only ${label(selectedId)}` : "Selected node",
3445
+ title: selectedId === void 0 ? "Select a node to filter by it" : `Only ${label(selectedId)}`,
3446
+ pressed: onlySelected,
3447
+ disabled: selectedId === void 0 && !onlySelected,
3448
+ onClick: () => {
3449
+ setOnlySelected((value) => !value);
3450
+ }
3451
+ }
3452
+ ),
3453
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3454
+ PanelAction,
3455
+ {
3456
+ icon: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(FollowIcon, {}),
3457
+ label: "Follow",
3458
+ title: "Follow the newest event",
3459
+ pressed: follow,
3460
+ onClick: () => {
3461
+ setFollow(!follow);
3462
+ }
3463
+ }
3464
+ ),
3465
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3466
+ PanelAction,
3467
+ {
3468
+ icon: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ClearIcon, {}),
3469
+ label: "Clear",
3470
+ title: "Clear the log",
3471
+ disabled: all.length === 0,
3472
+ onClick: () => {
3473
+ trace.clear();
3474
+ }
3475
+ }
3476
+ )
3477
+ ]
3478
+ }
3479
+ ),
3480
+ entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.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_runtime37.jsx)("ol", { className: "hl-runlog-body hl-mono", ref: bodyRef, onScroll, children: entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Row, { entry, label, onChoose: choose }, entry.seq)) })
3414
3481
  ] });
3415
3482
  }
3416
3483
  function Row({
@@ -3420,32 +3487,32 @@ function Row({
3420
3487
  }) {
3421
3488
  switch (entry.kind) {
3422
3489
  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) })
3490
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(Event, { className: "is-error", at: entry.at, onChoose: () => onChoose(entry.nodeId), ms: "", children: [
3491
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(NodePort, { node: label(entry.nodeId), port: "error" }),
3492
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-value", children: previewValue(entry.error) })
3426
3493
  ] });
3427
3494
  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" })
3495
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(Event, { className: "is-end", at: entry.at, onChoose: () => onChoose(entry.nodeId), ms: "", children: [
3496
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(NodePort, { node: label(entry.nodeId), port: entry.portId }),
3497
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-value", children: "ended" })
3431
3498
  ] });
3432
3499
  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" })
3500
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(Event, { className: "is-end", at: entry.at, onChoose: () => onChoose(entry.nodeId), ms: "", children: [
3501
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(NodePort, { node: label(entry.nodeId), port: "" }),
3502
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-value", children: "finished" })
3436
3503
  ] });
3437
3504
  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: [
3505
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(Event, { className: "is-dropped", at: entry.at, onChoose: () => onChoose(entry.targetNodeId), ms: "", children: [
3506
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Route, { entry, label }),
3507
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "hl-runlog-value", children: [
3441
3508
  "dropped ",
3442
3509
  previewValue(entry.message)
3443
3510
  ] })
3444
3511
  ] });
3445
3512
  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) })
3513
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(Event, { className: "", at: entry.at, onChoose: () => onChoose(entry.targetNodeId), ms: formatDuration(entry.duration), children: [
3514
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Route, { entry, label }),
3515
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-value", children: previewValue(entry.message) })
3449
3516
  ] });
3450
3517
  }
3451
3518
  }
@@ -3456,28 +3523,28 @@ function Event({
3456
3523
  onChoose,
3457
3524
  children
3458
3525
  }) {
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) }),
3526
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { className: `hl-runlog-row ${className2}`.trim(), children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("button", { type: "button", className: "hl-runlog-hit", onClick: onChoose, children: [
3527
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-at", children: formatElapsed(at) }),
3461
3528
  children,
3462
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "hl-runlog-ms", children: ms })
3529
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-ms", children: ms })
3463
3530
  ] }) });
3464
3531
  }
3465
3532
  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 })
3533
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "hl-runlog-route", children: [
3534
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-node", children: node }),
3535
+ port === "" ? null : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-port", children: port })
3469
3536
  ] });
3470
3537
  }
3471
3538
  function Route({
3472
3539
  entry,
3473
3540
  label
3474
3541
  }) {
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 })
3542
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "hl-runlog-route", children: [
3543
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-node", children: label(entry.sourceNodeId) }),
3544
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-port", children: entry.sourcePortId }),
3545
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-arrow", "aria-label": "to", children: "\u2192" }),
3546
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-node", children: label(entry.targetNodeId) }),
3547
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "hl-runlog-port", children: entry.targetPortId })
3481
3548
  ] });
3482
3549
  }
3483
3550
  // Annotate the CommonJS export names for ESM import in node:
@@ -3487,6 +3554,7 @@ function Route({
3487
3554
  CanvasChrome,
3488
3555
  CanvasDock,
3489
3556
  Cheatsheet,
3557
+ ClearIcon,
3490
3558
  ConnectionInspector,
3491
3559
  DEFAULT_EDGE_CAPACITY,
3492
3560
  DEFAULT_MIN_CANVAS_HEIGHT,
@@ -3496,7 +3564,9 @@ function Route({
3496
3564
  EmptyState,
3497
3565
  FOLLOW_SLACK_PX,
3498
3566
  FOOTER_KEY_STEP,
3567
+ FilterIcon,
3499
3568
  FlowEdge,
3569
+ FollowIcon,
3500
3570
  FooterResizer,
3501
3571
  HlangNode,
3502
3572
  InspectorPanel,
@@ -3513,6 +3583,8 @@ function Route({
3513
3583
  NodeSearch,
3514
3584
  OutlineTree,
3515
3585
  PREVIEW_LENGTH,
3586
+ PanelAction,
3587
+ PanelHead,
3516
3588
  ParamForm,
3517
3589
  PillNodeChrome,
3518
3590
  PortRow,
@@ -3520,6 +3592,7 @@ function Route({
3520
3592
  PreviewPanel,
3521
3593
  RUN_LOG_CAPACITY,
3522
3594
  RailTabs,
3595
+ ReadOnlyBadge,
3523
3596
  RunButton,
3524
3597
  RunLog,
3525
3598
  RunTrace,