@hlang-tech/editor-ui 0.4.0 → 0.5.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.mjs CHANGED
@@ -550,6 +550,15 @@ function ErrorIcon({ size = 13 }) {
550
550
  /* @__PURE__ */ jsx8("path", { d: "M12 16v.5" }, "d")
551
551
  ]);
552
552
  }
553
+ function FilterIcon({ size = 14 }) {
554
+ return svg(size, [/* @__PURE__ */ jsx8("path", { d: "M4 5h16l-6 7.5V19l-4-2v-4.5z" }, "f")]);
555
+ }
556
+ function FollowIcon({ size = 14 }) {
557
+ return svg(size, [/* @__PURE__ */ jsx8("path", { d: "M12 4v11M7 10.5l5 5 5-5" }, "a"), /* @__PURE__ */ jsx8("path", { d: "M5 20h14" }, "l")]);
558
+ }
559
+ function ClearIcon({ size = 14 }) {
560
+ return svg(size, [/* @__PURE__ */ jsx8("circle", { cx: "12", cy: "12", r: "8" }, "c"), /* @__PURE__ */ jsx8("path", { d: "M6.5 17.5l11-11" }, "s")]);
561
+ }
553
562
 
554
563
  // src/panels/DiagnosticBadge.tsx
555
564
  import { jsx as jsx9 } from "react/jsx-runtime";
@@ -569,8 +578,7 @@ function DiagnosticBadge({ diagnostic, variant, size = 12 }) {
569
578
  import { createContext as createContext2, useContext as useContext2 } from "react";
570
579
  import { jsx as jsx10 } from "react/jsx-runtime";
571
580
  var NoopInteraction = {
572
- diagnosticFor: () => void 0,
573
- rename: () => void 0
581
+ diagnosticFor: () => void 0
574
582
  };
575
583
  var NodeInteractionCtx = createContext2(NoopInteraction);
576
584
  function NodeInteractionProvider({
@@ -602,10 +610,11 @@ function PillNodeChrome({ node, selected }) {
602
610
  });
603
611
  }
604
612
  }, [editing, node.name]);
613
+ const { rename } = interaction;
605
614
  const commit = useCallback(() => {
606
615
  setEditing(false);
607
- interaction.rename(node.id, draft);
608
- }, [interaction, node.id, draft]);
616
+ rename == null ? void 0 : rename(node.id, draft);
617
+ }, [rename, node.id, draft]);
609
618
  const onKeyDown = useCallback(
610
619
  (e) => {
611
620
  if (e.key === "Enter") {
@@ -620,6 +629,9 @@ function PillNodeChrome({ node, selected }) {
620
629
  );
621
630
  const lastClick = useRef(0);
622
631
  const onNameClick = useCallback(() => {
632
+ if (rename === void 0) {
633
+ return;
634
+ }
623
635
  const now = performance.now();
624
636
  if (now - lastClick.current < DOUBLE_CLICK_MS) {
625
637
  setEditing(true);
@@ -627,7 +639,7 @@ function PillNodeChrome({ node, selected }) {
627
639
  } else {
628
640
  lastClick.current = now;
629
641
  }
630
- }, []);
642
+ }, [rename]);
631
643
  const inputs = node.ports.filter((p) => p.direction === "I" && !p.isError);
632
644
  const outputs = node.ports.filter((p) => p.direction === "O" && !p.isError);
633
645
  const className2 = [
@@ -662,7 +674,7 @@ function PillNodeChrome({ node, selected }) {
662
674
  onClick: () => {
663
675
  onNameClick();
664
676
  },
665
- title: "Double-click to rename",
677
+ title: rename === void 0 ? void 0 : "Double-click to rename",
666
678
  children: node.name
667
679
  }
668
680
  ),
@@ -1199,12 +1211,21 @@ function ToolbarGhostButton({
1199
1211
  return /* @__PURE__ */ jsx16("button", { type: "button", className: "hl-toolbar-btn", onClick, title, children });
1200
1212
  }
1201
1213
 
1202
- // src/toolbar/ToolCluster.tsx
1214
+ // src/toolbar/ReadOnlyBadge.tsx
1203
1215
  import { jsx as jsx17, jsxs as jsxs8 } from "react/jsx-runtime";
1216
+ function ReadOnlyBadge({ label = "View only", title }) {
1217
+ return /* @__PURE__ */ jsxs8("span", { className: "hl-readonly-badge", title: title != null ? title : label, children: [
1218
+ /* @__PURE__ */ jsx17(LockIcon, { size: 12 }),
1219
+ /* @__PURE__ */ jsx17("span", { className: "hl-readonly-badge-label", children: label })
1220
+ ] });
1221
+ }
1222
+
1223
+ // src/toolbar/ToolCluster.tsx
1224
+ import { jsx as jsx18, jsxs as jsxs9 } from "react/jsx-runtime";
1204
1225
  function ToolCluster({ items }) {
1205
- return /* @__PURE__ */ jsx17("div", { className: "hl-toolcluster", role: "toolbar", "aria-label": "Canvas tools", children: items.map((item) => /* @__PURE__ */ jsxs8("span", { className: "hl-toolcluster-slot", children: [
1206
- item.separatorBefore === true && /* @__PURE__ */ jsx17("span", { className: "hl-toolcluster-sep", "aria-hidden": true }),
1207
- /* @__PURE__ */ jsx17(
1226
+ return /* @__PURE__ */ jsx18("div", { className: "hl-toolcluster", role: "toolbar", "aria-label": "Canvas tools", children: items.map((item) => /* @__PURE__ */ jsxs9("span", { className: "hl-toolcluster-slot", children: [
1227
+ item.separatorBefore === true && /* @__PURE__ */ jsx18("span", { className: "hl-toolcluster-sep", "aria-hidden": true }),
1228
+ /* @__PURE__ */ jsx18(
1208
1229
  "button",
1209
1230
  {
1210
1231
  type: "button",
@@ -1222,30 +1243,30 @@ function ToolCluster({ items }) {
1222
1243
 
1223
1244
  // src/canvas/CanvasChrome.tsx
1224
1245
  import { Background, BackgroundVariant } from "@xyflow/react";
1225
- import { Fragment as Fragment3, jsx as jsx18 } from "react/jsx-runtime";
1246
+ import { Fragment as Fragment3, jsx as jsx19 } from "react/jsx-runtime";
1226
1247
  function CanvasChrome({ background = true }) {
1227
- return /* @__PURE__ */ jsx18(Fragment3, { children: background && /* @__PURE__ */ jsx18(Background, { variant: BackgroundVariant.Dots, gap: 26, size: 1, color: "#242830" }) });
1248
+ return /* @__PURE__ */ jsx19(Fragment3, { children: background && /* @__PURE__ */ jsx19(Background, { variant: BackgroundVariant.Dots, gap: 26, size: 1, color: "#242830" }) });
1228
1249
  }
1229
1250
 
1230
1251
  // src/canvas/CanvasDock.tsx
1231
- import { jsx as jsx19, jsxs as jsxs9 } from "react/jsx-runtime";
1252
+ import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs10 } from "react/jsx-runtime";
1232
1253
  function CursorIcon() {
1233
- return /* @__PURE__ */ jsx19("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__ */ jsx19("path", { d: "M5 3l6 16 2-6 6-2z" }) });
1254
+ return /* @__PURE__ */ jsx20("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__ */ jsx20("path", { d: "M5 3l6 16 2-6 6-2z" }) });
1234
1255
  }
1235
1256
  function HandIcon() {
1236
- return /* @__PURE__ */ jsx19("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__ */ jsx19("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" }) });
1257
+ return /* @__PURE__ */ jsx20("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__ */ jsx20("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" }) });
1237
1258
  }
1238
1259
  function PlusIcon() {
1239
- return /* @__PURE__ */ jsx19("svg", { width: 17, height: 17, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx19("path", { d: "M12 5v14M5 12h14" }) });
1260
+ return /* @__PURE__ */ jsx20("svg", { width: 17, height: 17, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx20("path", { d: "M12 5v14M5 12h14" }) });
1240
1261
  }
1241
1262
  function FitIcon() {
1242
- return /* @__PURE__ */ jsx19("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__ */ jsx19("path", { d: "M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5" }) });
1263
+ return /* @__PURE__ */ jsx20("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__ */ jsx20("path", { d: "M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5" }) });
1243
1264
  }
1244
1265
  function UndoIcon() {
1245
- return /* @__PURE__ */ jsx19("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__ */ jsx19("path", { d: "M9 7L4 12l5 5M4 12h11a5 5 0 010 10h-1" }) });
1266
+ return /* @__PURE__ */ jsx20("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__ */ jsx20("path", { d: "M9 7L4 12l5 5M4 12h11a5 5 0 010 10h-1" }) });
1246
1267
  }
1247
1268
  function RedoIcon() {
1248
- return /* @__PURE__ */ jsx19("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__ */ jsx19("path", { d: "M15 7l5 5-5 5M20 12H9a5 5 0 000 10h1" }) });
1269
+ return /* @__PURE__ */ jsx20("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__ */ jsx20("path", { d: "M15 7l5 5-5 5M20 12H9a5 5 0 000 10h1" }) });
1249
1270
  }
1250
1271
  function CanvasDock({
1251
1272
  zoom,
@@ -1258,76 +1279,79 @@ function CanvasDock({
1258
1279
  onUndo,
1259
1280
  onRedo,
1260
1281
  canUndo,
1261
- canRedo
1282
+ canRedo,
1283
+ readOnly = false
1262
1284
  }) {
1263
1285
  const pct = `${String(Math.round(zoom * 100))}%`;
1264
- return /* @__PURE__ */ jsxs9("div", { className: "hl-dock", role: "toolbar", "aria-label": "Canvas tools", children: [
1265
- /* @__PURE__ */ jsx19(
1266
- "button",
1267
- {
1268
- className: "hl-dock-btn",
1269
- onClick: onUndo,
1270
- disabled: !canUndo,
1271
- title: "Undo (\u2318Z)",
1272
- "aria-label": "Undo",
1273
- children: /* @__PURE__ */ jsx19(UndoIcon, {})
1274
- }
1275
- ),
1276
- /* @__PURE__ */ jsx19(
1277
- "button",
1278
- {
1279
- className: "hl-dock-btn",
1280
- onClick: onRedo,
1281
- disabled: !canRedo,
1282
- title: "Redo (\u2318\u21E7Z)",
1283
- "aria-label": "Redo",
1284
- children: /* @__PURE__ */ jsx19(RedoIcon, {})
1285
- }
1286
- ),
1287
- /* @__PURE__ */ jsx19("span", { className: "hl-dock-sep", "aria-hidden": true }),
1288
- /* @__PURE__ */ jsx19(
1289
- "button",
1290
- {
1291
- className: "hl-dock-btn" + (mode === "select" ? " is-active" : ""),
1292
- onClick: () => onModeChange("select"),
1293
- title: "Select (V)",
1294
- "aria-pressed": mode === "select",
1295
- children: /* @__PURE__ */ jsx19(CursorIcon, {})
1296
- }
1297
- ),
1298
- /* @__PURE__ */ jsx19(
1299
- "button",
1300
- {
1301
- className: "hl-dock-btn" + (mode === "pan" ? " is-active" : ""),
1302
- onClick: () => onModeChange("pan"),
1303
- title: "Pan (Space)",
1304
- "aria-pressed": mode === "pan",
1305
- children: /* @__PURE__ */ jsx19(HandIcon, {})
1306
- }
1307
- ),
1308
- /* @__PURE__ */ jsx19("button", { className: "hl-dock-btn hl-dock-add", onClick: onAdd, title: "Add node", children: /* @__PURE__ */ jsx19(PlusIcon, {}) }),
1309
- /* @__PURE__ */ jsx19("span", { className: "hl-dock-sep", "aria-hidden": true }),
1310
- /* @__PURE__ */ jsx19("button", { className: "hl-dock-btn", onClick: onZoomOut, title: "Zoom out", "aria-label": "Zoom out", children: /* @__PURE__ */ jsx19("span", { className: "hl-dock-glyph", children: "\u2212" }) }),
1311
- /* @__PURE__ */ jsx19("button", { className: "hl-dock-zoom hl-mono", onClick: onFitView, title: "Fit view", children: pct }),
1312
- /* @__PURE__ */ jsx19("button", { className: "hl-dock-btn", onClick: onZoomIn, title: "Zoom in", "aria-label": "Zoom in", children: /* @__PURE__ */ jsx19("span", { className: "hl-dock-glyph", children: "+" }) }),
1313
- /* @__PURE__ */ jsx19("button", { className: "hl-dock-btn hl-dock-fit", onClick: onFitView, title: "Fit view", "aria-label": "Fit view", children: /* @__PURE__ */ jsx19(FitIcon, {}) })
1286
+ return /* @__PURE__ */ jsxs10("div", { className: "hl-dock", role: "toolbar", "aria-label": "Canvas tools", children: [
1287
+ readOnly ? null : /* @__PURE__ */ jsxs10(Fragment4, { children: [
1288
+ /* @__PURE__ */ jsx20(
1289
+ "button",
1290
+ {
1291
+ className: "hl-dock-btn",
1292
+ onClick: onUndo,
1293
+ disabled: !canUndo,
1294
+ title: "Undo (\u2318Z)",
1295
+ "aria-label": "Undo",
1296
+ children: /* @__PURE__ */ jsx20(UndoIcon, {})
1297
+ }
1298
+ ),
1299
+ /* @__PURE__ */ jsx20(
1300
+ "button",
1301
+ {
1302
+ className: "hl-dock-btn",
1303
+ onClick: onRedo,
1304
+ disabled: !canRedo,
1305
+ title: "Redo (\u2318\u21E7Z)",
1306
+ "aria-label": "Redo",
1307
+ children: /* @__PURE__ */ jsx20(RedoIcon, {})
1308
+ }
1309
+ ),
1310
+ /* @__PURE__ */ jsx20("span", { className: "hl-dock-sep", "aria-hidden": true }),
1311
+ /* @__PURE__ */ jsx20(
1312
+ "button",
1313
+ {
1314
+ className: "hl-dock-btn" + (mode === "select" ? " is-active" : ""),
1315
+ onClick: () => onModeChange("select"),
1316
+ title: "Select (V)",
1317
+ "aria-pressed": mode === "select",
1318
+ children: /* @__PURE__ */ jsx20(CursorIcon, {})
1319
+ }
1320
+ ),
1321
+ /* @__PURE__ */ jsx20(
1322
+ "button",
1323
+ {
1324
+ className: "hl-dock-btn" + (mode === "pan" ? " is-active" : ""),
1325
+ onClick: () => onModeChange("pan"),
1326
+ title: "Pan (Space)",
1327
+ "aria-pressed": mode === "pan",
1328
+ children: /* @__PURE__ */ jsx20(HandIcon, {})
1329
+ }
1330
+ ),
1331
+ /* @__PURE__ */ jsx20("button", { className: "hl-dock-btn hl-dock-add", onClick: onAdd, title: "Add node", children: /* @__PURE__ */ jsx20(PlusIcon, {}) }),
1332
+ /* @__PURE__ */ jsx20("span", { className: "hl-dock-sep", "aria-hidden": true })
1333
+ ] }),
1334
+ /* @__PURE__ */ jsx20("button", { className: "hl-dock-btn", onClick: onZoomOut, title: "Zoom out", "aria-label": "Zoom out", children: /* @__PURE__ */ jsx20("span", { className: "hl-dock-glyph", children: "\u2212" }) }),
1335
+ /* @__PURE__ */ jsx20("button", { className: "hl-dock-zoom hl-mono", onClick: onFitView, title: "Fit view", children: pct }),
1336
+ /* @__PURE__ */ jsx20("button", { className: "hl-dock-btn", onClick: onZoomIn, title: "Zoom in", "aria-label": "Zoom in", children: /* @__PURE__ */ jsx20("span", { className: "hl-dock-glyph", children: "+" }) }),
1337
+ /* @__PURE__ */ jsx20("button", { className: "hl-dock-btn hl-dock-fit", onClick: onFitView, title: "Fit view", "aria-label": "Fit view", children: /* @__PURE__ */ jsx20(FitIcon, {}) })
1314
1338
  ] });
1315
1339
  }
1316
1340
 
1317
1341
  // src/canvas/EmptyState.tsx
1318
- import { jsx as jsx20, jsxs as jsxs10 } from "react/jsx-runtime";
1342
+ import { jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
1319
1343
  function EmptyState({
1320
1344
  title = "An empty canvas.",
1321
1345
  hint = "add a source \xB7 wire it to a sink \xB7 run"
1322
1346
  }) {
1323
- return /* @__PURE__ */ jsxs10("div", { className: "hl-empty", "aria-hidden": "true", children: [
1324
- /* @__PURE__ */ jsx20("p", { className: "hl-empty-title", children: title }),
1325
- /* @__PURE__ */ jsx20("p", { className: "hl-empty-hint", children: hint })
1347
+ return /* @__PURE__ */ jsxs11("div", { className: "hl-empty", "aria-hidden": "true", children: [
1348
+ /* @__PURE__ */ jsx21("p", { className: "hl-empty-title", children: title }),
1349
+ /* @__PURE__ */ jsx21("p", { className: "hl-empty-hint", children: hint })
1326
1350
  ] });
1327
1351
  }
1328
1352
 
1329
1353
  // src/panels/InspectorPanel.tsx
1330
- import { useSelection } from "@hlang-tech/editor-react";
1354
+ import { useReadOnly as useReadOnly2, useSelection } from "@hlang-tech/editor-react";
1331
1355
 
1332
1356
  // src/schema/param-field.ts
1333
1357
  function humanizeKey(key) {
@@ -1412,6 +1436,9 @@ function useNodeParams(core, nodeId) {
1412
1436
  );
1413
1437
  }
1414
1438
 
1439
+ // src/panels/ConnectionInspector.tsx
1440
+ import { useReadOnly } from "@hlang-tech/editor-react";
1441
+
1415
1442
  // src/hooks/useEdgeBounds.ts
1416
1443
  import { useCallback as useCallback4, useSyncExternalStore as useSyncExternalStore3 } from "react";
1417
1444
  import { SetEdgeBoundsCommand } from "@hlang-tech/editor-core";
@@ -1453,17 +1480,17 @@ function useEdgeBounds(core, edgeId) {
1453
1480
 
1454
1481
  // src/panels/ParamForm.tsx
1455
1482
  import { useState as useState3 } from "react";
1456
- import { jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
1457
- function ParamForm({ fields, onCommit }) {
1458
- return /* @__PURE__ */ jsx21("div", { className: "hl-form", children: fields.map((field) => /* @__PURE__ */ jsx21(FieldRow, { field, onCommit }, field.key)) });
1483
+ import { jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
1484
+ function ParamForm({ fields, onCommit, disabled = false }) {
1485
+ return /* @__PURE__ */ jsx22("fieldset", { className: "hl-form", disabled, children: fields.map((field) => /* @__PURE__ */ jsx22(FieldRow, { field, onCommit }, field.key)) });
1459
1486
  }
1460
1487
  function FieldRow({
1461
1488
  field,
1462
1489
  onCommit
1463
1490
  }) {
1464
- return /* @__PURE__ */ jsxs11("label", { className: "hl-field", children: [
1465
- /* @__PURE__ */ jsx21("span", { className: "hl-field-label", children: field.label }),
1466
- /* @__PURE__ */ jsx21(FieldControl, { field, onCommit })
1491
+ return /* @__PURE__ */ jsxs12("label", { className: "hl-field", children: [
1492
+ /* @__PURE__ */ jsx22("span", { className: "hl-field-label", children: field.label }),
1493
+ /* @__PURE__ */ jsx22(FieldControl, { field, onCommit })
1467
1494
  ] });
1468
1495
  }
1469
1496
  function FieldControl({
@@ -1472,7 +1499,7 @@ function FieldControl({
1472
1499
  }) {
1473
1500
  switch (field.type) {
1474
1501
  case "toggle":
1475
- return /* @__PURE__ */ jsx21(
1502
+ return /* @__PURE__ */ jsx22(
1476
1503
  "button",
1477
1504
  {
1478
1505
  type: "button",
@@ -1481,17 +1508,17 @@ function FieldControl({
1481
1508
  className: `hl-toggle${field.value ? " is-on" : ""}`,
1482
1509
  disabled: field.disabled,
1483
1510
  onClick: () => onCommit(field.key, !field.value),
1484
- children: /* @__PURE__ */ jsx21("span", { className: "hl-toggle-knob" })
1511
+ children: /* @__PURE__ */ jsx22("span", { className: "hl-toggle-knob" })
1485
1512
  }
1486
1513
  );
1487
1514
  case "number":
1488
- return /* @__PURE__ */ jsx21(NumberControl, { field, onCommit });
1515
+ return /* @__PURE__ */ jsx22(NumberControl, { field, onCommit });
1489
1516
  case "textarea":
1490
- return /* @__PURE__ */ jsx21(TextControl, { field, onCommit, multiline: true });
1517
+ return /* @__PURE__ */ jsx22(TextControl, { field, onCommit, multiline: true });
1491
1518
  case "text":
1492
- return /* @__PURE__ */ jsx21(TextControl, { field, onCommit });
1519
+ return /* @__PURE__ */ jsx22(TextControl, { field, onCommit });
1493
1520
  case "custom":
1494
- return /* @__PURE__ */ jsx21("span", { className: "hl-field-custom hl-mono", title: field.fieldType, children: describeCustom(field.value) });
1521
+ return /* @__PURE__ */ jsx22("span", { className: "hl-field-custom hl-mono", title: field.fieldType, children: describeCustom(field.value) });
1495
1522
  }
1496
1523
  }
1497
1524
  function TextControl({
@@ -1513,7 +1540,7 @@ function TextControl({
1513
1540
  onBlur: commit
1514
1541
  };
1515
1542
  if (multiline) {
1516
- return /* @__PURE__ */ jsx21(
1543
+ return /* @__PURE__ */ jsx22(
1517
1544
  "textarea",
1518
1545
  {
1519
1546
  ...shared,
@@ -1522,7 +1549,7 @@ function TextControl({
1522
1549
  }
1523
1550
  );
1524
1551
  }
1525
- return /* @__PURE__ */ jsx21(
1552
+ return /* @__PURE__ */ jsx22(
1526
1553
  "input",
1527
1554
  {
1528
1555
  ...shared,
@@ -1547,7 +1574,7 @@ function NumberControl({
1547
1574
  onCommit(field.key, parsed);
1548
1575
  }
1549
1576
  };
1550
- return /* @__PURE__ */ jsx21(
1577
+ return /* @__PURE__ */ jsx22(
1551
1578
  "input",
1552
1579
  {
1553
1580
  className: "hl-input hl-mono",
@@ -1579,9 +1606,10 @@ function describeCustom(value) {
1579
1606
  }
1580
1607
 
1581
1608
  // src/panels/ConnectionInspector.tsx
1582
- import { jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
1609
+ import { jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
1583
1610
  function ConnectionInspector({ core, edgeId }) {
1584
1611
  const { edge, setBounds } = useEdgeBounds(core, edgeId);
1612
+ const readOnly = useReadOnly(core);
1585
1613
  if (edge === void 0) {
1586
1614
  return null;
1587
1615
  }
@@ -1589,25 +1617,26 @@ function ConnectionInspector({ core, edgeId }) {
1589
1617
  var _a, _b;
1590
1618
  return `${(_b = (_a = core.model.getNode(endpoint.nodeId)) == null ? void 0 : _a.name) != null ? _b : endpoint.nodeId}.${endpoint.portId}`;
1591
1619
  };
1592
- return /* @__PURE__ */ jsxs12("div", { className: "hl-inspector", children: [
1593
- /* @__PURE__ */ jsxs12("header", { className: "hl-inspector-head", children: [
1594
- /* @__PURE__ */ jsx22("span", { className: "hl-inspector-kind hl-connection", children: "\u2192" }),
1595
- /* @__PURE__ */ jsxs12("div", { className: "hl-inspector-titles", children: [
1596
- /* @__PURE__ */ jsx22("span", { className: "hl-inspector-name", children: "Connection" }),
1597
- /* @__PURE__ */ jsxs12("span", { className: "hl-inspector-role hl-mono", children: [
1620
+ return /* @__PURE__ */ jsxs13("div", { className: "hl-inspector", children: [
1621
+ /* @__PURE__ */ jsxs13("header", { className: "hl-inspector-head", children: [
1622
+ /* @__PURE__ */ jsx23("span", { className: "hl-inspector-kind hl-connection", children: "\u2192" }),
1623
+ /* @__PURE__ */ jsxs13("div", { className: "hl-inspector-titles", children: [
1624
+ /* @__PURE__ */ jsx23("span", { className: "hl-inspector-name", children: "Connection" }),
1625
+ /* @__PURE__ */ jsxs13("span", { className: "hl-inspector-role hl-mono", children: [
1598
1626
  end(edge.source),
1599
1627
  " \u2192 ",
1600
1628
  end(edge.target)
1601
1629
  ] })
1602
1630
  ] })
1603
1631
  ] }),
1604
- /* @__PURE__ */ jsxs12("section", { className: "hl-inspector-section", children: [
1605
- /* @__PURE__ */ jsx22("h3", { className: "hl-inspector-section-title", children: "Back-pressure" }),
1606
- /* @__PURE__ */ jsx22("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." }),
1607
- /* @__PURE__ */ jsx22(
1632
+ /* @__PURE__ */ jsxs13("section", { className: "hl-inspector-section", children: [
1633
+ /* @__PURE__ */ jsx23("h3", { className: "hl-inspector-section-title", children: "Back-pressure" }),
1634
+ /* @__PURE__ */ jsx23("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." }),
1635
+ /* @__PURE__ */ jsx23(
1608
1636
  ParamForm,
1609
1637
  {
1610
1638
  fields: boundsFields(edge),
1639
+ disabled: readOnly,
1611
1640
  onCommit: (key, value) => {
1612
1641
  setBounds(nextBounds(edge, key, value));
1613
1642
  }
@@ -1733,10 +1762,10 @@ function previewValue(value, maxLength = PREVIEW_LENGTH) {
1733
1762
 
1734
1763
  // src/live/RunTraceContext.tsx
1735
1764
  import { createContext as createContext3, useContext as useContext3, useSyncExternalStore as useSyncExternalStore4 } from "react";
1736
- import { jsx as jsx23 } from "react/jsx-runtime";
1765
+ import { jsx as jsx24 } from "react/jsx-runtime";
1737
1766
  var RunTraceContext = createContext3(null);
1738
1767
  function RunTraceProvider({ trace, children }) {
1739
- return /* @__PURE__ */ jsx23(RunTraceContext.Provider, { value: trace, children });
1768
+ return /* @__PURE__ */ jsx24(RunTraceContext.Provider, { value: trace, children });
1740
1769
  }
1741
1770
  function useRunTraceStore() {
1742
1771
  return useContext3(RunTraceContext);
@@ -1750,7 +1779,7 @@ function useRunTraceVersion(trace) {
1750
1779
  }
1751
1780
 
1752
1781
  // src/panels/NodeRunStats.tsx
1753
- import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
1782
+ import { jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
1754
1783
  function NodeRunStats({ nodeId }) {
1755
1784
  const trace = useRunTraceStore();
1756
1785
  useRunTraceVersion(trace);
@@ -1758,105 +1787,107 @@ function NodeRunStats({ nodeId }) {
1758
1787
  return null;
1759
1788
  }
1760
1789
  const stats = trace.statsOf(nodeId);
1761
- return /* @__PURE__ */ jsxs13("section", { className: "hl-inspector-section", children: [
1762
- /* @__PURE__ */ jsx24("h3", { className: "hl-inspector-section-title", children: "Run" }),
1763
- stats === void 0 ? /* @__PURE__ */ jsx24("p", { className: "hl-inspector-hint hl-runstats-hint", children: "No activity in this run." }) : /* @__PURE__ */ jsxs13("dl", { className: "hl-runstats", children: [
1764
- /* @__PURE__ */ jsxs13("div", { className: "hl-runstats-cell", children: [
1765
- /* @__PURE__ */ jsx24("dt", { children: "In" }),
1766
- /* @__PURE__ */ jsx24("dd", { className: "hl-mono", children: stats.received })
1790
+ return /* @__PURE__ */ jsxs14("section", { className: "hl-inspector-section", children: [
1791
+ /* @__PURE__ */ jsx25("h3", { className: "hl-inspector-section-title", children: "Run" }),
1792
+ stats === void 0 ? /* @__PURE__ */ jsx25("p", { className: "hl-inspector-hint hl-runstats-hint", children: "No activity in this run." }) : /* @__PURE__ */ jsxs14("dl", { className: "hl-runstats", children: [
1793
+ /* @__PURE__ */ jsxs14("div", { className: "hl-runstats-cell", children: [
1794
+ /* @__PURE__ */ jsx25("dt", { children: "In" }),
1795
+ /* @__PURE__ */ jsx25("dd", { className: "hl-mono", children: stats.received })
1767
1796
  ] }),
1768
- /* @__PURE__ */ jsxs13("div", { className: "hl-runstats-cell", children: [
1769
- /* @__PURE__ */ jsx24("dt", { children: "Out" }),
1770
- /* @__PURE__ */ jsx24("dd", { className: "hl-mono", children: stats.sent })
1797
+ /* @__PURE__ */ jsxs14("div", { className: "hl-runstats-cell", children: [
1798
+ /* @__PURE__ */ jsx25("dt", { children: "Out" }),
1799
+ /* @__PURE__ */ jsx25("dd", { className: "hl-mono", children: stats.sent })
1771
1800
  ] }),
1772
- /* @__PURE__ */ jsxs13("div", { className: `hl-runstats-cell${stats.errors > 0 ? " is-error" : ""}`, children: [
1773
- /* @__PURE__ */ jsx24("dt", { children: "Errors" }),
1774
- /* @__PURE__ */ jsx24("dd", { className: "hl-mono", children: stats.errors })
1801
+ /* @__PURE__ */ jsxs14("div", { className: `hl-runstats-cell${stats.errors > 0 ? " is-error" : ""}`, children: [
1802
+ /* @__PURE__ */ jsx25("dt", { children: "Errors" }),
1803
+ /* @__PURE__ */ jsx25("dd", { className: "hl-mono", children: stats.errors })
1775
1804
  ] }),
1776
- /* @__PURE__ */ jsxs13("div", { className: "hl-runstats-cell", children: [
1777
- /* @__PURE__ */ jsx24("dt", { children: "Avg" }),
1778
- /* @__PURE__ */ jsx24("dd", { className: "hl-mono", children: stats.received === 0 ? "\u2014" : formatDuration(stats.totalMs / stats.received) })
1805
+ /* @__PURE__ */ jsxs14("div", { className: "hl-runstats-cell", children: [
1806
+ /* @__PURE__ */ jsx25("dt", { children: "Avg" }),
1807
+ /* @__PURE__ */ jsx25("dd", { className: "hl-mono", children: stats.received === 0 ? "\u2014" : formatDuration(stats.totalMs / stats.received) })
1779
1808
  ] }),
1780
- /* @__PURE__ */ jsxs13("div", { className: "hl-runstats-cell", children: [
1781
- /* @__PURE__ */ jsx24("dt", { children: "Max" }),
1782
- /* @__PURE__ */ jsx24("dd", { className: "hl-mono", children: stats.received === 0 ? "\u2014" : formatDuration(stats.maxMs) })
1809
+ /* @__PURE__ */ jsxs14("div", { className: "hl-runstats-cell", children: [
1810
+ /* @__PURE__ */ jsx25("dt", { children: "Max" }),
1811
+ /* @__PURE__ */ jsx25("dd", { className: "hl-mono", children: stats.received === 0 ? "\u2014" : formatDuration(stats.maxMs) })
1783
1812
  ] }),
1784
- stats.dropped > 0 && /* @__PURE__ */ jsxs13("div", { className: "hl-runstats-cell is-warning", children: [
1785
- /* @__PURE__ */ jsx24("dt", { children: "Dropped" }),
1786
- /* @__PURE__ */ jsx24("dd", { className: "hl-mono", children: stats.dropped })
1813
+ stats.dropped > 0 && /* @__PURE__ */ jsxs14("div", { className: "hl-runstats-cell is-warning", children: [
1814
+ /* @__PURE__ */ jsx25("dt", { children: "Dropped" }),
1815
+ /* @__PURE__ */ jsx25("dd", { className: "hl-mono", children: stats.dropped })
1787
1816
  ] }),
1788
- stats.finished && /* @__PURE__ */ jsxs13("div", { className: "hl-runstats-cell is-done", children: [
1789
- /* @__PURE__ */ jsx24("dt", { children: "State" }),
1790
- /* @__PURE__ */ jsx24("dd", { children: "Finished" })
1817
+ stats.finished && /* @__PURE__ */ jsxs14("div", { className: "hl-runstats-cell is-done", children: [
1818
+ /* @__PURE__ */ jsx25("dt", { children: "State" }),
1819
+ /* @__PURE__ */ jsx25("dd", { children: "Finished" })
1791
1820
  ] }),
1792
- stats.received > 0 && /* @__PURE__ */ jsxs13("div", { className: "hl-runstats-cell is-wide", children: [
1793
- /* @__PURE__ */ jsx24("dt", { children: "Last in" }),
1794
- /* @__PURE__ */ jsx24("dd", { className: "hl-mono", children: previewValue(stats.lastReceived) })
1821
+ stats.received > 0 && /* @__PURE__ */ jsxs14("div", { className: "hl-runstats-cell is-wide", children: [
1822
+ /* @__PURE__ */ jsx25("dt", { children: "Last in" }),
1823
+ /* @__PURE__ */ jsx25("dd", { className: "hl-mono", children: previewValue(stats.lastReceived) })
1795
1824
  ] }),
1796
- stats.sent > 0 && /* @__PURE__ */ jsxs13("div", { className: "hl-runstats-cell is-wide", children: [
1797
- /* @__PURE__ */ jsx24("dt", { children: "Last out" }),
1798
- /* @__PURE__ */ jsx24("dd", { className: "hl-mono", children: previewValue(stats.lastSent) })
1825
+ stats.sent > 0 && /* @__PURE__ */ jsxs14("div", { className: "hl-runstats-cell is-wide", children: [
1826
+ /* @__PURE__ */ jsx25("dt", { children: "Last out" }),
1827
+ /* @__PURE__ */ jsx25("dd", { className: "hl-mono", children: previewValue(stats.lastSent) })
1799
1828
  ] })
1800
1829
  ] })
1801
1830
  ] });
1802
1831
  }
1803
1832
 
1804
1833
  // src/panels/InspectorPanel.tsx
1805
- import { jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
1834
+ import { jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
1806
1835
  function InspectorPanel({ core, emptyState }) {
1807
1836
  const selection = useSelection(core);
1837
+ const readOnly = useReadOnly2(core);
1808
1838
  const selectedNodes2 = selection.selectedNodes;
1809
1839
  const singleId = selectedNodes2.length === 1 ? selectedNodes2[0] : void 0;
1810
1840
  const params = useNodeParams(core, singleId);
1811
1841
  const node = params.node;
1812
1842
  const singleEdgeId = selectedNodes2.length === 0 && selection.selectedEdges.length === 1 ? selection.selectedEdges[0] : void 0;
1813
1843
  if (node === void 0 && singleEdgeId !== void 0) {
1814
- return /* @__PURE__ */ jsx25(ConnectionInspector, { core, edgeId: singleEdgeId });
1844
+ return /* @__PURE__ */ jsx26(ConnectionInspector, { core, edgeId: singleEdgeId });
1815
1845
  }
1816
1846
  if (node === void 0) {
1817
- return /* @__PURE__ */ jsx25("div", { className: "hl-inspector hl-inspector-empty", children: emptyState != null ? emptyState : /* @__PURE__ */ jsxs14("div", { className: "hl-inspector-blank", children: [
1818
- /* @__PURE__ */ jsx25("span", { className: "hl-inspector-blank-badge", "aria-hidden": true }),
1819
- /* @__PURE__ */ jsx25("p", { className: "hl-inspector-blank-title", children: selectedNodes2.length > 1 ? `${String(selectedNodes2.length)} nodes selected` : "Nothing selected" }),
1820
- /* @__PURE__ */ jsx25("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." })
1847
+ return /* @__PURE__ */ jsx26("div", { className: "hl-inspector hl-inspector-empty", children: emptyState != null ? emptyState : /* @__PURE__ */ jsxs15("div", { className: "hl-inspector-blank", children: [
1848
+ /* @__PURE__ */ jsx26("span", { className: "hl-inspector-blank-badge", "aria-hidden": true }),
1849
+ /* @__PURE__ */ jsx26("p", { className: "hl-inspector-blank-title", children: selectedNodes2.length > 1 ? `${String(selectedNodes2.length)} nodes selected` : "Nothing selected" }),
1850
+ /* @__PURE__ */ jsx26("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." })
1821
1851
  ] }) });
1822
1852
  }
1823
1853
  const descriptor = core.registry.get(node.code);
1824
1854
  const fields = descriptor === void 0 ? [] : deriveParamSchema(descriptor, node);
1825
1855
  const imageUrl = typeof node.params.imageUrl === "string" && node.params.imageUrl.length > 0 ? node.params.imageUrl : void 0;
1826
- return /* @__PURE__ */ jsxs14("div", { className: "hl-inspector", children: [
1827
- /* @__PURE__ */ jsx25("div", { className: `hl-inspector-preview hl-${node.kind.toLowerCase()}`, children: imageUrl !== void 0 ? /* @__PURE__ */ jsx25("img", { className: "hl-inspector-preview-img", src: imageUrl, alt: node.name }) : /* @__PURE__ */ jsx25("span", { className: "hl-inspector-preview-badge hl-mono", children: node.kind }) }),
1828
- /* @__PURE__ */ jsxs14("header", { className: "hl-inspector-head", children: [
1829
- /* @__PURE__ */ jsx25("span", { className: `hl-inspector-kind hl-${node.kind.toLowerCase()}`, children: node.kind }),
1830
- /* @__PURE__ */ jsxs14("div", { className: "hl-inspector-titles", children: [
1831
- /* @__PURE__ */ jsx25("span", { className: "hl-inspector-name", children: node.name }),
1832
- /* @__PURE__ */ jsxs14("span", { className: "hl-inspector-role hl-mono", children: [
1856
+ return /* @__PURE__ */ jsxs15("div", { className: "hl-inspector", children: [
1857
+ /* @__PURE__ */ jsx26("div", { className: `hl-inspector-preview hl-${node.kind.toLowerCase()}`, children: imageUrl !== void 0 ? /* @__PURE__ */ jsx26("img", { className: "hl-inspector-preview-img", src: imageUrl, alt: node.name }) : /* @__PURE__ */ jsx26("span", { className: "hl-inspector-preview-badge hl-mono", children: node.kind }) }),
1858
+ /* @__PURE__ */ jsxs15("header", { className: "hl-inspector-head", children: [
1859
+ /* @__PURE__ */ jsx26("span", { className: `hl-inspector-kind hl-${node.kind.toLowerCase()}`, children: node.kind }),
1860
+ /* @__PURE__ */ jsxs15("div", { className: "hl-inspector-titles", children: [
1861
+ /* @__PURE__ */ jsx26("span", { className: "hl-inspector-name", children: node.name }),
1862
+ /* @__PURE__ */ jsxs15("span", { className: "hl-inspector-role hl-mono", children: [
1833
1863
  node.code,
1834
1864
  " \xB7 ",
1835
1865
  KIND_ROLE[node.kind]
1836
1866
  ] })
1837
1867
  ] })
1838
1868
  ] }),
1839
- /* @__PURE__ */ jsxs14("section", { className: "hl-inspector-section", children: [
1840
- /* @__PURE__ */ jsx25("h3", { className: "hl-inspector-section-title", children: "Parameters" }),
1841
- fields.length === 0 ? /* @__PURE__ */ jsx25("p", { className: "hl-inspector-hint", children: "This node has no parameters." }) : /* @__PURE__ */ jsx25(
1869
+ /* @__PURE__ */ jsxs15("section", { className: "hl-inspector-section", children: [
1870
+ /* @__PURE__ */ jsx26("h3", { className: "hl-inspector-section-title", children: "Parameters" }),
1871
+ fields.length === 0 ? /* @__PURE__ */ jsx26("p", { className: "hl-inspector-hint", children: "This node has no parameters." }) : /* @__PURE__ */ jsx26(
1842
1872
  ParamForm,
1843
1873
  {
1844
1874
  fields,
1875
+ disabled: readOnly,
1845
1876
  onCommit: (key, value) => params.setParam(key, value)
1846
1877
  }
1847
1878
  )
1848
1879
  ] }),
1849
- /* @__PURE__ */ jsx25(NodeRunStats, { nodeId: node.id }),
1850
- /* @__PURE__ */ jsxs14("section", { className: "hl-inspector-section", children: [
1851
- /* @__PURE__ */ jsx25("h3", { className: "hl-inspector-section-title", children: "Ports" }),
1852
- /* @__PURE__ */ jsx25("ul", { className: "hl-inspector-ports", children: node.ports.map((port) => /* @__PURE__ */ jsxs14(
1880
+ /* @__PURE__ */ jsx26(NodeRunStats, { nodeId: node.id }),
1881
+ /* @__PURE__ */ jsxs15("section", { className: "hl-inspector-section", children: [
1882
+ /* @__PURE__ */ jsx26("h3", { className: "hl-inspector-section-title", children: "Ports" }),
1883
+ /* @__PURE__ */ jsx26("ul", { className: "hl-inspector-ports", children: node.ports.map((port) => /* @__PURE__ */ jsxs15(
1853
1884
  "li",
1854
1885
  {
1855
1886
  className: `hl-inspector-port${port.isError ? " is-error" : ""}`,
1856
1887
  children: [
1857
- /* @__PURE__ */ jsx25("span", { className: `hl-port-dot hl-dir-${port.direction.toLowerCase()}` }),
1858
- /* @__PURE__ */ jsx25("span", { className: "hl-mono", children: port.name }),
1859
- /* @__PURE__ */ jsx25("span", { className: "hl-inspector-port-dir hl-mono", children: port.direction })
1888
+ /* @__PURE__ */ jsx26("span", { className: `hl-port-dot hl-dir-${port.direction.toLowerCase()}` }),
1889
+ /* @__PURE__ */ jsx26("span", { className: "hl-mono", children: port.name }),
1890
+ /* @__PURE__ */ jsx26("span", { className: "hl-inspector-port-dir hl-mono", children: port.direction })
1860
1891
  ]
1861
1892
  },
1862
1893
  port.id
@@ -1869,7 +1900,7 @@ function InspectorPanel({ core, emptyState }) {
1869
1900
  import { useCallback as useCallback5, useEffect as useEffect3, useMemo as useMemo4, useState as useState4 } from "react";
1870
1901
  import { GraphTopology } from "@hlang-tech/editor-core";
1871
1902
  import { useSelection as useSelection2, useViewState, useDiagnostics } from "@hlang-tech/editor-react";
1872
- import { jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
1903
+ import { jsx as jsx27, jsxs as jsxs16 } from "react/jsx-runtime";
1873
1904
  function useTopologyComponents(core) {
1874
1905
  const [tick, setTick] = useState4(0);
1875
1906
  const bump = useCallback5(() => setTick((t) => t + 1), []);
@@ -1922,80 +1953,80 @@ function OutlineTree({ core, onFocus }) {
1922
1953
  [selection, onFocus]
1923
1954
  );
1924
1955
  if (components.length === 0) {
1925
- return /* @__PURE__ */ jsx26("p", { className: "hl-outline-empty hl-mono", children: "no layers yet" });
1956
+ return /* @__PURE__ */ jsx27("p", { className: "hl-outline-empty hl-mono", children: "no layers yet" });
1926
1957
  }
1927
1958
  const groups = [...components].sort((a, b) => b.nodeIds.size - a.nodeIds.size);
1928
- return /* @__PURE__ */ jsx26("div", { className: "hl-outline", role: "tree", "aria-label": "Outline", children: groups.map((group, gi) => {
1959
+ return /* @__PURE__ */ jsx27("div", { className: "hl-outline", role: "tree", "aria-label": "Outline", children: groups.map((group, gi) => {
1929
1960
  const members = [...group.nodeIds].map((id) => nodeById.get(id)).filter((n) => n !== void 0);
1930
1961
  const open = !collapsed.has(group.id);
1931
1962
  const groupName = `Flow ${String(gi + 1)}`;
1932
- return /* @__PURE__ */ jsxs15("div", { className: "hl-outline-group", role: "treeitem", "aria-expanded": open, children: [
1933
- /* @__PURE__ */ jsxs15(
1963
+ return /* @__PURE__ */ jsxs16("div", { className: "hl-outline-group", role: "treeitem", "aria-expanded": open, children: [
1964
+ /* @__PURE__ */ jsxs16(
1934
1965
  "button",
1935
1966
  {
1936
1967
  className: "hl-outline-grouphead",
1937
1968
  onClick: () => toggleGroup(group.id),
1938
1969
  children: [
1939
- /* @__PURE__ */ jsx26("span", { className: "hl-outline-caret" + (open ? " is-open" : ""), "aria-hidden": true, children: "\u25B8" }),
1940
- /* @__PURE__ */ jsx26("span", { className: "hl-outline-groupname", children: groupName }),
1941
- /* @__PURE__ */ jsx26("span", { className: "hl-outline-count hl-mono", children: group.nodeIds.size })
1970
+ /* @__PURE__ */ jsx27("span", { className: "hl-outline-caret" + (open ? " is-open" : ""), "aria-hidden": true, children: "\u25B8" }),
1971
+ /* @__PURE__ */ jsx27("span", { className: "hl-outline-groupname", children: groupName }),
1972
+ /* @__PURE__ */ jsx27("span", { className: "hl-outline-count hl-mono", children: group.nodeIds.size })
1942
1973
  ]
1943
1974
  }
1944
1975
  ),
1945
- open && /* @__PURE__ */ jsx26("ul", { className: "hl-outline-members", children: members.map((node) => {
1976
+ open && /* @__PURE__ */ jsx27("ul", { className: "hl-outline-members", children: members.map((node) => {
1946
1977
  const active = selection.isSelected(node.id);
1947
1978
  const isHidden = viewState.isHidden(node.id);
1948
1979
  const isLocked = viewState.isLocked(node.id);
1949
1980
  const { imageUrl } = node.params;
1950
1981
  const thumbnail = typeof imageUrl === "string" && imageUrl.length > 0 ? imageUrl : void 0;
1951
- return /* @__PURE__ */ jsx26("li", { children: /* @__PURE__ */ jsxs15(
1982
+ return /* @__PURE__ */ jsx27("li", { children: /* @__PURE__ */ jsxs16(
1952
1983
  "div",
1953
1984
  {
1954
1985
  className: "hl-outline-row" + (active ? " is-active" : "") + (isHidden ? " is-hidden" : "") + (isLocked ? " is-locked" : ""),
1955
1986
  children: [
1956
- /* @__PURE__ */ jsxs15("button", { className: "hl-outline-main", onClick: () => select(node.id), children: [
1957
- /* @__PURE__ */ jsx26(
1987
+ /* @__PURE__ */ jsxs16("button", { className: "hl-outline-main", onClick: () => select(node.id), children: [
1988
+ /* @__PURE__ */ jsx27(
1958
1989
  "span",
1959
1990
  {
1960
1991
  className: `hl-outline-icon hl-kind-${node.kind.toLowerCase()}`,
1961
1992
  "aria-hidden": true,
1962
- children: thumbnail !== void 0 ? /* @__PURE__ */ jsx26(
1993
+ children: thumbnail !== void 0 ? /* @__PURE__ */ jsx27(
1963
1994
  "img",
1964
1995
  {
1965
1996
  className: "hl-outline-thumb",
1966
1997
  src: thumbnail,
1967
1998
  alt: ""
1968
1999
  }
1969
- ) : /* @__PURE__ */ jsx26(NodeIcon, { code: node.code, kind: node.kind, size: 13 })
2000
+ ) : /* @__PURE__ */ jsx27(NodeIcon, { code: node.code, kind: node.kind, size: 13 })
1970
2001
  }
1971
2002
  ),
1972
- /* @__PURE__ */ jsx26("span", { className: "hl-outline-name", children: node.name }),
2003
+ /* @__PURE__ */ jsx27("span", { className: "hl-outline-name", children: node.name }),
1973
2004
  (() => {
1974
2005
  const diag = diagnostics.worstOf(node.id);
1975
- return diag === void 0 ? null : /* @__PURE__ */ jsx26(DiagnosticBadge, { diagnostic: diag, variant: "hl-outline-diag", size: 11 });
2006
+ return diag === void 0 ? null : /* @__PURE__ */ jsx27(DiagnosticBadge, { diagnostic: diag, variant: "hl-outline-diag", size: 11 });
1976
2007
  })(),
1977
- isLocked && /* @__PURE__ */ jsx26("span", { className: "hl-outline-badge", "aria-hidden": true, children: /* @__PURE__ */ jsx26(LockIcon, { size: 11 }) }),
1978
- isHidden && /* @__PURE__ */ jsx26("span", { className: "hl-outline-badge", "aria-hidden": true, children: /* @__PURE__ */ jsx26(EyeOffIcon, { size: 11 }) })
2008
+ isLocked && /* @__PURE__ */ jsx27("span", { className: "hl-outline-badge", "aria-hidden": true, children: /* @__PURE__ */ jsx27(LockIcon, { size: 11 }) }),
2009
+ isHidden && /* @__PURE__ */ jsx27("span", { className: "hl-outline-badge", "aria-hidden": true, children: /* @__PURE__ */ jsx27(EyeOffIcon, { size: 11 }) })
1979
2010
  ] }),
1980
- /* @__PURE__ */ jsxs15("span", { className: "hl-outline-controls", children: [
1981
- /* @__PURE__ */ jsx26(
2011
+ /* @__PURE__ */ jsxs16("span", { className: "hl-outline-controls", children: [
2012
+ /* @__PURE__ */ jsx27(
1982
2013
  "button",
1983
2014
  {
1984
2015
  className: "hl-outline-ctl" + (isHidden ? " is-active-ctl" : ""),
1985
2016
  onClick: () => viewState.toggleHidden(node.id),
1986
2017
  "aria-label": isHidden ? "Show layer" : "Hide layer",
1987
2018
  title: isHidden ? "Show" : "Hide",
1988
- children: isHidden ? /* @__PURE__ */ jsx26(EyeOffIcon, {}) : /* @__PURE__ */ jsx26(EyeIcon, {})
2019
+ children: isHidden ? /* @__PURE__ */ jsx27(EyeOffIcon, {}) : /* @__PURE__ */ jsx27(EyeIcon, {})
1989
2020
  }
1990
2021
  ),
1991
- /* @__PURE__ */ jsx26(
2022
+ /* @__PURE__ */ jsx27(
1992
2023
  "button",
1993
2024
  {
1994
2025
  className: "hl-outline-ctl" + (isLocked ? " is-active-ctl" : ""),
1995
2026
  onClick: () => viewState.toggleLocked(node.id),
1996
2027
  "aria-label": isLocked ? "Unlock layer" : "Lock layer",
1997
2028
  title: isLocked ? "Unlock" : "Lock",
1998
- children: isLocked ? /* @__PURE__ */ jsx26(LockIcon, {}) : /* @__PURE__ */ jsx26(UnlockIcon, {})
2029
+ children: isLocked ? /* @__PURE__ */ jsx27(LockIcon, {}) : /* @__PURE__ */ jsx27(UnlockIcon, {})
1999
2030
  }
2000
2031
  )
2001
2032
  ] })
@@ -2008,21 +2039,48 @@ function OutlineTree({ core, onFocus }) {
2008
2039
  }
2009
2040
 
2010
2041
  // src/panels/PreviewPanel.tsx
2011
- import { jsx as jsx27, jsxs as jsxs16 } from "react/jsx-runtime";
2042
+ import { jsx as jsx28, jsxs as jsxs17 } from "react/jsx-runtime";
2012
2043
  function PreviewPanel({ imageUrl, title = "Preview", status }) {
2013
- return /* @__PURE__ */ jsxs16("section", { className: "hl-preview", "aria-label": "Preview", children: [
2014
- /* @__PURE__ */ jsxs16("div", { className: "hl-preview-head", children: [
2015
- /* @__PURE__ */ jsx27("span", { className: "hl-preview-title", children: title }),
2016
- status !== void 0 && /* @__PURE__ */ jsx27("span", { className: "hl-preview-status hl-mono", children: status })
2044
+ return /* @__PURE__ */ jsxs17("section", { className: "hl-preview", "aria-label": "Preview", children: [
2045
+ /* @__PURE__ */ jsxs17("div", { className: "hl-preview-head", children: [
2046
+ /* @__PURE__ */ jsx28("span", { className: "hl-preview-title", children: title }),
2047
+ status !== void 0 && /* @__PURE__ */ jsx28("span", { className: "hl-preview-status hl-mono", children: status })
2017
2048
  ] }),
2018
- /* @__PURE__ */ jsx27("div", { className: "hl-preview-frame", children: imageUrl !== void 0 && imageUrl.length > 0 ? /* @__PURE__ */ jsx27("img", { className: "hl-preview-img", src: imageUrl, alt: title, draggable: false }) : /* @__PURE__ */ jsx27("div", { className: "hl-preview-placeholder hl-mono", children: "no output yet" }) })
2049
+ /* @__PURE__ */ jsx28("div", { className: "hl-preview-frame", children: imageUrl !== void 0 && imageUrl.length > 0 ? /* @__PURE__ */ jsx28("img", { className: "hl-preview-img", src: imageUrl, alt: title, draggable: false }) : /* @__PURE__ */ jsx28("div", { className: "hl-preview-placeholder hl-mono", children: "no output yet" }) })
2019
2050
  ] });
2020
2051
  }
2021
2052
 
2053
+ // src/panels/PanelHead.tsx
2054
+ import { jsx as jsx29, jsxs as jsxs18 } from "react/jsx-runtime";
2055
+ function PanelHead({ leading, meta, children }) {
2056
+ return /* @__PURE__ */ jsxs18("header", { className: "hl-panel-head", children: [
2057
+ leading,
2058
+ meta === void 0 ? null : /* @__PURE__ */ jsx29("span", { className: "hl-panel-meta hl-mono", children: meta }),
2059
+ children === void 0 ? null : /* @__PURE__ */ jsx29("span", { className: "hl-panel-actions", children })
2060
+ ] });
2061
+ }
2062
+ function PanelAction({ icon, label, onClick, pressed, disabled = false, title }) {
2063
+ return /* @__PURE__ */ jsxs18(
2064
+ "button",
2065
+ {
2066
+ type: "button",
2067
+ className: `hl-panel-action${pressed === true ? " is-on" : ""}`,
2068
+ "aria-pressed": pressed,
2069
+ disabled,
2070
+ title: title != null ? title : label,
2071
+ onClick,
2072
+ children: [
2073
+ /* @__PURE__ */ jsx29("span", { className: "hl-panel-action-icon", "aria-hidden": true, children: icon }),
2074
+ /* @__PURE__ */ jsx29("span", { className: "hl-panel-action-label", children: label })
2075
+ ]
2076
+ }
2077
+ );
2078
+ }
2079
+
2022
2080
  // src/panels/RailTabs.tsx
2023
- import { jsx as jsx28 } from "react/jsx-runtime";
2081
+ import { jsx as jsx30 } from "react/jsx-runtime";
2024
2082
  function RailTabs({ tabs, active, onChange }) {
2025
- return /* @__PURE__ */ jsx28("div", { className: "hl-railtabs", role: "tablist", children: tabs.map((tab) => /* @__PURE__ */ jsx28(
2083
+ return /* @__PURE__ */ jsx30("div", { className: "hl-railtabs", role: "tablist", children: tabs.map((tab) => /* @__PURE__ */ jsx30(
2026
2084
  "button",
2027
2085
  {
2028
2086
  role: "tab",
@@ -2036,20 +2094,20 @@ function RailTabs({ tabs, active, onChange }) {
2036
2094
  }
2037
2095
 
2038
2096
  // src/panels/AssetGrid.tsx
2039
- import { jsx as jsx29 } from "react/jsx-runtime";
2097
+ import { jsx as jsx31 } from "react/jsx-runtime";
2040
2098
  function AssetGrid({ items, onSelect, emptyHint = "No assets yet" }) {
2041
2099
  if (items.length === 0) {
2042
- return /* @__PURE__ */ jsx29("p", { className: "hl-assetgrid-empty hl-mono", children: emptyHint });
2100
+ return /* @__PURE__ */ jsx31("p", { className: "hl-assetgrid-empty hl-mono", children: emptyHint });
2043
2101
  }
2044
- return /* @__PURE__ */ jsx29("div", { className: "hl-assetgrid", children: items.map((item) => {
2102
+ return /* @__PURE__ */ jsx31("div", { className: "hl-assetgrid", children: items.map((item) => {
2045
2103
  var _a;
2046
- return /* @__PURE__ */ jsx29(
2104
+ return /* @__PURE__ */ jsx31(
2047
2105
  "button",
2048
2106
  {
2049
2107
  className: "hl-assetgrid-cell",
2050
2108
  onClick: () => onSelect == null ? void 0 : onSelect(item.id),
2051
2109
  title: item.label,
2052
- children: /* @__PURE__ */ jsx29("img", { className: "hl-assetgrid-img", src: item.url, alt: (_a = item.label) != null ? _a : "", loading: "lazy" })
2110
+ children: /* @__PURE__ */ jsx31("img", { className: "hl-assetgrid-img", src: item.url, alt: (_a = item.label) != null ? _a : "", loading: "lazy" })
2053
2111
  },
2054
2112
  item.id
2055
2113
  );
@@ -2059,8 +2117,8 @@ function AssetGrid({ items, onSelect, emptyHint = "No assets yet" }) {
2059
2117
  // src/panels/PositionPanel.tsx
2060
2118
  import { useCallback as useCallback6, useEffect as useEffect4, useReducer } from "react";
2061
2119
  import { MoveNodeCommand } from "@hlang-tech/editor-core";
2062
- import { useSelection as useSelection3 } from "@hlang-tech/editor-react";
2063
- import { jsx as jsx30, jsxs as jsxs17 } from "react/jsx-runtime";
2120
+ import { useReadOnly as useReadOnly3, useSelection as useSelection3 } from "@hlang-tech/editor-react";
2121
+ import { jsx as jsx32, jsxs as jsxs19 } from "react/jsx-runtime";
2064
2122
  function selectedNodes(core) {
2065
2123
  const ids = core.selection.getSelectedNodes();
2066
2124
  const out = [];
@@ -2075,6 +2133,7 @@ function selectedNodes(core) {
2075
2133
  function PositionPanel({ core, livePosition }) {
2076
2134
  var _a;
2077
2135
  useSelection3(core);
2136
+ const readOnly = useReadOnly3(core);
2078
2137
  const [, bump] = useReducer((n) => n + 1, 0);
2079
2138
  useEffect4(() => {
2080
2139
  const kinds = ["nodeChanged", "nodeAdded", "nodeRemoved"];
@@ -2145,7 +2204,7 @@ function PositionPanel({ core, livePosition }) {
2145
2204
  },
2146
2205
  [core, single]
2147
2206
  );
2148
- const disabled = nodes.length === 0;
2207
+ const disabled = readOnly || nodes.length === 0;
2149
2208
  const alignButtons = [
2150
2209
  { kind: "left", label: "Align left", d: "M5 4v16M9 8h8M9 16h5" },
2151
2210
  { kind: "hcenter", label: "Align centre", d: "M12 4v16M8 9h8M9 15h6" },
@@ -2154,9 +2213,9 @@ function PositionPanel({ core, livePosition }) {
2154
2213
  { kind: "vcenter", label: "Align middle", d: "M4 12h16M9 8v8M15 9v6" },
2155
2214
  { kind: "bottom", label: "Align bottom", d: "M4 19h16M8 7v8M16 12v3" }
2156
2215
  ];
2157
- return /* @__PURE__ */ jsxs17("section", { className: "hl-position", children: [
2158
- /* @__PURE__ */ jsx30("h3", { className: "hl-position-title hl-mono", children: "Position" }),
2159
- /* @__PURE__ */ jsx30("div", { className: "hl-position-align", role: "group", "aria-label": "Align", children: alignButtons.map((b) => /* @__PURE__ */ jsx30(
2216
+ return /* @__PURE__ */ jsxs19("section", { className: "hl-position", children: [
2217
+ /* @__PURE__ */ jsx32("h3", { className: "hl-position-title hl-mono", children: "Position" }),
2218
+ /* @__PURE__ */ jsx32("div", { className: "hl-position-align", role: "group", "aria-label": "Align", children: alignButtons.map((b) => /* @__PURE__ */ jsx32(
2160
2219
  "button",
2161
2220
  {
2162
2221
  className: "hl-position-alignbtn",
@@ -2164,38 +2223,38 @@ function PositionPanel({ core, livePosition }) {
2164
2223
  disabled,
2165
2224
  title: b.label,
2166
2225
  "aria-label": b.label,
2167
- children: /* @__PURE__ */ jsx30("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__ */ jsx30("path", { d: b.d }) })
2226
+ children: /* @__PURE__ */ jsx32("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__ */ jsx32("path", { d: b.d }) })
2168
2227
  },
2169
2228
  b.kind
2170
2229
  )) }),
2171
- /* @__PURE__ */ jsxs17("div", { className: "hl-position-coords", children: [
2172
- /* @__PURE__ */ jsxs17("label", { className: "hl-position-field", children: [
2173
- /* @__PURE__ */ jsx30(
2230
+ /* @__PURE__ */ jsxs19("div", { className: "hl-position-coords", children: [
2231
+ /* @__PURE__ */ jsxs19("label", { className: "hl-position-field", children: [
2232
+ /* @__PURE__ */ jsx32(
2174
2233
  "input",
2175
2234
  {
2176
2235
  type: "number",
2177
2236
  className: "hl-position-input hl-mono",
2178
2237
  value: shownPos !== void 0 ? Math.round(shownPos.x) : "",
2179
- disabled: single === void 0,
2238
+ disabled: readOnly || single === void 0,
2180
2239
  onChange: (e) => setCoord("x", Number(e.target.value)),
2181
2240
  "aria-label": "X position"
2182
2241
  }
2183
2242
  ),
2184
- /* @__PURE__ */ jsx30("span", { className: "hl-position-axis hl-mono", children: "X" })
2243
+ /* @__PURE__ */ jsx32("span", { className: "hl-position-axis hl-mono", children: "X" })
2185
2244
  ] }),
2186
- /* @__PURE__ */ jsxs17("label", { className: "hl-position-field", children: [
2187
- /* @__PURE__ */ jsx30(
2245
+ /* @__PURE__ */ jsxs19("label", { className: "hl-position-field", children: [
2246
+ /* @__PURE__ */ jsx32(
2188
2247
  "input",
2189
2248
  {
2190
2249
  type: "number",
2191
2250
  className: "hl-position-input hl-mono",
2192
2251
  value: shownPos !== void 0 ? Math.round(shownPos.y) : "",
2193
- disabled: single === void 0,
2252
+ disabled: readOnly || single === void 0,
2194
2253
  onChange: (e) => setCoord("y", Number(e.target.value)),
2195
2254
  "aria-label": "Y position"
2196
2255
  }
2197
2256
  ),
2198
- /* @__PURE__ */ jsx30("span", { className: "hl-position-axis hl-mono", children: "Y" })
2257
+ /* @__PURE__ */ jsx32("span", { className: "hl-position-axis hl-mono", children: "Y" })
2199
2258
  ] })
2200
2259
  ] })
2201
2260
  ] });
@@ -2203,7 +2262,7 @@ function PositionPanel({ core, livePosition }) {
2203
2262
 
2204
2263
  // src/command-palette/NodeSearch.tsx
2205
2264
  import { useCallback as useCallback7, useEffect as useEffect5, useMemo as useMemo5, useRef as useRef5, useState as useState5 } from "react";
2206
- import { Fragment as Fragment4, jsx as jsx31, jsxs as jsxs18 } from "react/jsx-runtime";
2265
+ import { Fragment as Fragment5, jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
2207
2266
  var PANEL_WIDTH = 320;
2208
2267
  var PANEL_MAX_HEIGHT = 360;
2209
2268
  function NodeSearch({ controller }) {
@@ -2263,8 +2322,8 @@ function NodeSearch({ controller }) {
2263
2322
  }
2264
2323
  const style = clampToViewport(state.screen.x, state.screen.y);
2265
2324
  const connectMode = state.pending !== void 0;
2266
- return /* @__PURE__ */ jsxs18(Fragment4, { children: [
2267
- /* @__PURE__ */ jsx31(
2325
+ return /* @__PURE__ */ jsxs20(Fragment5, { children: [
2326
+ /* @__PURE__ */ jsx33(
2268
2327
  "div",
2269
2328
  {
2270
2329
  className: "hl-node-search-scrim",
@@ -2273,10 +2332,10 @@ function NodeSearch({ controller }) {
2273
2332
  }
2274
2333
  }
2275
2334
  ),
2276
- /* @__PURE__ */ jsxs18("div", { className: "hl-node-search", style, role: "dialog", "aria-label": "Create node", children: [
2277
- /* @__PURE__ */ jsxs18("div", { className: "hl-node-search-field", children: [
2278
- /* @__PURE__ */ jsx31("span", { className: "hl-node-search-glyph", "aria-hidden": true, children: connectMode ? "\u2933" : "\uFF0B" }),
2279
- /* @__PURE__ */ jsx31(
2335
+ /* @__PURE__ */ jsxs20("div", { className: "hl-node-search", style, role: "dialog", "aria-label": "Create node", children: [
2336
+ /* @__PURE__ */ jsxs20("div", { className: "hl-node-search-field", children: [
2337
+ /* @__PURE__ */ jsx33("span", { className: "hl-node-search-glyph", "aria-hidden": true, children: connectMode ? "\u2933" : "\uFF0B" }),
2338
+ /* @__PURE__ */ jsx33(
2280
2339
  "input",
2281
2340
  {
2282
2341
  ref: inputRef,
@@ -2294,12 +2353,12 @@ function NodeSearch({ controller }) {
2294
2353
  }
2295
2354
  )
2296
2355
  ] }),
2297
- /* @__PURE__ */ jsxs18("ul", { className: "hl-node-search-list", role: "listbox", children: [
2298
- results.length === 0 && /* @__PURE__ */ jsx31("li", { className: "hl-node-search-empty hl-mono", children: "no matching nodes" }),
2356
+ /* @__PURE__ */ jsxs20("ul", { className: "hl-node-search-list", role: "listbox", children: [
2357
+ results.length === 0 && /* @__PURE__ */ jsx33("li", { className: "hl-node-search-empty hl-mono", children: "no matching nodes" }),
2299
2358
  results.map((view, index) => {
2300
2359
  const descriptor = view.result.value;
2301
2360
  const kind = descriptor.kind;
2302
- return /* @__PURE__ */ jsxs18(
2361
+ return /* @__PURE__ */ jsxs20(
2303
2362
  "li",
2304
2363
  {
2305
2364
  role: "option",
@@ -2311,15 +2370,15 @@ function NodeSearch({ controller }) {
2311
2370
  choose(view);
2312
2371
  },
2313
2372
  children: [
2314
- /* @__PURE__ */ jsx31(
2373
+ /* @__PURE__ */ jsx33(
2315
2374
  "span",
2316
2375
  {
2317
2376
  className: `hl-node-search-kind hl-mono hl-kind-${kind.toLowerCase()}`,
2318
2377
  children: kind
2319
2378
  }
2320
2379
  ),
2321
- /* @__PURE__ */ jsx31("span", { className: "hl-node-search-label", children: highlight(descriptor.displayName, view.result.match) }),
2322
- view.connectPortId !== void 0 && /* @__PURE__ */ jsx31("span", { className: "hl-node-search-port hl-mono", children: view.connectPortId })
2380
+ /* @__PURE__ */ jsx33("span", { className: "hl-node-search-label", children: highlight(descriptor.displayName, view.result.match) }),
2381
+ view.connectPortId !== void 0 && /* @__PURE__ */ jsx33("span", { className: "hl-node-search-port hl-mono", children: view.connectPortId })
2323
2382
  ]
2324
2383
  },
2325
2384
  descriptor.code
@@ -2333,7 +2392,7 @@ function highlight(text, match) {
2333
2392
  const hit = new Set(match.positions);
2334
2393
  const chars = Array.from(text);
2335
2394
  return chars.map(
2336
- (ch, i) => hit.has(i) ? /* @__PURE__ */ jsx31("mark", { className: "hl-node-search-hit", children: ch }, i) : /* @__PURE__ */ jsx31("span", { children: ch }, i)
2395
+ (ch, i) => hit.has(i) ? /* @__PURE__ */ jsx33("mark", { className: "hl-node-search-hit", children: ch }, i) : /* @__PURE__ */ jsx33("span", { children: ch }, i)
2337
2396
  );
2338
2397
  }
2339
2398
  function clampToViewport(x, y) {
@@ -2513,7 +2572,7 @@ function useNodeSearchTriggers(core, search, options = {}) {
2513
2572
  (event, connectionState) => {
2514
2573
  const start = startRef.current;
2515
2574
  startRef.current = null;
2516
- if (connectionState.toNode !== null || start === null) {
2575
+ if (core.mode.readOnly || connectionState.toNode !== null || start === null) {
2517
2576
  return;
2518
2577
  }
2519
2578
  if (start.nodeId === null || start.handleId === null || start.handleType === null) {
@@ -2544,6 +2603,9 @@ function useNodeSearchTriggers(core, search, options = {}) {
2544
2603
  );
2545
2604
  const onPaneClick = useCallback9(
2546
2605
  (event) => {
2606
+ if (core.mode.readOnly) {
2607
+ return;
2608
+ }
2547
2609
  const t = now();
2548
2610
  const isDouble = t - lastPaneClickRef.current <= doubleClickMs;
2549
2611
  lastPaneClickRef.current = t;
@@ -2554,7 +2616,7 @@ function useNodeSearchTriggers(core, search, options = {}) {
2554
2616
  const screen = { x: event.clientX, y: event.clientY };
2555
2617
  search.openBlank(screen, flow.screenToFlowPosition(screen));
2556
2618
  },
2557
- [flow, search, doubleClickMs, now]
2619
+ [core, flow, search, doubleClickMs, now]
2558
2620
  );
2559
2621
  return { onConnectStart, onConnectEnd, onPaneClick };
2560
2622
  }
@@ -2657,7 +2719,7 @@ function useEditorHotkeys(core, options = {}) {
2657
2719
 
2658
2720
  // src/context-menu/NodeContextMenu.tsx
2659
2721
  import { useCallback as useCallback10, useEffect as useEffect7, useMemo as useMemo7, useRef as useRef7, useState as useState7 } from "react";
2660
- import { Fragment as Fragment5, jsx as jsx32, jsxs as jsxs19 } from "react/jsx-runtime";
2722
+ import { Fragment as Fragment6, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
2661
2723
  var PANEL_WIDTH2 = 208;
2662
2724
  var ITEM_HEIGHT = 34;
2663
2725
  var VIEWPORT_MARGIN = 8;
@@ -2735,8 +2797,8 @@ function NodeContextMenu({ controller, items }) {
2735
2797
  return null;
2736
2798
  }
2737
2799
  const style = clampToViewport2(state.screen.x, state.screen.y, rows.length);
2738
- return /* @__PURE__ */ jsxs19(Fragment5, { children: [
2739
- /* @__PURE__ */ jsx32(
2800
+ return /* @__PURE__ */ jsxs21(Fragment6, { children: [
2801
+ /* @__PURE__ */ jsx34(
2740
2802
  "div",
2741
2803
  {
2742
2804
  className: "hl-ctx-scrim",
@@ -2749,7 +2811,7 @@ function NodeContextMenu({ controller, items }) {
2749
2811
  }
2750
2812
  }
2751
2813
  ),
2752
- /* @__PURE__ */ jsx32(
2814
+ /* @__PURE__ */ jsx34(
2753
2815
  "div",
2754
2816
  {
2755
2817
  ref: menuRef,
@@ -2759,7 +2821,7 @@ function NodeContextMenu({ controller, items }) {
2759
2821
  "aria-label": "Node actions",
2760
2822
  tabIndex: -1,
2761
2823
  onKeyDown,
2762
- children: rows.map((item, index) => /* @__PURE__ */ jsxs19(
2824
+ children: rows.map((item, index) => /* @__PURE__ */ jsxs21(
2763
2825
  "button",
2764
2826
  {
2765
2827
  type: "button",
@@ -2772,9 +2834,9 @@ function NodeContextMenu({ controller, items }) {
2772
2834
  choose(item);
2773
2835
  },
2774
2836
  children: [
2775
- /* @__PURE__ */ jsx32("span", { className: "hl-ctx-item-icon", "aria-hidden": true, children: item.icon }),
2776
- /* @__PURE__ */ jsx32("span", { className: "hl-ctx-item-label", children: item.label }),
2777
- item.shortcut !== void 0 && /* @__PURE__ */ jsx32("span", { className: "hl-ctx-item-key hl-mono", children: item.shortcut })
2837
+ /* @__PURE__ */ jsx34("span", { className: "hl-ctx-item-icon", "aria-hidden": true, children: item.icon }),
2838
+ /* @__PURE__ */ jsx34("span", { className: "hl-ctx-item-label", children: item.label }),
2839
+ item.shortcut !== void 0 && /* @__PURE__ */ jsx34("span", { className: "hl-ctx-item-key hl-mono", children: item.shortcut })
2778
2840
  ]
2779
2841
  },
2780
2842
  item.id
@@ -2808,7 +2870,7 @@ function useNodeContextMenu() {
2808
2870
 
2809
2871
  // src/overlays/Cheatsheet.tsx
2810
2872
  import { useEffect as useEffect8, useRef as useRef8 } from "react";
2811
- import { Fragment as Fragment6, jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
2873
+ import { Fragment as Fragment7, jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
2812
2874
  var DEFAULT_GROUPS = [
2813
2875
  {
2814
2876
  title: "Edit",
@@ -2862,9 +2924,9 @@ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2862
2924
  if (!open) {
2863
2925
  return null;
2864
2926
  }
2865
- return /* @__PURE__ */ jsxs20(Fragment6, { children: [
2866
- /* @__PURE__ */ jsx33("div", { className: "hl-cheat-scrim", onPointerDown: onClose }),
2867
- /* @__PURE__ */ jsxs20(
2927
+ return /* @__PURE__ */ jsxs22(Fragment7, { children: [
2928
+ /* @__PURE__ */ jsx35("div", { className: "hl-cheat-scrim", onPointerDown: onClose }),
2929
+ /* @__PURE__ */ jsxs22(
2868
2930
  "div",
2869
2931
  {
2870
2932
  ref: dialogRef,
@@ -2874,18 +2936,18 @@ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2874
2936
  "aria-label": "Keyboard shortcuts",
2875
2937
  tabIndex: -1,
2876
2938
  children: [
2877
- /* @__PURE__ */ jsxs20("div", { className: "hl-cheat-head", children: [
2878
- /* @__PURE__ */ jsx33("span", { className: "hl-cheat-title", children: "Shortcuts" }),
2879
- /* @__PURE__ */ jsx33("button", { className: "hl-cheat-close", onClick: onClose, "aria-label": "Close", children: "\u2715" })
2939
+ /* @__PURE__ */ jsxs22("div", { className: "hl-cheat-head", children: [
2940
+ /* @__PURE__ */ jsx35("span", { className: "hl-cheat-title", children: "Shortcuts" }),
2941
+ /* @__PURE__ */ jsx35("button", { className: "hl-cheat-close", onClick: onClose, "aria-label": "Close", children: "\u2715" })
2880
2942
  ] }),
2881
- /* @__PURE__ */ jsx33("div", { className: "hl-cheat-grid", children: groups.map((group) => /* @__PURE__ */ jsxs20("section", { className: "hl-cheat-group", children: [
2882
- /* @__PURE__ */ jsx33("h3", { className: "hl-cheat-group-title hl-mono", children: group.title }),
2883
- /* @__PURE__ */ jsx33("ul", { className: "hl-cheat-rows", children: group.rows.map((row) => (
2943
+ /* @__PURE__ */ jsx35("div", { className: "hl-cheat-grid", children: groups.map((group) => /* @__PURE__ */ jsxs22("section", { className: "hl-cheat-group", children: [
2944
+ /* @__PURE__ */ jsx35("h3", { className: "hl-cheat-group-title hl-mono", children: group.title }),
2945
+ /* @__PURE__ */ jsx35("ul", { className: "hl-cheat-rows", children: group.rows.map((row) => (
2884
2946
  // A row is a binding: one action can have several (two ways to pan),
2885
2947
  // so neither the label nor the keys alone identify it.
2886
- /* @__PURE__ */ jsxs20("li", { className: "hl-cheat-row", children: [
2887
- /* @__PURE__ */ jsx33("span", { className: "hl-cheat-label", children: row.label }),
2888
- /* @__PURE__ */ jsx33("kbd", { className: "hl-cheat-keys hl-mono", children: row.keys })
2948
+ /* @__PURE__ */ jsxs22("li", { className: "hl-cheat-row", children: [
2949
+ /* @__PURE__ */ jsx35("span", { className: "hl-cheat-label", children: row.label }),
2950
+ /* @__PURE__ */ jsx35("kbd", { className: "hl-cheat-keys hl-mono", children: row.keys })
2889
2951
  ] }, `${row.keys}\0${row.label}`)
2890
2952
  )) })
2891
2953
  ] }, group.title)) })
@@ -2897,7 +2959,7 @@ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2897
2959
 
2898
2960
  // src/overlays/Toast.tsx
2899
2961
  import { createContext as createContext4, useCallback as useCallback12, useContext as useContext4, useMemo as useMemo9, useRef as useRef9, useState as useState9 } from "react";
2900
- import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
2962
+ import { jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
2901
2963
  var ToastCtx = createContext4({ show: () => void 0 });
2902
2964
  function ToastProvider({ children, duration = 2200 }) {
2903
2965
  const [items, setItems] = useState9([]);
@@ -2914,9 +2976,9 @@ function ToastProvider({ children, duration = 2200 }) {
2914
2976
  [duration]
2915
2977
  );
2916
2978
  const api = useMemo9(() => ({ show }), [show]);
2917
- return /* @__PURE__ */ jsxs21(ToastCtx.Provider, { value: api, children: [
2979
+ return /* @__PURE__ */ jsxs23(ToastCtx.Provider, { value: api, children: [
2918
2980
  children,
2919
- /* @__PURE__ */ jsx34("div", { className: "hl-toast-stack", "aria-live": "polite", children: items.map((t) => /* @__PURE__ */ jsx34("div", { className: "hl-toast", children: t.message }, t.id)) })
2981
+ /* @__PURE__ */ jsx36("div", { className: "hl-toast-stack", "aria-live": "polite", children: items.map((t) => /* @__PURE__ */ jsx36("div", { className: "hl-toast", children: t.message }, t.id)) })
2920
2982
  ] });
2921
2983
  }
2922
2984
  function useToast() {
@@ -3221,7 +3283,7 @@ function useFollowScroll() {
3221
3283
  // src/panels/RunLog.tsx
3222
3284
  import { useState as useState11 } from "react";
3223
3285
  import { useSelection as useSelection4 } from "@hlang-tech/editor-react";
3224
- import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
3286
+ import { jsx as jsx37, jsxs as jsxs24 } from "react/jsx-runtime";
3225
3287
  function RunLog({ core, onSelectNode, title = "Run log", leading }) {
3226
3288
  const trace = useRunTraceStore();
3227
3289
  useRunTraceVersion(trace);
@@ -3243,55 +3305,54 @@ function RunLog({ core, onSelectNode, title = "Run log", leading }) {
3243
3305
  core.selection.selectNodes([nodeId], "replace");
3244
3306
  onSelectNode == null ? void 0 : onSelectNode(nodeId);
3245
3307
  };
3246
- return /* @__PURE__ */ jsxs22("section", { className: "hl-runlog", "aria-label": title, children: [
3247
- /* @__PURE__ */ jsxs22("header", { className: "hl-runlog-head", children: [
3248
- leading != null ? leading : /* @__PURE__ */ jsx35("span", { className: "hl-runlog-title", children: title }),
3249
- /* @__PURE__ */ jsxs22("span", { className: "hl-runlog-count hl-mono", children: [
3250
- entries.length === all.length ? `${String(all.length)} events` : `${String(entries.length)} of ${String(all.length)} events`,
3251
- trace.dropped > 0 ? ` \xB7 ${String(trace.dropped)} earlier not kept` : ""
3252
- ] }),
3253
- /* @__PURE__ */ jsxs22("span", { className: "hl-runlog-actions", children: [
3254
- /* @__PURE__ */ jsx35(
3255
- "button",
3256
- {
3257
- type: "button",
3258
- className: `hl-runlog-toggle${onlySelected ? " is-on" : ""}`,
3259
- "aria-pressed": onlySelected,
3260
- disabled: selectedId === void 0 && !onlySelected,
3261
- title: selectedId === void 0 ? "Select a node to filter by it" : `Only ${label(selectedId)}`,
3262
- onClick: () => {
3263
- setOnlySelected((value) => !value);
3264
- },
3265
- children: onlySelected && selectedId !== void 0 ? `Only ${label(selectedId)}` : "Selected node"
3266
- }
3267
- ),
3268
- /* @__PURE__ */ jsx35(
3269
- "button",
3270
- {
3271
- type: "button",
3272
- className: `hl-runlog-toggle${follow ? " is-on" : ""}`,
3273
- "aria-pressed": follow,
3274
- onClick: () => {
3275
- setFollow(!follow);
3276
- },
3277
- children: "Follow"
3278
- }
3279
- ),
3280
- /* @__PURE__ */ jsx35(
3281
- "button",
3282
- {
3283
- type: "button",
3284
- className: "hl-runlog-toggle",
3285
- disabled: all.length === 0,
3286
- onClick: () => {
3287
- trace.clear();
3288
- },
3289
- children: "Clear"
3290
- }
3291
- )
3292
- ] })
3293
- ] }),
3294
- entries.length === 0 ? /* @__PURE__ */ jsx35("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__ */ jsx35("ol", { className: "hl-runlog-body hl-mono", ref: bodyRef, onScroll, children: entries.map((entry) => /* @__PURE__ */ jsx35(Row, { entry, label, onChoose: choose }, entry.seq)) })
3308
+ return /* @__PURE__ */ jsxs24("section", { className: "hl-runlog", "aria-label": title, children: [
3309
+ /* @__PURE__ */ jsxs24(
3310
+ PanelHead,
3311
+ {
3312
+ leading: leading != null ? leading : /* @__PURE__ */ jsx37("span", { className: "hl-panel-title", children: title }),
3313
+ 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` : ""),
3314
+ children: [
3315
+ /* @__PURE__ */ jsx37(
3316
+ PanelAction,
3317
+ {
3318
+ icon: /* @__PURE__ */ jsx37(FilterIcon, {}),
3319
+ label: onlySelected && selectedId !== void 0 ? `Only ${label(selectedId)}` : "Selected node",
3320
+ title: selectedId === void 0 ? "Select a node to filter by it" : `Only ${label(selectedId)}`,
3321
+ pressed: onlySelected,
3322
+ disabled: selectedId === void 0 && !onlySelected,
3323
+ onClick: () => {
3324
+ setOnlySelected((value) => !value);
3325
+ }
3326
+ }
3327
+ ),
3328
+ /* @__PURE__ */ jsx37(
3329
+ PanelAction,
3330
+ {
3331
+ icon: /* @__PURE__ */ jsx37(FollowIcon, {}),
3332
+ label: "Follow",
3333
+ title: "Follow the newest event",
3334
+ pressed: follow,
3335
+ onClick: () => {
3336
+ setFollow(!follow);
3337
+ }
3338
+ }
3339
+ ),
3340
+ /* @__PURE__ */ jsx37(
3341
+ PanelAction,
3342
+ {
3343
+ icon: /* @__PURE__ */ jsx37(ClearIcon, {}),
3344
+ label: "Clear",
3345
+ title: "Clear the log",
3346
+ disabled: all.length === 0,
3347
+ onClick: () => {
3348
+ trace.clear();
3349
+ }
3350
+ }
3351
+ )
3352
+ ]
3353
+ }
3354
+ ),
3355
+ entries.length === 0 ? /* @__PURE__ */ jsx37("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__ */ jsx37("ol", { className: "hl-runlog-body hl-mono", ref: bodyRef, onScroll, children: entries.map((entry) => /* @__PURE__ */ jsx37(Row, { entry, label, onChoose: choose }, entry.seq)) })
3295
3356
  ] });
3296
3357
  }
3297
3358
  function Row({
@@ -3301,32 +3362,32 @@ function Row({
3301
3362
  }) {
3302
3363
  switch (entry.kind) {
3303
3364
  case "error":
3304
- return /* @__PURE__ */ jsxs22(Event, { className: "is-error", at: entry.at, onChoose: () => onChoose(entry.nodeId), ms: "", children: [
3305
- /* @__PURE__ */ jsx35(NodePort, { node: label(entry.nodeId), port: "error" }),
3306
- /* @__PURE__ */ jsx35("span", { className: "hl-runlog-value", children: previewValue(entry.error) })
3365
+ return /* @__PURE__ */ jsxs24(Event, { className: "is-error", at: entry.at, onChoose: () => onChoose(entry.nodeId), ms: "", children: [
3366
+ /* @__PURE__ */ jsx37(NodePort, { node: label(entry.nodeId), port: "error" }),
3367
+ /* @__PURE__ */ jsx37("span", { className: "hl-runlog-value", children: previewValue(entry.error) })
3307
3368
  ] });
3308
3369
  case "ended":
3309
- return /* @__PURE__ */ jsxs22(Event, { className: "is-end", at: entry.at, onChoose: () => onChoose(entry.nodeId), ms: "", children: [
3310
- /* @__PURE__ */ jsx35(NodePort, { node: label(entry.nodeId), port: entry.portId }),
3311
- /* @__PURE__ */ jsx35("span", { className: "hl-runlog-value", children: "ended" })
3370
+ return /* @__PURE__ */ jsxs24(Event, { className: "is-end", at: entry.at, onChoose: () => onChoose(entry.nodeId), ms: "", children: [
3371
+ /* @__PURE__ */ jsx37(NodePort, { node: label(entry.nodeId), port: entry.portId }),
3372
+ /* @__PURE__ */ jsx37("span", { className: "hl-runlog-value", children: "ended" })
3312
3373
  ] });
3313
3374
  case "finished":
3314
- return /* @__PURE__ */ jsxs22(Event, { className: "is-end", at: entry.at, onChoose: () => onChoose(entry.nodeId), ms: "", children: [
3315
- /* @__PURE__ */ jsx35(NodePort, { node: label(entry.nodeId), port: "" }),
3316
- /* @__PURE__ */ jsx35("span", { className: "hl-runlog-value", children: "finished" })
3375
+ return /* @__PURE__ */ jsxs24(Event, { className: "is-end", at: entry.at, onChoose: () => onChoose(entry.nodeId), ms: "", children: [
3376
+ /* @__PURE__ */ jsx37(NodePort, { node: label(entry.nodeId), port: "" }),
3377
+ /* @__PURE__ */ jsx37("span", { className: "hl-runlog-value", children: "finished" })
3317
3378
  ] });
3318
3379
  case "dropped":
3319
- return /* @__PURE__ */ jsxs22(Event, { className: "is-dropped", at: entry.at, onChoose: () => onChoose(entry.targetNodeId), ms: "", children: [
3320
- /* @__PURE__ */ jsx35(Route, { entry, label }),
3321
- /* @__PURE__ */ jsxs22("span", { className: "hl-runlog-value", children: [
3380
+ return /* @__PURE__ */ jsxs24(Event, { className: "is-dropped", at: entry.at, onChoose: () => onChoose(entry.targetNodeId), ms: "", children: [
3381
+ /* @__PURE__ */ jsx37(Route, { entry, label }),
3382
+ /* @__PURE__ */ jsxs24("span", { className: "hl-runlog-value", children: [
3322
3383
  "dropped ",
3323
3384
  previewValue(entry.message)
3324
3385
  ] })
3325
3386
  ] });
3326
3387
  case "delivery":
3327
- return /* @__PURE__ */ jsxs22(Event, { className: "", at: entry.at, onChoose: () => onChoose(entry.targetNodeId), ms: formatDuration(entry.duration), children: [
3328
- /* @__PURE__ */ jsx35(Route, { entry, label }),
3329
- /* @__PURE__ */ jsx35("span", { className: "hl-runlog-value", children: previewValue(entry.message) })
3388
+ return /* @__PURE__ */ jsxs24(Event, { className: "", at: entry.at, onChoose: () => onChoose(entry.targetNodeId), ms: formatDuration(entry.duration), children: [
3389
+ /* @__PURE__ */ jsx37(Route, { entry, label }),
3390
+ /* @__PURE__ */ jsx37("span", { className: "hl-runlog-value", children: previewValue(entry.message) })
3330
3391
  ] });
3331
3392
  }
3332
3393
  }
@@ -3337,28 +3398,28 @@ function Event({
3337
3398
  onChoose,
3338
3399
  children
3339
3400
  }) {
3340
- return /* @__PURE__ */ jsx35("li", { className: `hl-runlog-row ${className2}`.trim(), children: /* @__PURE__ */ jsxs22("button", { type: "button", className: "hl-runlog-hit", onClick: onChoose, children: [
3341
- /* @__PURE__ */ jsx35("span", { className: "hl-runlog-at", children: formatElapsed(at) }),
3401
+ return /* @__PURE__ */ jsx37("li", { className: `hl-runlog-row ${className2}`.trim(), children: /* @__PURE__ */ jsxs24("button", { type: "button", className: "hl-runlog-hit", onClick: onChoose, children: [
3402
+ /* @__PURE__ */ jsx37("span", { className: "hl-runlog-at", children: formatElapsed(at) }),
3342
3403
  children,
3343
- /* @__PURE__ */ jsx35("span", { className: "hl-runlog-ms", children: ms })
3404
+ /* @__PURE__ */ jsx37("span", { className: "hl-runlog-ms", children: ms })
3344
3405
  ] }) });
3345
3406
  }
3346
3407
  function NodePort({ node, port }) {
3347
- return /* @__PURE__ */ jsxs22("span", { className: "hl-runlog-route", children: [
3348
- /* @__PURE__ */ jsx35("span", { className: "hl-runlog-node", children: node }),
3349
- port === "" ? null : /* @__PURE__ */ jsx35("span", { className: "hl-runlog-port", children: port })
3408
+ return /* @__PURE__ */ jsxs24("span", { className: "hl-runlog-route", children: [
3409
+ /* @__PURE__ */ jsx37("span", { className: "hl-runlog-node", children: node }),
3410
+ port === "" ? null : /* @__PURE__ */ jsx37("span", { className: "hl-runlog-port", children: port })
3350
3411
  ] });
3351
3412
  }
3352
3413
  function Route({
3353
3414
  entry,
3354
3415
  label
3355
3416
  }) {
3356
- return /* @__PURE__ */ jsxs22("span", { className: "hl-runlog-route", children: [
3357
- /* @__PURE__ */ jsx35("span", { className: "hl-runlog-node", children: label(entry.sourceNodeId) }),
3358
- /* @__PURE__ */ jsx35("span", { className: "hl-runlog-port", children: entry.sourcePortId }),
3359
- /* @__PURE__ */ jsx35("span", { className: "hl-runlog-arrow", "aria-label": "to", children: "\u2192" }),
3360
- /* @__PURE__ */ jsx35("span", { className: "hl-runlog-node", children: label(entry.targetNodeId) }),
3361
- /* @__PURE__ */ jsx35("span", { className: "hl-runlog-port", children: entry.targetPortId })
3417
+ return /* @__PURE__ */ jsxs24("span", { className: "hl-runlog-route", children: [
3418
+ /* @__PURE__ */ jsx37("span", { className: "hl-runlog-node", children: label(entry.sourceNodeId) }),
3419
+ /* @__PURE__ */ jsx37("span", { className: "hl-runlog-port", children: entry.sourcePortId }),
3420
+ /* @__PURE__ */ jsx37("span", { className: "hl-runlog-arrow", "aria-label": "to", children: "\u2192" }),
3421
+ /* @__PURE__ */ jsx37("span", { className: "hl-runlog-node", children: label(entry.targetNodeId) }),
3422
+ /* @__PURE__ */ jsx37("span", { className: "hl-runlog-port", children: entry.targetPortId })
3362
3423
  ] });
3363
3424
  }
3364
3425
  export {
@@ -3367,6 +3428,7 @@ export {
3367
3428
  CanvasChrome,
3368
3429
  CanvasDock,
3369
3430
  Cheatsheet,
3431
+ ClearIcon,
3370
3432
  ConnectionInspector,
3371
3433
  DEFAULT_EDGE_CAPACITY,
3372
3434
  DEFAULT_MIN_CANVAS_HEIGHT,
@@ -3376,7 +3438,9 @@ export {
3376
3438
  EmptyState,
3377
3439
  FOLLOW_SLACK_PX,
3378
3440
  FOOTER_KEY_STEP,
3441
+ FilterIcon,
3379
3442
  FlowEdge,
3443
+ FollowIcon,
3380
3444
  FooterResizer,
3381
3445
  HlangNode,
3382
3446
  InspectorPanel,
@@ -3393,6 +3457,8 @@ export {
3393
3457
  NodeSearch,
3394
3458
  OutlineTree,
3395
3459
  PREVIEW_LENGTH,
3460
+ PanelAction,
3461
+ PanelHead,
3396
3462
  ParamForm,
3397
3463
  PillNodeChrome,
3398
3464
  PortRow,
@@ -3400,6 +3466,7 @@ export {
3400
3466
  PreviewPanel,
3401
3467
  RUN_LOG_CAPACITY,
3402
3468
  RailTabs,
3469
+ ReadOnlyBadge,
3403
3470
  RunButton,
3404
3471
  RunLog,
3405
3472
  RunTrace,