@petrarca/sonnet-graph 0.6.0 → 0.8.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/dist/index.js CHANGED
@@ -182,11 +182,225 @@ var EMPTY_NORMALIZED = {
182
182
  edgeIds: []
183
183
  };
184
184
 
185
- // src/stores/Selection.ts
185
+ // src/graph/extractFromRows.ts
186
+ function isNodeCell(value) {
187
+ return typeof value === "object" && value !== null && "node" in value;
188
+ }
189
+ function isEdgeCell(value) {
190
+ return typeof value === "object" && value !== null && "edge" in value;
191
+ }
192
+ function extractGraphFromRows(rows) {
193
+ const nodesById = /* @__PURE__ */ new Map();
194
+ const edgesById = /* @__PURE__ */ new Map();
195
+ for (const row of rows) {
196
+ for (const cell of row) {
197
+ if (isNodeCell(cell) && cell.node.id_ !== void 0) {
198
+ nodesById.set(cell.node.id_, cell.node);
199
+ } else if (isEdgeCell(cell) && cell.edge.id_ !== void 0) {
200
+ edgesById.set(cell.edge.id_, cell.edge);
201
+ }
202
+ }
203
+ }
204
+ return {
205
+ nodes: [...nodesById.values()],
206
+ edges: [...edgesById.values()],
207
+ isGraph: nodesById.size > 0 || edgesById.size > 0
208
+ };
209
+ }
210
+
211
+ // src/query/QueryEditor.tsx
212
+ import { useRef, useCallback, useEffect, useState } from "react";
213
+ import {
214
+ Alert,
215
+ Button,
216
+ InputGroup,
217
+ InputGroupAddon,
218
+ InputGroupButton,
219
+ InputGroupTextarea,
220
+ SimpleTooltip
221
+ } from "@petrarca/sonnet-ui";
222
+ import { copyTextToClipboard } from "@petrarca/sonnet-core";
223
+ import { Play, Copy } from "lucide-react";
224
+ import { jsx, jsxs } from "react/jsx-runtime";
225
+ var LINE_HEIGHT = 24;
226
+ function CopyErrorButton({
227
+ error,
228
+ query
229
+ }) {
230
+ return /* @__PURE__ */ jsx(SimpleTooltip, { label: "Copy error and query", children: /* @__PURE__ */ jsx(
231
+ Button,
232
+ {
233
+ variant: "ghost",
234
+ size: "icon",
235
+ onClick: () => copyTextToClipboard(`Error: ${error}
236
+
237
+ Query:
238
+ ${query}`),
239
+ className: "ml-2",
240
+ children: /* @__PURE__ */ jsx(Copy, { size: 16 })
241
+ }
242
+ ) });
243
+ }
244
+ function QueryEditor({
245
+ value,
246
+ onExecute,
247
+ onChange,
248
+ isLoading = false,
249
+ error = null,
250
+ placeholder = "Example: MATCH (p)-[e]->(t) RETURN p,e,t LIMIT 10",
251
+ minRows = 1,
252
+ maxRows = 12,
253
+ className,
254
+ renderActions
255
+ }) {
256
+ const textareaRef = useRef(null);
257
+ const [localValue, setLocalValue] = useState(value);
258
+ const handleResize = useCallback(() => {
259
+ const textarea = textareaRef.current;
260
+ if (!textarea) return;
261
+ textarea.style.height = "auto";
262
+ const minH = minRows * LINE_HEIGHT;
263
+ const maxH = maxRows * LINE_HEIGHT;
264
+ const newHeight = Math.min(Math.max(textarea.scrollHeight, minH), maxH);
265
+ textarea.style.height = `${newHeight}px`;
266
+ textarea.style.overflowY = textarea.scrollHeight > maxH ? "auto" : "hidden";
267
+ }, [minRows, maxRows]);
268
+ useEffect(() => {
269
+ setLocalValue(value);
270
+ if (textareaRef.current && textareaRef.current.value !== value) {
271
+ textareaRef.current.value = value;
272
+ }
273
+ setTimeout(handleResize, 0);
274
+ }, [value, handleResize]);
275
+ const handleExecute = useCallback(() => {
276
+ const current = textareaRef.current?.value || "";
277
+ if (!current.trim()) return;
278
+ onExecute(current);
279
+ }, [onExecute]);
280
+ const handleKeyDown = useCallback(
281
+ (e) => {
282
+ if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
283
+ e.preventDefault();
284
+ handleExecute();
285
+ }
286
+ },
287
+ [handleExecute]
288
+ );
289
+ const textareaRefCallback = (element) => {
290
+ textareaRef.current = element;
291
+ if (element) setTimeout(handleResize, 0);
292
+ };
293
+ const handleChange = useCallback(
294
+ (e) => {
295
+ setLocalValue(e.target.value);
296
+ onChange?.(e.target.value);
297
+ setTimeout(handleResize, 0);
298
+ },
299
+ [onChange, handleResize]
300
+ );
301
+ return /* @__PURE__ */ jsxs("div", { className, children: [
302
+ /* @__PURE__ */ jsxs(InputGroup, { className: "!ring-0 !ring-offset-0 focus:!ring-0 has-[[data-slot=input-group-control]:focus-visible]:!ring-0", children: [
303
+ /* @__PURE__ */ jsx(
304
+ InputGroupTextarea,
305
+ {
306
+ ref: textareaRefCallback,
307
+ value: localValue,
308
+ onChange: handleChange,
309
+ onKeyDown: handleKeyDown,
310
+ placeholder,
311
+ className: "resize-none font-mono",
312
+ style: { minHeight: `${minRows * LINE_HEIGHT}px` },
313
+ rows: minRows
314
+ }
315
+ ),
316
+ /* @__PURE__ */ jsx(InputGroupAddon, { align: "inline-end", className: "gap-0", children: renderActions ? renderActions({ onExecute: handleExecute, isLoading }) : /* @__PURE__ */ jsx(
317
+ InputGroupButton,
318
+ {
319
+ onClick: handleExecute,
320
+ disabled: isLoading,
321
+ title: "Execute query (Ctrl+Enter)",
322
+ children: /* @__PURE__ */ jsx(Play, { size: 16 })
323
+ }
324
+ ) })
325
+ ] }),
326
+ error && /* @__PURE__ */ jsx(Alert, { variant: "destructive", className: "p-2 py-1 mt-1", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-row justify-between items-start", children: [
327
+ /* @__PURE__ */ jsx("pre", { className: "text-sm m-0 flex-1 whitespace-pre-wrap break-words md:text-sm", children: error }),
328
+ /* @__PURE__ */ jsx(CopyErrorButton, { error, query: localValue })
329
+ ] }) })
330
+ ] });
331
+ }
332
+
333
+ // src/query/history.ts
186
334
  import { create } from "zustand";
335
+ import { persist } from "zustand/middleware";
336
+ var DEFAULT_LIMIT = 50;
337
+ function createQueryHistoryStore(persistKey, limit = DEFAULT_LIMIT) {
338
+ return create()(
339
+ persist(
340
+ (set, get) => ({
341
+ queries: [],
342
+ add: (query) => {
343
+ const trimmed = query.trim();
344
+ if (!trimmed) return;
345
+ set({
346
+ queries: [
347
+ trimmed,
348
+ ...get().queries.filter((q) => q !== trimmed)
349
+ ].slice(0, limit)
350
+ });
351
+ },
352
+ clear: () => set({ queries: [] })
353
+ }),
354
+ { name: persistKey, version: 1 }
355
+ )
356
+ );
357
+ }
358
+
359
+ // src/query/QueryHistory.tsx
360
+ import { History as HistoryIcon } from "lucide-react";
361
+ import {
362
+ Button as Button2,
363
+ DropdownMenu,
364
+ DropdownMenuContent,
365
+ DropdownMenuItem,
366
+ DropdownMenuSeparator,
367
+ DropdownMenuTrigger
368
+ } from "@petrarca/sonnet-ui";
369
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
370
+ function QueryHistory({
371
+ store,
372
+ onSelect
373
+ }) {
374
+ const queries = store((s) => s.queries);
375
+ const clear = store((s) => s.clear);
376
+ return /* @__PURE__ */ jsxs2(DropdownMenu, { children: [
377
+ /* @__PURE__ */ jsx2(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx2(
378
+ Button2,
379
+ {
380
+ variant: "ghost",
381
+ size: "icon",
382
+ title: "Query history",
383
+ disabled: queries.length === 0,
384
+ children: /* @__PURE__ */ jsx2(HistoryIcon, { size: 16 })
385
+ }
386
+ ) }),
387
+ /* @__PURE__ */ jsxs2(DropdownMenuContent, { align: "end", className: "max-w-[560px]", children: [
388
+ queries.map((q) => /* @__PURE__ */ jsx2(DropdownMenuItem, { onClick: () => onSelect(q), children: /* @__PURE__ */ jsx2("span", { className: "truncate font-mono text-xs", children: q }) }, q)),
389
+ queries.length > 0 && /* @__PURE__ */ jsx2(DropdownMenuSeparator, {}),
390
+ /* @__PURE__ */ jsx2(DropdownMenuItem, { onClick: clear, disabled: queries.length === 0, children: "Clear history" })
391
+ ] })
392
+ ] });
393
+ }
394
+
395
+ // src/stores/Selection.ts
396
+ import { create as create2 } from "zustand";
187
397
  import { devLog } from "@petrarca/sonnet-core";
398
+ var MAX_NODE_SELECTION = 2;
399
+ function evictToMax(selected, id, max = MAX_NODE_SELECTION) {
400
+ return [...selected.filter((existing) => existing !== id), id].slice(-max);
401
+ }
188
402
  function createSelectionStore() {
189
- return create()((set, get) => ({
403
+ return create2()((set, get) => ({
190
404
  selectedNodeIds: [],
191
405
  setSelectedNodeIds: (ids) => {
192
406
  const prev = get().selectedNodeIds;
@@ -213,10 +427,10 @@ function createSelectionStore() {
213
427
  var useSelection = createSelectionStore();
214
428
 
215
429
  // src/stores/GraphData.ts
216
- import { create as create2 } from "zustand";
430
+ import { create as create3 } from "zustand";
217
431
  import { devLog as devLog2 } from "@petrarca/sonnet-core";
218
432
  function createGraphDataStore() {
219
- return create2()((set, get) => ({
433
+ return create3()((set, get) => ({
220
434
  nodes: [],
221
435
  edges: [],
222
436
  stats: void 0,
@@ -324,7 +538,7 @@ function createGraphDataStore() {
324
538
  var useGraphData = createGraphDataStore();
325
539
 
326
540
  // src/stores/GraphFilter.ts
327
- import { create as create3 } from "zustand";
541
+ import { create as create4 } from "zustand";
328
542
  function calculateNodeLabelUpdate(affectedLabels, currentNodeFilters, allNodesDisabled) {
329
543
  if (affectedLabels.length === 0) return null;
330
544
  if (allNodesDisabled) {
@@ -362,7 +576,7 @@ function handleToggleFilter(label, current, exists, getNodeLabelUpdate, set) {
362
576
  set({ edgeLabelFilters: next, ...nodeUpdate });
363
577
  }
364
578
  function createGraphFilterStore() {
365
- return create3()((set, get) => ({
579
+ return create4()((set, get) => ({
366
580
  nodeLabelFilters: [],
367
581
  allLabelsDisabled: false,
368
582
  edgeLabelFilters: [],
@@ -438,7 +652,7 @@ function createGraphFilterStore() {
438
652
  var useGraphFilter = createGraphFilterStore();
439
653
 
440
654
  // src/stores/GraphExploration.ts
441
- import { create as create4 } from "zustand";
655
+ import { create as create5 } from "zustand";
442
656
  import { devLog as devLog3, errorLog } from "@petrarca/sonnet-core";
443
657
  import { toast } from "sonner";
444
658
  function collectReachable(startId, direction, allEdges, includeStart) {
@@ -565,7 +779,7 @@ function mergeExpandedLabelsIntoFilters(stores, enrichedNodes, nodeId2) {
565
779
  }
566
780
  function createGraphExplorationStore(deps) {
567
781
  const stores = deps;
568
- return create4()((set, get) => ({
782
+ return create5()((set, get) => ({
569
783
  focusedNodeIds: /* @__PURE__ */ new Set(),
570
784
  isFocusMode: false,
571
785
  hiddenElementIds: /* @__PURE__ */ new Set(),
@@ -974,7 +1188,7 @@ function createGraphExplorationStore(deps) {
974
1188
  }
975
1189
 
976
1190
  // src/stores/GraphFocus.ts
977
- import { create as create5 } from "zustand";
1191
+ import { create as create6 } from "zustand";
978
1192
  import { devLog as devLog4 } from "@petrarca/sonnet-core";
979
1193
  function buildStore() {
980
1194
  return (set, get) => ({
@@ -1008,13 +1222,13 @@ function buildStore() {
1008
1222
  });
1009
1223
  }
1010
1224
  function createGraphFocusStore() {
1011
- return create5()(buildStore());
1225
+ return create6()(buildStore());
1012
1226
  }
1013
1227
  var useGraphFocus = createGraphFocusStore();
1014
1228
  var GraphFocus_default = useGraphFocus;
1015
1229
 
1016
1230
  // src/stores/ActionPanelStore.ts
1017
- import { create as create6 } from "zustand";
1231
+ import { create as create7 } from "zustand";
1018
1232
  import { devLog as devLog5 } from "@petrarca/sonnet-core";
1019
1233
  function buildStore2() {
1020
1234
  return (set, get) => ({
@@ -1035,7 +1249,7 @@ function buildStore2() {
1035
1249
  });
1036
1250
  }
1037
1251
  function createActionPanelStore() {
1038
- return create6()(buildStore2());
1252
+ return create7()(buildStore2());
1039
1253
  }
1040
1254
  var useActionPanelStore = createActionPanelStore();
1041
1255
 
@@ -1046,7 +1260,7 @@ function useGraphStores() {
1046
1260
  const ctx = useContext(GraphStoresContext);
1047
1261
  if (!ctx) {
1048
1262
  throw new Error(
1049
- "useGraphStores must be used within a GraphStoresProvider. Wrap your graph UI with <GraphStoresContext.Provider value={...}>."
1263
+ "useGraphStores must be used within a GraphStoresContext.Provider. Wrap your graph UI with <GraphStoresContext.Provider value={...}>."
1050
1264
  );
1051
1265
  }
1052
1266
  return ctx;
@@ -1063,31 +1277,35 @@ function useWorkspaceGraphExploration() {
1063
1277
  function useWorkspaceSelection() {
1064
1278
  return useGraphStores().selection;
1065
1279
  }
1280
+ function useWorkspaceGraphFocus() {
1281
+ return useGraphStores().graphFocus;
1282
+ }
1066
1283
 
1067
1284
  // src/hooks/useStableRef.ts
1068
- import { useCallback, useRef } from "react";
1285
+ import { useCallback as useCallback2, useRef as useRef2 } from "react";
1069
1286
  function useStableRef(fn) {
1070
- const ref = useRef(fn);
1287
+ const ref = useRef2(fn);
1071
1288
  ref.current = fn;
1072
- return useCallback(
1289
+ return useCallback2(
1073
1290
  ((...args) => ref.current?.(...args)),
1074
1291
  []
1075
1292
  );
1076
1293
  }
1077
1294
 
1078
1295
  // src/hooks/useLiveGraphData.ts
1079
- import { useMemo, useEffect } from "react";
1296
+ import { useMemo, useEffect as useEffect2 } from "react";
1080
1297
  import { devLog as devLog6 } from "@petrarca/sonnet-core";
1081
1298
  function useLiveGraphData() {
1082
1299
  const useGraphFilter2 = useWorkspaceGraphFilter();
1083
1300
  const useGraphData2 = useWorkspaceGraphData();
1084
1301
  const useGraphExploration = useWorkspaceGraphExploration();
1302
+ const useGraphFocus2 = useWorkspaceGraphFocus();
1085
1303
  const lastDataChangeTime = useGraphFilter2((s) => s.lastDataChangeTime);
1086
1304
  const filterLabels = useGraphFilter2((s) => s.nodeLabelFilters);
1087
1305
  const allLabelsDisabled = useGraphFilter2((s) => s.allLabelsDisabled);
1088
1306
  const edgeFilterLabels = useGraphFilter2((s) => s.edgeLabelFilters);
1089
1307
  const allEdgeLabelsDisabled = useGraphFilter2((s) => s.allEdgeLabelsDisabled);
1090
- const overrideElementIds = GraphFocus_default((s) => s.overrideElementIds);
1308
+ const overrideElementIds = useGraphFocus2((s) => s.overrideElementIds);
1091
1309
  const isFocusMode = useGraphExploration((s) => s.isFocusMode);
1092
1310
  const focusedNodeIds = useGraphExploration((s) => s.focusedNodeIds);
1093
1311
  const hiddenElementIds = useGraphExploration((s) => s.hiddenElementIds);
@@ -1103,9 +1321,9 @@ function useLiveGraphData() {
1103
1321
  focusedCount: focusedNodeIds.size,
1104
1322
  hiddenCount: hiddenElementIds.size
1105
1323
  });
1106
- useEffect(() => {
1107
- GraphFocus_default.getState().clearOverrides();
1108
- }, [lastDataChangeTime]);
1324
+ useEffect2(() => {
1325
+ useGraphFocus2.getState().clearOverrides();
1326
+ }, [lastDataChangeTime, useGraphFocus2]);
1109
1327
  const allLabelCounts = useMemo(() => {
1110
1328
  if (isValueSet) return {};
1111
1329
  const counts = {};
@@ -1301,10 +1519,11 @@ function useLiveGraphData() {
1301
1519
  edges: availableEdges.length
1302
1520
  };
1303
1521
  }, [stats, availableNodes.length, availableEdges.length]);
1304
- useEffect(() => {
1305
- const { clearOverrides } = GraphFocus_default.getState();
1522
+ useEffect2(() => {
1523
+ const { clearOverrides } = useGraphFocus2.getState();
1306
1524
  clearOverrides();
1307
1525
  }, [
1526
+ useGraphFocus2,
1308
1527
  filterLabels,
1309
1528
  allLabelsDisabled,
1310
1529
  edgeFilterLabels,
@@ -1439,30 +1658,30 @@ function useGraphRenderer() {
1439
1658
  }
1440
1659
 
1441
1660
  // src/hooks/graphRendererProvider.tsx
1442
- import { useState, useCallback as useCallback2 } from "react";
1443
- import { jsx } from "react/jsx-runtime";
1661
+ import { useState as useState2, useCallback as useCallback3 } from "react";
1662
+ import { jsx as jsx3 } from "react/jsx-runtime";
1444
1663
  function GraphRendererProvider({
1445
1664
  children,
1446
1665
  defaultRenderer = "cytoscape",
1447
1666
  onRendererChange
1448
1667
  }) {
1449
- const [renderer, setRendererState] = useState(defaultRenderer);
1450
- const setRenderer = useCallback2(
1668
+ const [renderer, setRendererState] = useState2(defaultRenderer);
1669
+ const setRenderer = useCallback3(
1451
1670
  (newRenderer) => {
1452
1671
  setRendererState(newRenderer);
1453
1672
  onRendererChange?.(newRenderer);
1454
1673
  },
1455
1674
  [onRendererChange]
1456
1675
  );
1457
- return /* @__PURE__ */ jsx(GraphRendererContext.Provider, { value: { renderer, setRenderer }, children });
1676
+ return /* @__PURE__ */ jsx3(GraphRendererContext.Provider, { value: { renderer, setRenderer }, children });
1458
1677
  }
1459
1678
 
1460
1679
  // src/components/rea/ReaGraph.tsx
1461
1680
  import {
1462
- useEffect as useEffect3,
1463
- useRef as useRef3,
1464
- useState as useState5,
1465
- useCallback as useCallback4
1681
+ useEffect as useEffect4,
1682
+ useRef as useRef4,
1683
+ useState as useState6,
1684
+ useCallback as useCallback5
1466
1685
  } from "react";
1467
1686
  import { devLog as devLog8 } from "@petrarca/sonnet-core";
1468
1687
  import {
@@ -1507,43 +1726,43 @@ import { cn } from "@petrarca/sonnet-core";
1507
1726
 
1508
1727
  // src/components/rea/LayoutAndCameraControls.tsx
1509
1728
  import {
1510
- Button as Button2,
1511
- DropdownMenu as DropdownMenu2,
1512
- DropdownMenuContent as DropdownMenuContent2,
1513
- DropdownMenuItem as DropdownMenuItem2,
1729
+ Button as Button4,
1730
+ DropdownMenu as DropdownMenu3,
1731
+ DropdownMenuContent as DropdownMenuContent3,
1732
+ DropdownMenuItem as DropdownMenuItem3,
1514
1733
  DropdownMenuLabel,
1515
- DropdownMenuSeparator,
1516
- DropdownMenuTrigger as DropdownMenuTrigger2,
1517
- SimpleTooltip as SimpleTooltip2
1734
+ DropdownMenuSeparator as DropdownMenuSeparator2,
1735
+ DropdownMenuTrigger as DropdownMenuTrigger3,
1736
+ SimpleTooltip as SimpleTooltip3
1518
1737
  } from "@petrarca/sonnet-ui";
1519
1738
  import { LayoutPanelTop, Maximize, Shrink, ArrowUp, Eye } from "lucide-react";
1520
- import { useState as useState2 } from "react";
1739
+ import { useState as useState3 } from "react";
1521
1740
 
1522
1741
  // src/components/shared/RendererDropdown.tsx
1523
1742
  import {
1524
- Button,
1525
- DropdownMenu,
1526
- DropdownMenuContent,
1527
- DropdownMenuItem,
1528
- DropdownMenuTrigger,
1529
- SimpleTooltip
1743
+ Button as Button3,
1744
+ DropdownMenu as DropdownMenu2,
1745
+ DropdownMenuContent as DropdownMenuContent2,
1746
+ DropdownMenuItem as DropdownMenuItem2,
1747
+ DropdownMenuTrigger as DropdownMenuTrigger2,
1748
+ SimpleTooltip as SimpleTooltip2
1530
1749
  } from "@petrarca/sonnet-ui";
1531
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
1750
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1532
1751
  function RendererDropdown() {
1533
1752
  const { renderer, setRenderer } = useGraphRenderer();
1534
- return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1535
- /* @__PURE__ */ jsx2(SimpleTooltip, { label: "Renderer", side: "left", children: /* @__PURE__ */ jsx2(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx2(Button, { variant: "ghost", size: "icon", children: renderer === "reagraph" ? "RG" : "CY" }) }) }),
1536
- /* @__PURE__ */ jsxs(DropdownMenuContent, { side: "left", align: "start", children: [
1537
- /* @__PURE__ */ jsx2(
1538
- DropdownMenuItem,
1753
+ return /* @__PURE__ */ jsxs3(DropdownMenu2, { children: [
1754
+ /* @__PURE__ */ jsx4(SimpleTooltip2, { label: "Renderer", side: "left", children: /* @__PURE__ */ jsx4(DropdownMenuTrigger2, { asChild: true, children: /* @__PURE__ */ jsx4(Button3, { variant: "ghost", size: "icon", children: renderer === "reagraph" ? "RG" : "CY" }) }) }),
1755
+ /* @__PURE__ */ jsxs3(DropdownMenuContent2, { side: "left", align: "start", children: [
1756
+ /* @__PURE__ */ jsx4(
1757
+ DropdownMenuItem2,
1539
1758
  {
1540
1759
  onClick: () => setRenderer("reagraph"),
1541
1760
  className: renderer === "reagraph" ? "text-blue-600" : "",
1542
1761
  children: "Reagraph"
1543
1762
  }
1544
1763
  ),
1545
- /* @__PURE__ */ jsx2(
1546
- DropdownMenuItem,
1764
+ /* @__PURE__ */ jsx4(
1765
+ DropdownMenuItem2,
1547
1766
  {
1548
1767
  onClick: () => setRenderer("cytoscape"),
1549
1768
  className: renderer === "cytoscape" ? "text-blue-600" : "",
@@ -1555,46 +1774,46 @@ function RendererDropdown() {
1555
1774
  }
1556
1775
 
1557
1776
  // src/components/rea/LayoutAndCameraControls.tsx
1558
- import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1777
+ import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1559
1778
  function PathSelectionDropdown({
1560
1779
  setPathSelectionType
1561
1780
  }) {
1562
- return /* @__PURE__ */ jsxs2(DropdownMenu2, { children: [
1563
- /* @__PURE__ */ jsx3(SimpleTooltip2, { label: "Path Selection Type", side: "left", children: /* @__PURE__ */ jsx3(DropdownMenuTrigger2, { asChild: true, children: /* @__PURE__ */ jsx3(Button2, { variant: "ghost", size: "icon", children: /* @__PURE__ */ jsx3(ArrowUp, {}) }) }) }),
1564
- /* @__PURE__ */ jsxs2(DropdownMenuContent2, { side: "left", align: "start", children: [
1565
- /* @__PURE__ */ jsx3(DropdownMenuItem2, { onClick: () => setPathSelectionType("direct"), children: "None" }),
1566
- /* @__PURE__ */ jsx3(DropdownMenuItem2, { onClick: () => setPathSelectionType("all"), children: "All Paths" }),
1567
- /* @__PURE__ */ jsx3(DropdownMenuItem2, { onClick: () => setPathSelectionType("in"), children: "Inbound" }),
1568
- /* @__PURE__ */ jsx3(DropdownMenuItem2, { onClick: () => setPathSelectionType("out"), children: "Outbound" })
1781
+ return /* @__PURE__ */ jsxs4(DropdownMenu3, { children: [
1782
+ /* @__PURE__ */ jsx5(SimpleTooltip3, { label: "Path Selection Type", side: "left", children: /* @__PURE__ */ jsx5(DropdownMenuTrigger3, { asChild: true, children: /* @__PURE__ */ jsx5(Button4, { variant: "ghost", size: "icon", children: /* @__PURE__ */ jsx5(ArrowUp, {}) }) }) }),
1783
+ /* @__PURE__ */ jsxs4(DropdownMenuContent3, { side: "left", align: "start", children: [
1784
+ /* @__PURE__ */ jsx5(DropdownMenuItem3, { onClick: () => setPathSelectionType("direct"), children: "None" }),
1785
+ /* @__PURE__ */ jsx5(DropdownMenuItem3, { onClick: () => setPathSelectionType("all"), children: "All Paths" }),
1786
+ /* @__PURE__ */ jsx5(DropdownMenuItem3, { onClick: () => setPathSelectionType("in"), children: "Inbound" }),
1787
+ /* @__PURE__ */ jsx5(DropdownMenuItem3, { onClick: () => setPathSelectionType("out"), children: "Outbound" })
1569
1788
  ] })
1570
1789
  ] });
1571
1790
  }
1572
1791
  function LayoutDropdown({ updateLayout }) {
1573
- const [showTooltip, setShowTooltip] = useState2(true);
1792
+ const [showTooltip, setShowTooltip] = useState3(true);
1574
1793
  const handleSelect = (layout) => {
1575
1794
  updateLayout(layout);
1576
1795
  setShowTooltip(false);
1577
1796
  setTimeout(() => setShowTooltip(true), 500);
1578
1797
  };
1579
- const trigger = /* @__PURE__ */ jsx3(DropdownMenuTrigger2, { asChild: true, children: /* @__PURE__ */ jsx3(Button2, { variant: "ghost", size: "icon", children: /* @__PURE__ */ jsx3(LayoutPanelTop, {}) }) });
1580
- return /* @__PURE__ */ jsxs2(DropdownMenu2, { children: [
1581
- showTooltip ? /* @__PURE__ */ jsx3(SimpleTooltip2, { label: "Layout", side: "left", children: trigger }) : trigger,
1582
- /* @__PURE__ */ jsxs2(DropdownMenuContent2, { side: "left", align: "start", children: [
1583
- /* @__PURE__ */ jsx3(DropdownMenuLabel, { children: "Force-directed Layouts" }),
1584
- /* @__PURE__ */ jsx3(DropdownMenuItem2, { onClick: () => handleSelect("forceDirected2d"), children: "Force Directed" }),
1585
- /* @__PURE__ */ jsx3(DropdownMenuItem2, { onClick: () => handleSelect("forceatlas2"), children: "Force Atlas 2" }),
1586
- /* @__PURE__ */ jsx3(DropdownMenuSeparator, {}),
1587
- /* @__PURE__ */ jsx3(DropdownMenuLabel, { children: "Tree Layouts" }),
1588
- /* @__PURE__ */ jsx3(DropdownMenuItem2, { onClick: () => handleSelect("treeTd2d"), children: "Tree Top-Down" }),
1589
- /* @__PURE__ */ jsx3(DropdownMenuItem2, { onClick: () => handleSelect("treeLr2d"), children: "Tree Left-Right" }),
1590
- /* @__PURE__ */ jsx3(DropdownMenuSeparator, {}),
1591
- /* @__PURE__ */ jsx3(DropdownMenuLabel, { children: "Circular & Radial Layouts" }),
1592
- /* @__PURE__ */ jsx3(DropdownMenuItem2, { onClick: () => handleSelect("circular2d"), children: "Circular" }),
1593
- /* @__PURE__ */ jsx3(DropdownMenuItem2, { onClick: () => handleSelect("radialOut2d"), children: "Radial Out" }),
1594
- /* @__PURE__ */ jsx3(DropdownMenuItem2, { onClick: () => handleSelect("concentric2d"), children: "Concentric" }),
1595
- /* @__PURE__ */ jsx3(DropdownMenuSeparator, {}),
1596
- /* @__PURE__ */ jsx3(DropdownMenuLabel, { children: "Utility Layouts" }),
1597
- /* @__PURE__ */ jsx3(DropdownMenuItem2, { onClick: () => handleSelect("nooverlap"), children: "No Overlap" })
1798
+ const trigger = /* @__PURE__ */ jsx5(DropdownMenuTrigger3, { asChild: true, children: /* @__PURE__ */ jsx5(Button4, { variant: "ghost", size: "icon", children: /* @__PURE__ */ jsx5(LayoutPanelTop, {}) }) });
1799
+ return /* @__PURE__ */ jsxs4(DropdownMenu3, { children: [
1800
+ showTooltip ? /* @__PURE__ */ jsx5(SimpleTooltip3, { label: "Layout", side: "left", children: trigger }) : trigger,
1801
+ /* @__PURE__ */ jsxs4(DropdownMenuContent3, { side: "left", align: "start", children: [
1802
+ /* @__PURE__ */ jsx5(DropdownMenuLabel, { children: "Force-directed Layouts" }),
1803
+ /* @__PURE__ */ jsx5(DropdownMenuItem3, { onClick: () => handleSelect("forceDirected2d"), children: "Force Directed" }),
1804
+ /* @__PURE__ */ jsx5(DropdownMenuItem3, { onClick: () => handleSelect("forceatlas2"), children: "Force Atlas 2" }),
1805
+ /* @__PURE__ */ jsx5(DropdownMenuSeparator2, {}),
1806
+ /* @__PURE__ */ jsx5(DropdownMenuLabel, { children: "Tree Layouts" }),
1807
+ /* @__PURE__ */ jsx5(DropdownMenuItem3, { onClick: () => handleSelect("treeTd2d"), children: "Tree Top-Down" }),
1808
+ /* @__PURE__ */ jsx5(DropdownMenuItem3, { onClick: () => handleSelect("treeLr2d"), children: "Tree Left-Right" }),
1809
+ /* @__PURE__ */ jsx5(DropdownMenuSeparator2, {}),
1810
+ /* @__PURE__ */ jsx5(DropdownMenuLabel, { children: "Circular & Radial Layouts" }),
1811
+ /* @__PURE__ */ jsx5(DropdownMenuItem3, { onClick: () => handleSelect("circular2d"), children: "Circular" }),
1812
+ /* @__PURE__ */ jsx5(DropdownMenuItem3, { onClick: () => handleSelect("radialOut2d"), children: "Radial Out" }),
1813
+ /* @__PURE__ */ jsx5(DropdownMenuItem3, { onClick: () => handleSelect("concentric2d"), children: "Concentric" }),
1814
+ /* @__PURE__ */ jsx5(DropdownMenuSeparator2, {}),
1815
+ /* @__PURE__ */ jsx5(DropdownMenuLabel, { children: "Utility Layouts" }),
1816
+ /* @__PURE__ */ jsx5(DropdownMenuItem3, { onClick: () => handleSelect("nooverlap"), children: "No Overlap" })
1598
1817
  ] })
1599
1818
  ] });
1600
1819
  }
@@ -1606,15 +1825,15 @@ function ViewControls({
1606
1825
  isFocusMode,
1607
1826
  onResetView
1608
1827
  }) {
1609
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
1610
- /* @__PURE__ */ jsx3(SimpleTooltip2, { label: "Fullscreen", side: "left", children: /* @__PURE__ */ jsx3(Button2, { variant: "ghost", size: "icon", onClick: toggleFullscreen, children: !fullscreen ? /* @__PURE__ */ jsx3(Maximize, {}) : /* @__PURE__ */ jsx3(Shrink, {}) }) }),
1611
- /* @__PURE__ */ jsx3(SimpleTooltip2, { label: "Fit to screen", side: "left", children: /* @__PURE__ */ jsx3(Button2, { variant: "ghost", size: "icon", onClick: fitToScreen, children: /* @__PURE__ */ jsx3(Shrink, {}) }) }),
1612
- (hasHiddenElements || isFocusMode) && onResetView && /* @__PURE__ */ jsx3(
1613
- SimpleTooltip2,
1828
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
1829
+ /* @__PURE__ */ jsx5(SimpleTooltip3, { label: "Fullscreen", side: "left", children: /* @__PURE__ */ jsx5(Button4, { variant: "ghost", size: "icon", onClick: toggleFullscreen, children: !fullscreen ? /* @__PURE__ */ jsx5(Maximize, {}) : /* @__PURE__ */ jsx5(Shrink, {}) }) }),
1830
+ /* @__PURE__ */ jsx5(SimpleTooltip3, { label: "Fit to screen", side: "left", children: /* @__PURE__ */ jsx5(Button4, { variant: "ghost", size: "icon", onClick: fitToScreen, children: /* @__PURE__ */ jsx5(Shrink, {}) }) }),
1831
+ (hasHiddenElements || isFocusMode) && onResetView && /* @__PURE__ */ jsx5(
1832
+ SimpleTooltip3,
1614
1833
  {
1615
1834
  label: isFocusMode ? "Reset Focus" : "Show All Hidden",
1616
1835
  side: "left",
1617
- children: /* @__PURE__ */ jsx3(Button2, { variant: "ghost", size: "icon", onClick: onResetView, children: /* @__PURE__ */ jsx3(Eye, {}) })
1836
+ children: /* @__PURE__ */ jsx5(Button4, { variant: "ghost", size: "icon", onClick: onResetView, children: /* @__PURE__ */ jsx5(Eye, {}) })
1618
1837
  }
1619
1838
  )
1620
1839
  ] });
@@ -1630,9 +1849,9 @@ function LayoutAndCameraControls({
1630
1849
  onResetView,
1631
1850
  extraToolbarContent
1632
1851
  }) {
1633
- return /* @__PURE__ */ jsxs2("div", { className: "absolute right-2 top-2 flex flex-col border", children: [
1634
- /* @__PURE__ */ jsx3(RendererDropdown, {}),
1635
- /* @__PURE__ */ jsx3(
1852
+ return /* @__PURE__ */ jsxs4("div", { className: "absolute right-2 top-2 flex flex-col border", children: [
1853
+ /* @__PURE__ */ jsx5(RendererDropdown, {}),
1854
+ /* @__PURE__ */ jsx5(
1636
1855
  ViewControls,
1637
1856
  {
1638
1857
  fullscreen,
@@ -1643,9 +1862,9 @@ function LayoutAndCameraControls({
1643
1862
  onResetView
1644
1863
  }
1645
1864
  ),
1646
- /* @__PURE__ */ jsx3(PathSelectionDropdown, { setPathSelectionType }),
1865
+ /* @__PURE__ */ jsx5(PathSelectionDropdown, { setPathSelectionType }),
1647
1866
  extraToolbarContent,
1648
- /* @__PURE__ */ jsx3(LayoutDropdown, { updateLayout })
1867
+ /* @__PURE__ */ jsx5(LayoutDropdown, { updateLayout })
1649
1868
  ] });
1650
1869
  }
1651
1870
  var LayoutAndCameraControls_default = LayoutAndCameraControls;
@@ -1655,10 +1874,10 @@ import { Card as Card2 } from "@petrarca/sonnet-ui";
1655
1874
 
1656
1875
  // src/components/rea/NodeHoverInfo.tsx
1657
1876
  import { Card, Separator } from "@petrarca/sonnet-ui";
1658
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1877
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1659
1878
  function NodeHoverInfo({ node }) {
1660
- return /* @__PURE__ */ jsx4(Card, { className: "absolute top-2 left-1/2 -translate-x-1/2 bg-white shadow-md p-2", children: /* @__PURE__ */ jsxs3("div", { className: "flex flex-row items-center gap-2", children: [
1661
- /* @__PURE__ */ jsx4(
1879
+ return /* @__PURE__ */ jsx6(Card, { className: "absolute top-2 left-1/2 -translate-x-1/2 bg-white shadow-md p-2", children: /* @__PURE__ */ jsxs5("div", { className: "flex flex-row items-center gap-2", children: [
1880
+ /* @__PURE__ */ jsx6(
1662
1881
  Separator,
1663
1882
  {
1664
1883
  orientation: "vertical",
@@ -1667,43 +1886,43 @@ function NodeHoverInfo({ node }) {
1667
1886
  style: { backgroundColor: node.fill }
1668
1887
  }
1669
1888
  ),
1670
- /* @__PURE__ */ jsxs3("div", { children: [
1671
- /* @__PURE__ */ jsxs3("p", { children: [
1672
- /* @__PURE__ */ jsxs3("strong", { children: [
1889
+ /* @__PURE__ */ jsxs5("div", { children: [
1890
+ /* @__PURE__ */ jsxs5("p", { children: [
1891
+ /* @__PURE__ */ jsxs5("strong", { children: [
1673
1892
  node.data.label_,
1674
1893
  ": "
1675
1894
  ] }),
1676
1895
  node.label
1677
1896
  ] }),
1678
- /* @__PURE__ */ jsx4("p", { className: "text-sm text-muted-foreground", children: node.data.id_ })
1897
+ /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: node.data.id_ })
1679
1898
  ] })
1680
1899
  ] }) });
1681
1900
  }
1682
1901
  var NodeHoverInfo_default = NodeHoverInfo;
1683
1902
 
1684
1903
  // src/components/rea/ReaContextMenu.tsx
1685
- import React3, { useState as useState4 } from "react";
1904
+ import React4, { useState as useState5 } from "react";
1686
1905
  import { devLog as devLog7 } from "@petrarca/sonnet-core";
1687
1906
 
1688
1907
  // src/components/rea/ExpandOptionsPanel.tsx
1689
- import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
1908
+ import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
1690
1909
  import { Checkbox, Input, Label } from "@petrarca/sonnet-ui";
1691
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1910
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1692
1911
  function ExpandOptionsPanel({
1693
1912
  nodeLabel,
1694
1913
  onExpand,
1695
1914
  onBack,
1696
1915
  getEdgeTypes
1697
1916
  }) {
1698
- const [direction, setDirection] = useState3("both");
1699
- const [limit, setLimit] = useState3(100);
1700
- const [edgeTypes, setEdgeTypes] = useState3([]);
1701
- const [selectedTypes, setSelectedTypes] = useState3([]);
1702
- const [loading, setLoading] = useState3(!!getEdgeTypes);
1703
- const [offset, setOffset] = useState3({ x: 0, y: 0 });
1704
- const expandBtnRef = useRef2(null);
1705
- const dragRef = useRef2(null);
1706
- const onPointerDown = useCallback3(
1917
+ const [direction, setDirection] = useState4("both");
1918
+ const [limit, setLimit] = useState4(100);
1919
+ const [edgeTypes, setEdgeTypes] = useState4([]);
1920
+ const [selectedTypes, setSelectedTypes] = useState4([]);
1921
+ const [loading, setLoading] = useState4(!!getEdgeTypes);
1922
+ const [offset, setOffset] = useState4({ x: 0, y: 0 });
1923
+ const expandBtnRef = useRef3(null);
1924
+ const dragRef = useRef3(null);
1925
+ const onPointerDown = useCallback4(
1707
1926
  (e) => {
1708
1927
  if (e.target.closest("button")) return;
1709
1928
  e.preventDefault();
@@ -1717,16 +1936,16 @@ function ExpandOptionsPanel({
1717
1936
  },
1718
1937
  [offset]
1719
1938
  );
1720
- const onPointerMove = useCallback3((e) => {
1939
+ const onPointerMove = useCallback4((e) => {
1721
1940
  if (!dragRef.current) return;
1722
1941
  const dx = e.clientX - dragRef.current.startX;
1723
1942
  const dy = e.clientY - dragRef.current.startY;
1724
1943
  setOffset({ x: dragRef.current.origX + dx, y: dragRef.current.origY + dy });
1725
1944
  }, []);
1726
- const onPointerUp = useCallback3(() => {
1945
+ const onPointerUp = useCallback4(() => {
1727
1946
  dragRef.current = null;
1728
1947
  }, []);
1729
- useEffect2(() => {
1948
+ useEffect3(() => {
1730
1949
  if (!getEdgeTypes) return;
1731
1950
  setLoading(true);
1732
1951
  getEdgeTypes().then((types) => {
@@ -1751,18 +1970,18 @@ function ExpandOptionsPanel({
1751
1970
  };
1752
1971
  const allSelected = selectedTypes.length === edgeTypes.length;
1753
1972
  const noneSelected = edgeTypes.length > 0 && selectedTypes.length === 0;
1754
- useEffect2(() => {
1973
+ useEffect3(() => {
1755
1974
  if (!loading && !noneSelected) {
1756
1975
  expandBtnRef.current?.focus();
1757
1976
  }
1758
1977
  }, [loading, noneSelected]);
1759
- return /* @__PURE__ */ jsxs4(
1978
+ return /* @__PURE__ */ jsxs6(
1760
1979
  "div",
1761
1980
  {
1762
1981
  className: "bg-background border border-border rounded-md shadow-md p-2.5 min-w-48 space-y-2",
1763
1982
  style: { transform: `translate(${offset.x}px, ${offset.y}px)` },
1764
1983
  children: [
1765
- /* @__PURE__ */ jsxs4(
1984
+ /* @__PURE__ */ jsxs6(
1766
1985
  "div",
1767
1986
  {
1768
1987
  className: "flex items-center justify-between gap-2 cursor-grab active:cursor-grabbing select-none",
@@ -1770,7 +1989,7 @@ function ExpandOptionsPanel({
1770
1989
  onPointerMove,
1771
1990
  onPointerUp,
1772
1991
  children: [
1773
- /* @__PURE__ */ jsx5(
1992
+ /* @__PURE__ */ jsx7(
1774
1993
  "button",
1775
1994
  {
1776
1995
  className: "text-[11px] text-muted-foreground hover:text-foreground cursor-pointer",
@@ -1778,13 +1997,13 @@ function ExpandOptionsPanel({
1778
1997
  children: "Back"
1779
1998
  }
1780
1999
  ),
1781
- /* @__PURE__ */ jsx5("span", { className: "text-[11px] font-medium text-foreground truncate max-w-32", children: nodeLabel })
2000
+ /* @__PURE__ */ jsx7("span", { className: "text-[11px] font-medium text-foreground truncate max-w-32", children: nodeLabel })
1782
2001
  ]
1783
2002
  }
1784
2003
  ),
1785
- /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
1786
- /* @__PURE__ */ jsx5(Label, { className: "text-[11px] text-muted-foreground", children: "Direction" }),
1787
- /* @__PURE__ */ jsx5("div", { className: "flex gap-0.5", children: ["both", "out", "in"].map((dir) => /* @__PURE__ */ jsx5(
2004
+ /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
2005
+ /* @__PURE__ */ jsx7(Label, { className: "text-[11px] text-muted-foreground", children: "Direction" }),
2006
+ /* @__PURE__ */ jsx7("div", { className: "flex gap-0.5", children: ["both", "out", "in"].map((dir) => /* @__PURE__ */ jsx7(
1788
2007
  "button",
1789
2008
  {
1790
2009
  className: `flex-1 px-2 py-1 text-[11px] capitalize rounded border cursor-pointer ${direction === dir ? "bg-primary/10 text-primary border-primary/40 font-medium" : "bg-background text-muted-foreground border-border hover:bg-muted"}`,
@@ -1794,10 +2013,10 @@ function ExpandOptionsPanel({
1794
2013
  dir
1795
2014
  )) })
1796
2015
  ] }),
1797
- !loading && edgeTypes.length > 1 && /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
1798
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between", children: [
1799
- /* @__PURE__ */ jsx5(Label, { className: "text-[11px] text-muted-foreground", children: "Rel Types" }),
1800
- /* @__PURE__ */ jsx5(
2016
+ !loading && edgeTypes.length > 1 && /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
2017
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between", children: [
2018
+ /* @__PURE__ */ jsx7(Label, { className: "text-[11px] text-muted-foreground", children: "Rel Types" }),
2019
+ /* @__PURE__ */ jsx7(
1801
2020
  "button",
1802
2021
  {
1803
2022
  className: "text-[10px] text-muted-foreground hover:text-foreground cursor-pointer",
@@ -1806,12 +2025,12 @@ function ExpandOptionsPanel({
1806
2025
  }
1807
2026
  )
1808
2027
  ] }),
1809
- /* @__PURE__ */ jsx5("div", { className: "space-y-0.5 max-h-32 overflow-y-auto", children: edgeTypes.map((type) => /* @__PURE__ */ jsxs4(
2028
+ /* @__PURE__ */ jsx7("div", { className: "space-y-0.5 max-h-32 overflow-y-auto", children: edgeTypes.map((type) => /* @__PURE__ */ jsxs6(
1810
2029
  "label",
1811
2030
  {
1812
2031
  className: "flex items-center gap-1.5 text-[11px] cursor-pointer",
1813
2032
  children: [
1814
- /* @__PURE__ */ jsx5(
2033
+ /* @__PURE__ */ jsx7(
1815
2034
  Checkbox,
1816
2035
  {
1817
2036
  checked: selectedTypes.includes(type),
@@ -1819,15 +2038,15 @@ function ExpandOptionsPanel({
1819
2038
  className: "h-3 w-3"
1820
2039
  }
1821
2040
  ),
1822
- /* @__PURE__ */ jsx5("span", { className: "truncate", children: type })
2041
+ /* @__PURE__ */ jsx7("span", { className: "truncate", children: type })
1823
2042
  ]
1824
2043
  },
1825
2044
  type
1826
2045
  )) })
1827
2046
  ] }),
1828
- /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
1829
- /* @__PURE__ */ jsx5(Label, { className: "text-[11px] text-muted-foreground", children: "Limit" }),
1830
- /* @__PURE__ */ jsx5(
2047
+ /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
2048
+ /* @__PURE__ */ jsx7(Label, { className: "text-[11px] text-muted-foreground", children: "Limit" }),
2049
+ /* @__PURE__ */ jsx7(
1831
2050
  Input,
1832
2051
  {
1833
2052
  type: "number",
@@ -1842,7 +2061,7 @@ function ExpandOptionsPanel({
1842
2061
  }
1843
2062
  )
1844
2063
  ] }),
1845
- /* @__PURE__ */ jsx5(
2064
+ /* @__PURE__ */ jsx7(
1846
2065
  "button",
1847
2066
  {
1848
2067
  ref: expandBtnRef,
@@ -1860,7 +2079,7 @@ function ExpandOptionsPanel({
1860
2079
  var ExpandOptionsPanel_default = ExpandOptionsPanel;
1861
2080
 
1862
2081
  // src/components/rea/ReaContextMenu.tsx
1863
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2082
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1864
2083
  function ReaContextMenu({
1865
2084
  data,
1866
2085
  onClose,
@@ -1869,8 +2088,8 @@ function ReaContextMenu({
1869
2088
  onCopyNode,
1870
2089
  getNodeEdgeTypes
1871
2090
  }) {
1872
- const [showExpandPanel, setShowExpandPanel] = useState4(false);
1873
- React3.useEffect(() => {
2091
+ const [showExpandPanel, setShowExpandPanel] = useState5(false);
2092
+ React4.useEffect(() => {
1874
2093
  const handleKey = (e) => {
1875
2094
  if (e.key === "Escape") onClose();
1876
2095
  };
@@ -1980,7 +2199,7 @@ function ReaContextMenu({
1980
2199
  const menuItems = elementType === "node" ? nodeMenuItems : edgeMenuItems;
1981
2200
  if (showExpandPanel && elementType === "node") {
1982
2201
  const nodeLabel = data.label || data.id;
1983
- return /* @__PURE__ */ jsx6(
2202
+ return /* @__PURE__ */ jsx8(
1984
2203
  ExpandOptionsPanel_default,
1985
2204
  {
1986
2205
  nodeLabel: String(nodeLabel),
@@ -1990,9 +2209,9 @@ function ReaContextMenu({
1990
2209
  }
1991
2210
  );
1992
2211
  }
1993
- return /* @__PURE__ */ jsxs5("div", { className: "bg-background border border-border rounded-md shadow-lg py-1 min-w-48", children: [
1994
- menuItems.map((item, index) => /* @__PURE__ */ jsxs5(React3.Fragment, { children: [
1995
- /* @__PURE__ */ jsx6(
2212
+ return /* @__PURE__ */ jsxs7("div", { className: "bg-background border border-border rounded-md shadow-lg py-1 min-w-48", children: [
2213
+ menuItems.map((item, index) => /* @__PURE__ */ jsxs7(React4.Fragment, { children: [
2214
+ /* @__PURE__ */ jsx8(
1996
2215
  "button",
1997
2216
  {
1998
2217
  className: "w-full text-left px-3 py-2 text-sm hover:bg-muted/50 cursor-pointer",
@@ -2000,10 +2219,10 @@ function ReaContextMenu({
2000
2219
  children: item.label
2001
2220
  }
2002
2221
  ),
2003
- index === 1 && /* @__PURE__ */ jsx6("div", { className: "border-t border-border my-1" })
2222
+ index === 1 && /* @__PURE__ */ jsx8("div", { className: "border-t border-border my-1" })
2004
2223
  ] }, item.label)),
2005
- /* @__PURE__ */ jsx6("div", { className: "border-t border-border my-1" }),
2006
- /* @__PURE__ */ jsx6(
2224
+ /* @__PURE__ */ jsx8("div", { className: "border-t border-border my-1" }),
2225
+ /* @__PURE__ */ jsx8(
2007
2226
  "button",
2008
2227
  {
2009
2228
  className: "w-full text-left px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted/50 cursor-pointer",
@@ -2017,7 +2236,7 @@ var ReaContextMenu_default = ReaContextMenu;
2017
2236
 
2018
2237
  // src/components/rea/ReaGraph.tsx
2019
2238
  import { toast as toast2 } from "sonner";
2020
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2239
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2021
2240
  function findElementInGraphData(elementId, elementType, nodes, edges) {
2022
2241
  if (elementType === "node") {
2023
2242
  const node = nodes.find((n) => n.id === elementId);
@@ -2084,24 +2303,25 @@ function ReaGraph({
2084
2303
  extraToolbarContent,
2085
2304
  getNodeEdgeTypes
2086
2305
  }) {
2087
- const isMountedRef = useRef3(false);
2306
+ const isMountedRef = useRef4(false);
2088
2307
  const { nodes, edges } = useReagraphData();
2089
2308
  const { normalized } = useLiveGraphData();
2090
- useEffect3(() => {
2309
+ useEffect4(() => {
2091
2310
  devLog8("[ReGraph] counts", { nodes: nodes.length, edges: edges.length });
2092
2311
  if (nodes.length > 0) devLog8("[ReGraph] sample node", nodes[0]);
2093
2312
  if (edges.length > 0) devLog8("[ReGraph] sample edge", edges[0]);
2094
2313
  }, [nodes, edges]);
2095
- const canvas = useRef3(null);
2096
- const [hoveredNode, setHoveredNode] = useState5(
2314
+ const canvas = useRef4(null);
2315
+ const [hoveredNode, setHoveredNode] = useState6(
2097
2316
  void 0
2098
2317
  );
2099
- const [layout, setLayout] = useState5("forceDirected2d");
2100
- const [fullscreen, setFullscreen] = useState5(false);
2101
- const [pathSelectionType, setPathSelectionType] = useState5("direct");
2318
+ const [layout, setLayout] = useState6("forceDirected2d");
2319
+ const [fullscreen, setFullscreen] = useState6(false);
2320
+ const [pathSelectionType, setPathSelectionType] = useState6("direct");
2102
2321
  const useGraphSelection = useWorkspaceSelection();
2103
2322
  const useGraphFilter2 = useWorkspaceGraphFilter();
2104
2323
  const useGraphExploration = useWorkspaceGraphExploration();
2324
+ const useGraphFocus2 = useWorkspaceGraphFocus();
2105
2325
  const hiddenElementIds = useGraphExploration((s) => s.hiddenElementIds);
2106
2326
  const isFocusMode = useGraphExploration((s) => s.isFocusMode);
2107
2327
  const hasHiddenElements = hiddenElementIds.size > 0;
@@ -2124,8 +2344,8 @@ function ReaGraph({
2124
2344
  pathSelectionType,
2125
2345
  type: "multi"
2126
2346
  });
2127
- useEffect3(() => {
2128
- const unsubscribe = GraphFocus_default.subscribe((state) => {
2347
+ useEffect4(() => {
2348
+ const unsubscribe = useGraphFocus2.subscribe((state) => {
2129
2349
  const focusAction = state.focusAction;
2130
2350
  if (!focusAction || !canvas.current) return;
2131
2351
  devLog8("[ReGraph] Processing focus action", {
@@ -2143,17 +2363,17 @@ function ReaGraph({
2143
2363
  setSelectedEdge
2144
2364
  );
2145
2365
  if (handled) {
2146
- GraphFocus_default.setState({ focusAction: null });
2366
+ useGraphFocus2.setState({ focusAction: null });
2147
2367
  }
2148
2368
  });
2149
2369
  return unsubscribe;
2150
- }, [nodes, edges, setSelectedNodeIds, setSelectedEdge]);
2151
- useEffect3(() => {
2370
+ }, [nodes, edges, setSelectedNodeIds, setSelectedEdge, useGraphFocus2]);
2371
+ useEffect4(() => {
2152
2372
  if (!canvas.current) return;
2153
2373
  clearSelections();
2154
2374
  clearSelectionStore();
2155
2375
  }, [lastDataChangeTime, clearSelections, clearSelectionStore]);
2156
- const fitToScreen = useCallback4(
2376
+ const fitToScreen = useCallback5(
2157
2377
  (reason) => {
2158
2378
  if (!isMountedRef.current) return;
2159
2379
  if (nodes.length === 0) return;
@@ -2179,7 +2399,7 @@ function ReaGraph({
2179
2399
  },
2180
2400
  [nodes.length]
2181
2401
  );
2182
- useEffect3(() => {
2402
+ useEffect4(() => {
2183
2403
  isMountedRef.current = true;
2184
2404
  fitToScreen("mount");
2185
2405
  const state = useGraphSelection.getState();
@@ -2199,9 +2419,9 @@ function ReaGraph({
2199
2419
  isMountedRef.current = false;
2200
2420
  };
2201
2421
  }, [fitToScreen, setSelections, setSelectedEdge, useGraphSelection]);
2202
- const selectionsRef = useRef3(selections);
2422
+ const selectionsRef = useRef4(selections);
2203
2423
  selectionsRef.current = selections;
2204
- useEffect3(() => {
2424
+ useEffect4(() => {
2205
2425
  const inSync = (nodeIds) => {
2206
2426
  const cur = selectionsRef.current;
2207
2427
  return cur.length === nodeIds.length && nodeIds.every((id) => cur.includes(id));
@@ -2213,7 +2433,7 @@ function ReaGraph({
2213
2433
  });
2214
2434
  return unsubscribe;
2215
2435
  }, [useGraphSelection, setSelections]);
2216
- useEffect3(() => {
2436
+ useEffect4(() => {
2217
2437
  if (isMountedRef.current) fitToScreen("counts-changed");
2218
2438
  }, [nodes.length, edges.length, fitToScreen]);
2219
2439
  function updateLayout(next) {
@@ -2222,7 +2442,13 @@ function ReaGraph({
2222
2442
  }
2223
2443
  function handleNodeClick(node) {
2224
2444
  setSelectedEdge(null);
2225
- if (selections.length < 2 && onNodeClick) onNodeClick(node);
2445
+ if (!onNodeClick) return;
2446
+ const alreadySelected = selections.includes(node.id);
2447
+ if (alreadySelected || selections.length < MAX_NODE_SELECTION) {
2448
+ onNodeClick(node);
2449
+ return;
2450
+ }
2451
+ setSelections(evictToMax(selections, node.id));
2226
2452
  }
2227
2453
  function handleEdgeClick(edge) {
2228
2454
  const startNode = normalized.nodesById[edge.data.start_id_ + ""];
@@ -2255,14 +2481,12 @@ function ReaGraph({
2255
2481
  setSelectedEdge(null);
2256
2482
  if (onCanvasClick) onCanvasClick(e);
2257
2483
  }
2258
- useEffect3(() => {
2484
+ useEffect4(() => {
2259
2485
  if (!isMountedRef.current) return;
2260
- if (selections.length > 0 && selections.length <= 2) {
2261
- const selectedNodes = selections.map((id) => nodes.find((n) => n.id === id)).filter(Boolean);
2262
- setSelectedNodeIds(selectedNodes.map((n) => n.id));
2263
- }
2486
+ const selectedNodes = selections.map((id) => nodes.find((n) => n.id === id)).filter(Boolean);
2487
+ setSelectedNodeIds(selectedNodes.map((n) => n.id));
2264
2488
  }, [selections, nodes]);
2265
- useEffect3(() => {
2489
+ useEffect4(() => {
2266
2490
  if (!isMountedRef.current) return;
2267
2491
  const currentSelectedIds = useGraphSelection.getState().selectedNodeIds;
2268
2492
  if (!currentSelectedIds.length) return;
@@ -2281,7 +2505,7 @@ function ReaGraph({
2281
2505
  setSelectedEdge,
2282
2506
  useGraphSelection
2283
2507
  ]);
2284
- useEffect3(() => {
2508
+ useEffect4(() => {
2285
2509
  if (!isMountedRef.current) return;
2286
2510
  const currentSelectedIds = useGraphSelection.getState().selectedNodeIds;
2287
2511
  if (!currentSelectedIds.length) return;
@@ -2293,7 +2517,7 @@ function ReaGraph({
2293
2517
  if (stillPresentIds.length < 2) setSelectedEdge(null);
2294
2518
  }
2295
2519
  }, [nodes, setSelectedNodeIds, setSelectedEdge, useGraphSelection]);
2296
- useEffect3(() => {
2520
+ useEffect4(() => {
2297
2521
  if (!fullscreen) return;
2298
2522
  const prevOverflow = document.body.style.overflow;
2299
2523
  document.body.style.overflow = "hidden";
@@ -2307,7 +2531,7 @@ function ReaGraph({
2307
2531
  window.removeEventListener("keydown", handleKey);
2308
2532
  };
2309
2533
  }, [fullscreen, fitToScreen]);
2310
- useEffect3(() => {
2534
+ useEffect4(() => {
2311
2535
  if (!fullscreen) return;
2312
2536
  const id = setTimeout(() => {
2313
2537
  if (canvas.current) {
@@ -2316,7 +2540,7 @@ function ReaGraph({
2316
2540
  }, 50);
2317
2541
  return () => clearTimeout(id);
2318
2542
  }, [fullscreen, fitToScreen]);
2319
- useEffect3(() => {
2543
+ useEffect4(() => {
2320
2544
  if (!isMountedRef.current) return;
2321
2545
  const handleResize = () => {
2322
2546
  if (isMountedRef.current && canvas.current) {
@@ -2328,8 +2552,8 @@ function ReaGraph({
2328
2552
  window.removeEventListener("resize", handleResize);
2329
2553
  };
2330
2554
  }, [fitToScreen]);
2331
- const firstDataRef = useRef3(true);
2332
- useEffect3(() => {
2555
+ const firstDataRef = useRef4(true);
2556
+ useEffect4(() => {
2333
2557
  if (!isMountedRef.current) return;
2334
2558
  if (nodes.length > 0) {
2335
2559
  if (firstDataRef.current) {
@@ -2342,18 +2566,18 @@ function ReaGraph({
2342
2566
  }, [nodes.length, fitToScreen]);
2343
2567
  if (nodes.length === 0) {
2344
2568
  devLog8("[ReGraph] empty state render");
2345
- return /* @__PURE__ */ jsxs6(Card2, { className: "relative w-full h-full items-center justify-center text-slate-500 gap-2 shadow-xs p-4", children: [
2346
- /* @__PURE__ */ jsx7("div", { children: "No data to display." }),
2347
- /* @__PURE__ */ jsxs6("div", { children: [
2569
+ return /* @__PURE__ */ jsxs8(Card2, { className: "relative w-full h-full items-center justify-center text-slate-500 gap-2 shadow-xs p-4", children: [
2570
+ /* @__PURE__ */ jsx9("div", { children: "No data to display." }),
2571
+ /* @__PURE__ */ jsxs8("div", { children: [
2348
2572
  "Please",
2349
2573
  " ",
2350
- /* @__PURE__ */ jsx7("span", { className: "font-semibold", children: "run a query, check filters, or add a node" }),
2574
+ /* @__PURE__ */ jsx9("span", { className: "font-semibold", children: "run a query, check filters, or add a node" }),
2351
2575
  " ",
2352
2576
  "to the workbench to populate the graph."
2353
2577
  ] })
2354
2578
  ] });
2355
2579
  }
2356
- return /* @__PURE__ */ jsxs6(
2580
+ return /* @__PURE__ */ jsxs8(
2357
2581
  "div",
2358
2582
  {
2359
2583
  className: cn(
@@ -2361,7 +2585,7 @@ function ReaGraph({
2361
2585
  fullscreen ? "fixed inset-0 z-50 w-screen h-screen p-0 m-0" : "relative w-full h-full"
2362
2586
  ),
2363
2587
  children: [
2364
- /* @__PURE__ */ jsx7(Card2, { className: "h-full shadow-xs p-4", children: /* @__PURE__ */ jsx7(
2588
+ /* @__PURE__ */ jsx9(Card2, { className: "h-full shadow-xs p-4", children: /* @__PURE__ */ jsx9(
2365
2589
  GraphCanvas,
2366
2590
  {
2367
2591
  ref: canvas,
@@ -2386,7 +2610,7 @@ function ReaGraph({
2386
2610
  elementId: data.id,
2387
2611
  elementType
2388
2612
  });
2389
- return /* @__PURE__ */ jsx7(
2613
+ return /* @__PURE__ */ jsx9(
2390
2614
  ReaContextMenu_default,
2391
2615
  {
2392
2616
  data,
@@ -2400,8 +2624,8 @@ function ReaGraph({
2400
2624
  }
2401
2625
  }
2402
2626
  ) }),
2403
- hoveredNode && /* @__PURE__ */ jsx7(NodeHoverInfo_default, { node: hoveredNode }),
2404
- /* @__PURE__ */ jsx7(
2627
+ hoveredNode && /* @__PURE__ */ jsx9(NodeHoverInfo_default, { node: hoveredNode }),
2628
+ /* @__PURE__ */ jsx9(
2405
2629
  LayoutAndCameraControls_default,
2406
2630
  {
2407
2631
  fitToScreen,
@@ -2428,10 +2652,10 @@ var ReaGraph_default = ReaGraph;
2428
2652
 
2429
2653
  // src/components/cyto/CytoscapeGraph.tsx
2430
2654
  import {
2431
- useEffect as useEffect4,
2432
- useRef as useRef4,
2433
- useState as useState6,
2434
- useCallback as useCallback5
2655
+ useEffect as useEffect5,
2656
+ useRef as useRef5,
2657
+ useState as useState7,
2658
+ useCallback as useCallback6
2435
2659
  } from "react";
2436
2660
  import { devLog as devLog10, errorLog as errorLog3 } from "@petrarca/sonnet-core";
2437
2661
  import cytoscape from "cytoscape";
@@ -2445,17 +2669,17 @@ import cxtmenu from "cytoscape-cxtmenu";
2445
2669
 
2446
2670
  // src/components/cyto/CytoscapeControls.tsx
2447
2671
  import {
2448
- Button as Button3,
2449
- DropdownMenu as DropdownMenu3,
2450
- DropdownMenuContent as DropdownMenuContent3,
2451
- DropdownMenuItem as DropdownMenuItem3,
2672
+ Button as Button5,
2673
+ DropdownMenu as DropdownMenu4,
2674
+ DropdownMenuContent as DropdownMenuContent4,
2675
+ DropdownMenuItem as DropdownMenuItem4,
2452
2676
  DropdownMenuLabel as DropdownMenuLabel2,
2453
- DropdownMenuSeparator as DropdownMenuSeparator2,
2454
- DropdownMenuTrigger as DropdownMenuTrigger3,
2455
- SimpleTooltip as SimpleTooltip3
2677
+ DropdownMenuSeparator as DropdownMenuSeparator3,
2678
+ DropdownMenuTrigger as DropdownMenuTrigger4,
2679
+ SimpleTooltip as SimpleTooltip4
2456
2680
  } from "@petrarca/sonnet-ui";
2457
2681
  import { LayoutPanelTop as LayoutPanelTop2, Monitor, Frame, Eye as Eye2, Minus } from "lucide-react";
2458
- import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2682
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2459
2683
  function ViewControls2({
2460
2684
  zoomIn,
2461
2685
  zoomOut,
@@ -2466,17 +2690,17 @@ function ViewControls2({
2466
2690
  isFocusMode,
2467
2691
  onResetView
2468
2692
  }) {
2469
- return /* @__PURE__ */ jsxs7(Fragment2, { children: [
2470
- /* @__PURE__ */ jsx8(SimpleTooltip3, { label: "Zoom In", side: "left", children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: zoomIn, children: /* @__PURE__ */ jsx8("span", { style: { fontWeight: 600 }, children: "+" }) }) }),
2471
- /* @__PURE__ */ jsx8(SimpleTooltip3, { label: "Zoom Out", side: "left", children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: zoomOut, children: /* @__PURE__ */ jsx8(Minus, {}) }) }),
2472
- /* @__PURE__ */ jsx8(SimpleTooltip3, { label: "Fullscreen", side: "left", children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: toggleFullscreen, children: !fullscreen ? /* @__PURE__ */ jsx8(Monitor, {}) : /* @__PURE__ */ jsx8(Frame, {}) }) }),
2473
- /* @__PURE__ */ jsx8(SimpleTooltip3, { label: "Fit to screen", side: "left", children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: fitToScreen, children: /* @__PURE__ */ jsx8(Frame, {}) }) }),
2474
- (hasHiddenElements || isFocusMode) && onResetView && /* @__PURE__ */ jsx8(
2475
- SimpleTooltip3,
2693
+ return /* @__PURE__ */ jsxs9(Fragment2, { children: [
2694
+ /* @__PURE__ */ jsx10(SimpleTooltip4, { label: "Zoom In", side: "left", children: /* @__PURE__ */ jsx10(Button5, { variant: "ghost", size: "icon", onClick: zoomIn, children: /* @__PURE__ */ jsx10("span", { style: { fontWeight: 600 }, children: "+" }) }) }),
2695
+ /* @__PURE__ */ jsx10(SimpleTooltip4, { label: "Zoom Out", side: "left", children: /* @__PURE__ */ jsx10(Button5, { variant: "ghost", size: "icon", onClick: zoomOut, children: /* @__PURE__ */ jsx10(Minus, {}) }) }),
2696
+ /* @__PURE__ */ jsx10(SimpleTooltip4, { label: "Fullscreen", side: "left", children: /* @__PURE__ */ jsx10(Button5, { variant: "ghost", size: "icon", onClick: toggleFullscreen, children: !fullscreen ? /* @__PURE__ */ jsx10(Monitor, {}) : /* @__PURE__ */ jsx10(Frame, {}) }) }),
2697
+ /* @__PURE__ */ jsx10(SimpleTooltip4, { label: "Fit to screen", side: "left", children: /* @__PURE__ */ jsx10(Button5, { variant: "ghost", size: "icon", onClick: fitToScreen, children: /* @__PURE__ */ jsx10(Frame, {}) }) }),
2698
+ (hasHiddenElements || isFocusMode) && onResetView && /* @__PURE__ */ jsx10(
2699
+ SimpleTooltip4,
2476
2700
  {
2477
2701
  label: isFocusMode ? "Reset Focus" : "Show All Hidden",
2478
2702
  side: "left",
2479
- children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: onResetView, children: /* @__PURE__ */ jsx8(Eye2, {}) })
2703
+ children: /* @__PURE__ */ jsx10(Button5, { variant: "ghost", size: "icon", onClick: onResetView, children: /* @__PURE__ */ jsx10(Eye2, {}) })
2480
2704
  }
2481
2705
  )
2482
2706
  ] });
@@ -2511,13 +2735,13 @@ var LAYOUT_GROUPS = [
2511
2735
  }
2512
2736
  ];
2513
2737
  function LayoutDropdown2({ currentLayout, updateLayout }) {
2514
- return /* @__PURE__ */ jsxs7(DropdownMenu3, { children: [
2515
- /* @__PURE__ */ jsx8(SimpleTooltip3, { label: "Layout", side: "left", children: /* @__PURE__ */ jsx8(DropdownMenuTrigger3, { asChild: true, children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", children: /* @__PURE__ */ jsx8(LayoutPanelTop2, {}) }) }) }),
2516
- /* @__PURE__ */ jsx8(DropdownMenuContent3, { side: "left", align: "start", children: LAYOUT_GROUPS.map((group, groupIdx) => /* @__PURE__ */ jsxs7("div", { children: [
2517
- groupIdx > 0 && /* @__PURE__ */ jsx8(DropdownMenuSeparator2, {}),
2518
- /* @__PURE__ */ jsx8(DropdownMenuLabel2, { children: group.label }),
2519
- group.items.map((item) => /* @__PURE__ */ jsx8(
2520
- DropdownMenuItem3,
2738
+ return /* @__PURE__ */ jsxs9(DropdownMenu4, { children: [
2739
+ /* @__PURE__ */ jsx10(SimpleTooltip4, { label: "Layout", side: "left", children: /* @__PURE__ */ jsx10(DropdownMenuTrigger4, { asChild: true, children: /* @__PURE__ */ jsx10(Button5, { variant: "ghost", size: "icon", children: /* @__PURE__ */ jsx10(LayoutPanelTop2, {}) }) }) }),
2740
+ /* @__PURE__ */ jsx10(DropdownMenuContent4, { side: "left", align: "start", children: LAYOUT_GROUPS.map((group, groupIdx) => /* @__PURE__ */ jsxs9("div", { children: [
2741
+ groupIdx > 0 && /* @__PURE__ */ jsx10(DropdownMenuSeparator3, {}),
2742
+ /* @__PURE__ */ jsx10(DropdownMenuLabel2, { children: group.label }),
2743
+ group.items.map((item) => /* @__PURE__ */ jsx10(
2744
+ DropdownMenuItem4,
2521
2745
  {
2522
2746
  onClick: () => updateLayout(item.layout),
2523
2747
  className: currentLayout === item.layout ? "text-blue-600" : "",
@@ -2541,9 +2765,9 @@ function CytoscapeControls({
2541
2765
  onResetView,
2542
2766
  extraToolbarContent
2543
2767
  }) {
2544
- return /* @__PURE__ */ jsxs7("div", { className: "absolute right-2 top-2 flex flex-col border bg-white rounded-md shadow-sm z-10", children: [
2545
- /* @__PURE__ */ jsx8(RendererDropdown, {}),
2546
- /* @__PURE__ */ jsx8(
2768
+ return /* @__PURE__ */ jsxs9("div", { className: "absolute right-2 top-2 flex flex-col border bg-white rounded-md shadow-sm z-10", children: [
2769
+ /* @__PURE__ */ jsx10(RendererDropdown, {}),
2770
+ /* @__PURE__ */ jsx10(
2547
2771
  ViewControls2,
2548
2772
  {
2549
2773
  zoomIn,
@@ -2557,7 +2781,7 @@ function CytoscapeControls({
2557
2781
  }
2558
2782
  ),
2559
2783
  extraToolbarContent,
2560
- /* @__PURE__ */ jsx8(
2784
+ /* @__PURE__ */ jsx10(
2561
2785
  LayoutDropdown2,
2562
2786
  {
2563
2787
  currentLayout,
@@ -2997,7 +3221,7 @@ var createEdgeContextMenu = ({
2997
3221
 
2998
3222
  // src/components/cyto/CytoscapeGraph.tsx
2999
3223
  import { toast as toast4 } from "sonner";
3000
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3224
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3001
3225
  function CytoscapeExpandOverlay({
3002
3226
  expandRequest,
3003
3227
  containerRef,
@@ -3005,7 +3229,7 @@ function CytoscapeExpandOverlay({
3005
3229
  getNodeEdgeTypes,
3006
3230
  onClose
3007
3231
  }) {
3008
- return /* @__PURE__ */ jsx9(
3232
+ return /* @__PURE__ */ jsx11(
3009
3233
  "div",
3010
3234
  {
3011
3235
  className: "absolute z-50",
@@ -3020,7 +3244,7 @@ function CytoscapeExpandOverlay({
3020
3244
  ),
3021
3245
  transform: "translate(-50%, 12px)"
3022
3246
  },
3023
- children: /* @__PURE__ */ jsx9(
3247
+ children: /* @__PURE__ */ jsx11(
3024
3248
  ExpandOptionsPanel_default,
3025
3249
  {
3026
3250
  nodeLabel: expandRequest.nodeLabel,
@@ -3133,6 +3357,7 @@ function applyLayout({
3133
3357
  }
3134
3358
  }
3135
3359
  function processPendingFocus({
3360
+ focusStore,
3136
3361
  cy,
3137
3362
  pendingFocus,
3138
3363
  edgesRef,
@@ -3157,7 +3382,7 @@ function processPendingFocus({
3157
3382
  }
3158
3383
  );
3159
3384
  pendingFocusActionRef.current = null;
3160
- GraphFocus_default.setState({ focusAction: null });
3385
+ focusStore.setState({ focusAction: null });
3161
3386
  return;
3162
3387
  }
3163
3388
  cy.animate(
@@ -3177,7 +3402,7 @@ function processPendingFocus({
3177
3402
  });
3178
3403
  }
3179
3404
  pendingFocusActionRef.current = null;
3180
- GraphFocus_default.setState({ focusAction: null });
3405
+ focusStore.setState({ focusAction: null });
3181
3406
  }, 100);
3182
3407
  }
3183
3408
  function selectFocusedElement({
@@ -3220,6 +3445,7 @@ function selectFocusedElement({
3220
3445
  }
3221
3446
  }
3222
3447
  function handleFocusStateChange({
3448
+ focusStore,
3223
3449
  focusAction,
3224
3450
  cyRef,
3225
3451
  edgesRef,
@@ -3240,6 +3466,7 @@ function handleFocusStateChange({
3240
3466
  const element = cy.$id(elementId);
3241
3467
  if (element.length > 0) {
3242
3468
  handleImmediateFocus({
3469
+ focusStore,
3243
3470
  cy,
3244
3471
  element,
3245
3472
  focusAction,
@@ -3256,7 +3483,7 @@ function handleFocusStateChange({
3256
3483
  elementType: focusAction.elementType
3257
3484
  });
3258
3485
  pendingFocusActionRef.current = focusAction;
3259
- GraphFocus_default.setState((state) => {
3486
+ focusStore.setState((state) => {
3260
3487
  const overrides = new Set(state.overrideElementIds);
3261
3488
  overrides.add(elementId);
3262
3489
  if (focusAction.elementType === "edge") {
@@ -3280,6 +3507,7 @@ function handleFocusStateChange({
3280
3507
  });
3281
3508
  }
3282
3509
  function handleImmediateFocus({
3510
+ focusStore,
3283
3511
  cy,
3284
3512
  element,
3285
3513
  focusAction,
@@ -3310,7 +3538,7 @@ function handleImmediateFocus({
3310
3538
  selectedEdgeRef
3311
3539
  });
3312
3540
  }
3313
- GraphFocus_default.setState({ focusAction: null });
3541
+ focusStore.setState({ focusAction: null });
3314
3542
  }
3315
3543
  function computeGraphViewState({
3316
3544
  hasNodes,
@@ -3328,8 +3556,8 @@ function computeGraphViewState({
3328
3556
  };
3329
3557
  }
3330
3558
  function useFullscreen(fitToScreen, cyRef) {
3331
- const [fullscreen, setFullscreen] = useState6(false);
3332
- useEffect4(() => {
3559
+ const [fullscreen, setFullscreen] = useState7(false);
3560
+ useEffect5(() => {
3333
3561
  if (fullscreen) {
3334
3562
  const prevOverflow = document.body.style.overflow;
3335
3563
  document.body.style.overflow = "hidden";
@@ -3344,7 +3572,7 @@ function useFullscreen(fitToScreen, cyRef) {
3344
3572
  };
3345
3573
  }
3346
3574
  }, [fullscreen, fitToScreen]);
3347
- useEffect4(() => {
3575
+ useEffect5(() => {
3348
3576
  if (!fullscreen) return;
3349
3577
  const id = setTimeout(() => {
3350
3578
  if (cyRef.current) {
@@ -3357,7 +3585,7 @@ function useFullscreen(fitToScreen, cyRef) {
3357
3585
  return {
3358
3586
  fullscreen,
3359
3587
  setFullscreen,
3360
- toggleFullscreen: useCallback5(() => setFullscreen((f) => !f), [])
3588
+ toggleFullscreen: useCallback6(() => setFullscreen((f) => !f), [])
3361
3589
  };
3362
3590
  }
3363
3591
  function setupCytoscapeEventHandlers({
@@ -3400,16 +3628,14 @@ function setupCytoscapeEventHandlers({
3400
3628
  updateSelectedEdge(null);
3401
3629
  }
3402
3630
  let selectedIds = evtCy.$("node:selected").map((e) => e.id());
3403
- if (selectedIds.length > 2) {
3404
- const currentOrder = [...selectedNodeIdsRef.current];
3405
- const updatedOrder = [...currentOrder.filter((cid) => cid !== id), id];
3406
- const keep = updatedOrder.slice(-2);
3631
+ if (selectedIds.length > MAX_NODE_SELECTION) {
3632
+ const keep = evictToMax(selectedNodeIdsRef.current, id);
3407
3633
  evtCy.batch(() => {
3408
3634
  evtCy.$("node:selected").unselect();
3409
3635
  keep.forEach((kid) => evtCy.$id(kid).select());
3410
3636
  });
3411
3637
  selectedIds = keep;
3412
- devLog10("[CytoscapeGraph] enforced max 2 after select", { keep });
3638
+ devLog10("[CytoscapeGraph] enforced max selection after select", { keep });
3413
3639
  }
3414
3640
  updateSelectedNodeIds(selectedIds);
3415
3641
  });
@@ -3473,16 +3699,16 @@ function CytoscapeGraph({
3473
3699
  extraToolbarContent,
3474
3700
  getNodeEdgeTypes
3475
3701
  }) {
3476
- const containerRef = useRef4(null);
3477
- const cyRef = useRef4(null);
3478
- const isInitializedRef = useRef4(false);
3479
- const [currentLayout, setCurrentLayout] = useState6(initialLayout);
3480
- const [expandRequest, setExpandRequest] = useState6(
3702
+ const containerRef = useRef5(null);
3703
+ const cyRef = useRef5(null);
3704
+ const isInitializedRef = useRef5(false);
3705
+ const [currentLayout, setCurrentLayout] = useState7(initialLayout);
3706
+ const [expandRequest, setExpandRequest] = useState7(
3481
3707
  null
3482
3708
  );
3483
- const nodesRef = useRef4([]);
3484
- const edgesRef = useRef4([]);
3485
- const pendingFocusActionRef = useRef4(null);
3709
+ const nodesRef = useRef5([]);
3710
+ const edgesRef = useRef5([]);
3711
+ const pendingFocusActionRef = useRef5(null);
3486
3712
  const cytoData = useCytoscapeData();
3487
3713
  const live = useLiveGraphData();
3488
3714
  const nodes = live.nodes;
@@ -3491,6 +3717,7 @@ function CytoscapeGraph({
3491
3717
  const useGraphFilter2 = useWorkspaceGraphFilter();
3492
3718
  const useGraphExploration = useWorkspaceGraphExploration();
3493
3719
  const useSelection3 = useWorkspaceSelection();
3720
+ const useGraphFocus2 = useWorkspaceGraphFocus();
3494
3721
  const lastDataChangeTime = useGraphFilter2((s) => s.lastDataChangeTime);
3495
3722
  const resetView = useGraphExploration((s) => s.resetView);
3496
3723
  const hasHiddenElements = useGraphExploration(
@@ -3502,13 +3729,13 @@ function CytoscapeGraph({
3502
3729
  setSelectedEdge: setSelectedEdgeStore,
3503
3730
  clear: clearSelectionStore
3504
3731
  } = useSelection3.getState();
3505
- const selectedNodeIdsRef = useRef4(
3732
+ const selectedNodeIdsRef = useRef5(
3506
3733
  useSelection3.getState().selectedNodeIds
3507
3734
  );
3508
- const selectedEdgeRef = useRef4(
3735
+ const selectedEdgeRef = useRef5(
3509
3736
  useSelection3.getState().selectedEdge
3510
3737
  );
3511
- useEffect4(() => {
3738
+ useEffect5(() => {
3512
3739
  const applyStoreSelection = (nodeIds, edge) => {
3513
3740
  selectedNodeIdsRef.current = nodeIds;
3514
3741
  selectedEdgeRef.current = edge;
@@ -3548,13 +3775,13 @@ function CytoscapeGraph({
3548
3775
  });
3549
3776
  return unsubscribe;
3550
3777
  }, [useSelection3]);
3551
- useEffect4(() => {
3778
+ useEffect5(() => {
3552
3779
  nodesRef.current = nodes;
3553
3780
  }, [nodes]);
3554
- useEffect4(() => {
3781
+ useEffect5(() => {
3555
3782
  edgesRef.current = edges;
3556
3783
  }, [edges]);
3557
- useEffect4(() => {
3784
+ useEffect5(() => {
3558
3785
  if (!containerRef.current || cyRef.current) return;
3559
3786
  isInitializedRef.current = true;
3560
3787
  cyRef.current = cytoscape({
@@ -3717,10 +3944,10 @@ function CytoscapeGraph({
3717
3944
  cyRef.current = null;
3718
3945
  };
3719
3946
  }, []);
3720
- const elementIdsRef = useRef4("");
3721
- const lastLayoutRef = useRef4("");
3722
- const lastFitPaddingRef = useRef4(fitPadding);
3723
- useEffect4(() => {
3947
+ const elementIdsRef = useRef5("");
3948
+ const lastLayoutRef = useRef5("");
3949
+ const lastFitPaddingRef = useRef5(fitPadding);
3950
+ useEffect5(() => {
3724
3951
  const cy = cyRef.current;
3725
3952
  if (!cy || !isInitializedRef.current) return;
3726
3953
  const currentIds = nodes.map((n) => n.id_).sort().join(",") + "|" + edges.map((e) => e.id_).sort().join(",");
@@ -3782,6 +4009,7 @@ function CytoscapeGraph({
3782
4009
  const pendingFocus = pendingFocusActionRef.current;
3783
4010
  if (pendingFocus) {
3784
4011
  processPendingFocus({
4012
+ focusStore: useGraphFocus2,
3785
4013
  cy,
3786
4014
  pendingFocus,
3787
4015
  edgesRef,
@@ -3793,14 +4021,15 @@ function CytoscapeGraph({
3793
4021
  });
3794
4022
  }
3795
4023
  }, [nodes, edges, currentLayout, fitPadding]);
3796
- useEffect4(() => {
4024
+ useEffect5(() => {
3797
4025
  if (!cyRef.current) return;
3798
4026
  cyRef.current.elements().unselect();
3799
4027
  clearSelectionStore();
3800
4028
  }, [lastDataChangeTime, clearSelectionStore]);
3801
- useEffect4(() => {
3802
- const unsubscribe = GraphFocus_default.subscribe((state) => {
4029
+ useEffect5(() => {
4030
+ const unsubscribe = useGraphFocus2.subscribe((state) => {
3803
4031
  handleFocusStateChange({
4032
+ focusStore: useGraphFocus2,
3804
4033
  focusAction: state.focusAction,
3805
4034
  cyRef,
3806
4035
  edgesRef,
@@ -3813,19 +4042,24 @@ function CytoscapeGraph({
3813
4042
  });
3814
4043
  });
3815
4044
  return unsubscribe;
3816
- }, [baseNormalized, setSelectedEdgeStore, setSelectedNodeIdsStore]);
4045
+ }, [
4046
+ baseNormalized,
4047
+ setSelectedEdgeStore,
4048
+ setSelectedNodeIdsStore,
4049
+ useGraphFocus2
4050
+ ]);
3817
4051
  const updateLayout = (newLayout) => {
3818
4052
  setCurrentLayout(newLayout);
3819
4053
  };
3820
- const fitToScreen = useCallback5(() => {
4054
+ const fitToScreen = useCallback6(() => {
3821
4055
  if (cyRef.current) {
3822
4056
  cyRef.current.fit(void 0, fitPadding);
3823
4057
  }
3824
4058
  }, [fitPadding]);
3825
- useEffect4(() => {
4059
+ useEffect5(() => {
3826
4060
  fitToScreen();
3827
4061
  }, [nodes.length, fitToScreen]);
3828
- useEffect4(() => {
4062
+ useEffect5(() => {
3829
4063
  if (!isInitializedRef.current) return;
3830
4064
  const handleResize = () => cyRef.current?.resize();
3831
4065
  window.addEventListener("resize", handleResize);
@@ -3844,9 +4078,9 @@ function CytoscapeGraph({
3844
4078
  isFocusMode,
3845
4079
  fullscreen
3846
4080
  });
3847
- return /* @__PURE__ */ jsx9("div", { className: outerClass, children: /* @__PURE__ */ jsxs8("div", { className: innerClass, children: [
3848
- /* @__PURE__ */ jsx9("div", { ref: containerRef, className: "absolute inset-0" }),
3849
- expandRequest && /* @__PURE__ */ jsx9(
4081
+ return /* @__PURE__ */ jsx11("div", { className: outerClass, children: /* @__PURE__ */ jsxs10("div", { className: innerClass, children: [
4082
+ /* @__PURE__ */ jsx11("div", { ref: containerRef, className: "absolute inset-0" }),
4083
+ expandRequest && /* @__PURE__ */ jsx11(
3850
4084
  CytoscapeExpandOverlay,
3851
4085
  {
3852
4086
  expandRequest,
@@ -3856,9 +4090,9 @@ function CytoscapeGraph({
3856
4090
  onClose: () => setExpandRequest(null)
3857
4091
  }
3858
4092
  ),
3859
- showEmptyMessage && /* @__PURE__ */ jsx9("div", { className: "absolute inset-0 flex items-center justify-center text-xs text-gray-500 pointer-events-none select-none", children: "No graph data to visualize (run a query or check the filters)" }),
3860
- showFilteredMessage && /* @__PURE__ */ jsx9("div", { className: "absolute inset-0 flex items-center justify-center text-xs text-gray-500 pointer-events-none select-none", children: 'All elements are hidden or filtered (click "Reset Focus" to restore)' }),
3861
- showControls && /* @__PURE__ */ jsx9(
4093
+ showEmptyMessage && /* @__PURE__ */ jsx11("div", { className: "absolute inset-0 flex items-center justify-center text-xs text-gray-500 pointer-events-none select-none", children: "No graph data to visualize (run a query or check the filters)" }),
4094
+ showFilteredMessage && /* @__PURE__ */ jsx11("div", { className: "absolute inset-0 flex items-center justify-center text-xs text-gray-500 pointer-events-none select-none", children: 'All elements are hidden or filtered (click "Reset Focus" to restore)' }),
4095
+ showControls && /* @__PURE__ */ jsx11(
3862
4096
  CytoscapeControls_default,
3863
4097
  {
3864
4098
  updateLayout,
@@ -3906,15 +4140,15 @@ function CytoscapeGraph({
3906
4140
  var CytoscapeGraph_default = CytoscapeGraph;
3907
4141
 
3908
4142
  // src/components/GraphVisualizer.tsx
3909
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
4143
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3910
4144
  function GraphVisualizer({
3911
4145
  onCopyNode,
3912
4146
  extraToolbarContent,
3913
4147
  getNodeEdgeTypes
3914
4148
  }) {
3915
4149
  const { renderer } = useGraphRenderer();
3916
- return /* @__PURE__ */ jsx10("div", { className: "relative w-full h-full flex flex-col overflow-hidden", children: /* @__PURE__ */ jsxs9("div", { className: "flex-1 h-full", children: [
3917
- renderer === "reagraph" && /* @__PURE__ */ jsx10(
4150
+ return /* @__PURE__ */ jsx12("div", { className: "relative w-full h-full flex flex-col overflow-hidden", children: /* @__PURE__ */ jsxs11("div", { className: "flex-1 h-full", children: [
4151
+ renderer === "reagraph" && /* @__PURE__ */ jsx12(
3918
4152
  ReaGraph_default,
3919
4153
  {
3920
4154
  onCopyNode,
@@ -3923,7 +4157,7 @@ function GraphVisualizer({
3923
4157
  },
3924
4158
  "reagraph"
3925
4159
  ),
3926
- renderer === "cytoscape" && /* @__PURE__ */ jsx10(
4160
+ renderer === "cytoscape" && /* @__PURE__ */ jsx12(
3927
4161
  CytoscapeGraph_default,
3928
4162
  {
3929
4163
  onCopyNode,
@@ -3940,7 +4174,7 @@ var GraphVisualizer_default = GraphVisualizer;
3940
4174
  import { Card as Card5 } from "@petrarca/sonnet-ui";
3941
4175
 
3942
4176
  // src/components/ActionPanel/NodeInfoCard.tsx
3943
- import { useState as useState7 } from "react";
4177
+ import { useState as useState8 } from "react";
3944
4178
  import {
3945
4179
  Card as Card3,
3946
4180
  Collapsible,
@@ -3948,11 +4182,11 @@ import {
3948
4182
  CollapsibleTrigger,
3949
4183
  PropertyList
3950
4184
  } from "@petrarca/sonnet-ui";
3951
- import { copyTextToClipboard } from "@petrarca/sonnet-core";
3952
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
4185
+ import { copyTextToClipboard as copyTextToClipboard2 } from "@petrarca/sonnet-core";
4186
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
3953
4187
  function NodeInfoCard({ node }) {
3954
- const [showProperties, setShowProperties] = useState7(false);
3955
- return /* @__PURE__ */ jsxs10(
4188
+ const [showProperties, setShowProperties] = useState8(false);
4189
+ return /* @__PURE__ */ jsxs12(
3956
4190
  Card3,
3957
4191
  {
3958
4192
  className: "w-full rounded-lg overflow-hidden bg-muted/30 border shadow-none p-3",
@@ -3960,20 +4194,20 @@ function NodeInfoCard({ node }) {
3960
4194
  borderLeft: `4px solid ${node.visual.color}`
3961
4195
  },
3962
4196
  children: [
3963
- /* @__PURE__ */ jsx11("p", { className: "text-sm leading-tight font-medium mb-0", children: node.label_ }),
3964
- /* @__PURE__ */ jsx11("p", { className: "text-sm leading-tight mb-1", children: node.visual.displayName }),
3965
- /* @__PURE__ */ jsx11(
4197
+ /* @__PURE__ */ jsx13("p", { className: "text-sm leading-tight font-medium mb-0", children: node.label_ }),
4198
+ /* @__PURE__ */ jsx13("p", { className: "text-sm leading-tight mb-1", children: node.visual.displayName }),
4199
+ /* @__PURE__ */ jsx13(
3966
4200
  "p",
3967
4201
  {
3968
4202
  className: "text-xs text-muted-foreground cursor-pointer select-all mb-2",
3969
- onClick: () => copyTextToClipboard(String(node.id_)),
4203
+ onClick: () => copyTextToClipboard2(String(node.id_)),
3970
4204
  title: "Click to copy id",
3971
4205
  children: node.id_
3972
4206
  }
3973
4207
  ),
3974
- /* @__PURE__ */ jsxs10(Collapsible, { open: showProperties, onOpenChange: setShowProperties, children: [
3975
- /* @__PURE__ */ jsx11(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsx11("button", { className: "text-xs cursor-pointer", children: showProperties ? "Hide" : "Show more" }) }),
3976
- /* @__PURE__ */ jsx11(CollapsibleContent, { children: /* @__PURE__ */ jsx11("div", { className: "mt-2", children: /* @__PURE__ */ jsx11(PropertyList, { properties: node.properties_ || {}, showHeader: true }) }) })
4208
+ /* @__PURE__ */ jsxs12(Collapsible, { open: showProperties, onOpenChange: setShowProperties, children: [
4209
+ /* @__PURE__ */ jsx13(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsx13("button", { className: "text-xs cursor-pointer", children: showProperties ? "Hide" : "Show more" }) }),
4210
+ /* @__PURE__ */ jsx13(CollapsibleContent, { children: /* @__PURE__ */ jsx13("div", { className: "mt-2", children: /* @__PURE__ */ jsx13(PropertyList, { properties: node.properties_ || {}, showHeader: true }) }) })
3977
4211
  ] })
3978
4212
  ]
3979
4213
  }
@@ -3982,7 +4216,7 @@ function NodeInfoCard({ node }) {
3982
4216
  var NodeInfoCard_default = NodeInfoCard;
3983
4217
 
3984
4218
  // src/components/ActionPanel/EdgeInfoCard.tsx
3985
- import { useState as useState8 } from "react";
4219
+ import { useState as useState9 } from "react";
3986
4220
  import {
3987
4221
  Card as Card4,
3988
4222
  Collapsible as Collapsible2,
@@ -3991,11 +4225,11 @@ import {
3991
4225
  PropertyList as PropertyList2
3992
4226
  } from "@petrarca/sonnet-ui";
3993
4227
  import { ArrowDown } from "lucide-react";
3994
- import { copyTextToClipboard as copyTextToClipboard2 } from "@petrarca/sonnet-core";
3995
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
4228
+ import { copyTextToClipboard as copyTextToClipboard3 } from "@petrarca/sonnet-core";
4229
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
3996
4230
  function EdgeInfoCard({ edge }) {
3997
- const [showProperties, setShowProperties] = useState8(false);
3998
- return /* @__PURE__ */ jsxs11(
4231
+ const [showProperties, setShowProperties] = useState9(false);
4232
+ return /* @__PURE__ */ jsxs13(
3999
4233
  Card4,
4000
4234
  {
4001
4235
  className: "w-full rounded-lg overflow-hidden bg-muted/30 border shadow-none p-3",
@@ -4003,22 +4237,22 @@ function EdgeInfoCard({ edge }) {
4003
4237
  borderLeft: `4px solid ${edge.visual.color}`
4004
4238
  },
4005
4239
  children: [
4006
- /* @__PURE__ */ jsxs11("p", { className: "text-sm leading-tight mb-1", children: [
4007
- /* @__PURE__ */ jsx12(ArrowDown, { className: "inline-block mr-1 opacity-70 align-middle h-3.5 w-3.5" }),
4008
- /* @__PURE__ */ jsx12("strong", { className: "font-medium align-middle", children: edge.label_ })
4240
+ /* @__PURE__ */ jsxs13("p", { className: "text-sm leading-tight mb-1", children: [
4241
+ /* @__PURE__ */ jsx14(ArrowDown, { className: "inline-block mr-1 opacity-70 align-middle h-3.5 w-3.5" }),
4242
+ /* @__PURE__ */ jsx14("strong", { className: "font-medium align-middle", children: edge.label_ })
4009
4243
  ] }),
4010
- edge.id_ && /* @__PURE__ */ jsx12(
4244
+ edge.id_ && /* @__PURE__ */ jsx14(
4011
4245
  "p",
4012
4246
  {
4013
4247
  className: "text-xs text-muted-foreground select-all cursor-pointer mt-1 mb-2",
4014
4248
  title: "Click to copy edge id",
4015
- onClick: () => copyTextToClipboard2(String(edge.id_)),
4249
+ onClick: () => copyTextToClipboard3(String(edge.id_)),
4016
4250
  children: edge.id_
4017
4251
  }
4018
4252
  ),
4019
- edge.properties_ && Object.keys(edge.properties_).length > 0 && /* @__PURE__ */ jsxs11(Collapsible2, { open: showProperties, onOpenChange: setShowProperties, children: [
4020
- /* @__PURE__ */ jsx12(CollapsibleTrigger2, { asChild: true, children: /* @__PURE__ */ jsx12("button", { className: "text-xs cursor-pointer", children: showProperties ? "Hide" : "Show more" }) }),
4021
- /* @__PURE__ */ jsx12(CollapsibleContent2, { children: /* @__PURE__ */ jsx12("div", { className: "mt-2", children: /* @__PURE__ */ jsx12(PropertyList2, { properties: edge.properties_ || {}, showHeader: true }) }) })
4253
+ edge.properties_ && Object.keys(edge.properties_).length > 0 && /* @__PURE__ */ jsxs13(Collapsible2, { open: showProperties, onOpenChange: setShowProperties, children: [
4254
+ /* @__PURE__ */ jsx14(CollapsibleTrigger2, { asChild: true, children: /* @__PURE__ */ jsx14("button", { className: "text-xs cursor-pointer", children: showProperties ? "Hide" : "Show more" }) }),
4255
+ /* @__PURE__ */ jsx14(CollapsibleContent2, { children: /* @__PURE__ */ jsx14("div", { className: "mt-2", children: /* @__PURE__ */ jsx14(PropertyList2, { properties: edge.properties_ || {}, showHeader: true }) }) })
4022
4256
  ] })
4023
4257
  ]
4024
4258
  }
@@ -4030,7 +4264,7 @@ var EdgeInfoCard_default = EdgeInfoCard;
4030
4264
  import { Badge } from "@petrarca/sonnet-ui";
4031
4265
  import { formatNumber, devLog as devLog11 } from "@petrarca/sonnet-core";
4032
4266
  import { useMemo as useMemo4 } from "react";
4033
- import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
4267
+ import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
4034
4268
  function computeEdgeToNodeLabels(nodesById, edgesById) {
4035
4269
  const mapping = {};
4036
4270
  for (const edge of Object.values(edgesById)) {
@@ -4063,7 +4297,7 @@ function AllToggleBadge({
4063
4297
  badgeKey
4064
4298
  }) {
4065
4299
  const bgColor = allDisabled ? colorPalette["faded"] : colorPalette["neutral"];
4066
- return /* @__PURE__ */ jsxs12(
4300
+ return /* @__PURE__ */ jsxs14(
4067
4301
  Badge,
4068
4302
  {
4069
4303
  className: "cursor-pointer text-xs px-2.5 py-0.5",
@@ -4091,7 +4325,7 @@ function NodeLabelBadges({
4091
4325
  }) {
4092
4326
  return labels.map((label) => {
4093
4327
  const bgColor = shown.has(label) ? colorPalette[label] : colorPalette["faded"];
4094
- return /* @__PURE__ */ jsxs12(
4328
+ return /* @__PURE__ */ jsxs14(
4095
4329
  Badge,
4096
4330
  {
4097
4331
  className: "cursor-pointer text-xs px-2.5 py-0.5",
@@ -4129,7 +4363,7 @@ function EdgeLabelBadges({
4129
4363
  colorPalette,
4130
4364
  "neutral"
4131
4365
  );
4132
- return /* @__PURE__ */ jsxs12(
4366
+ return /* @__PURE__ */ jsxs14(
4133
4367
  Badge,
4134
4368
  {
4135
4369
  className: "cursor-pointer text-xs px-2.5 py-0.5",
@@ -4232,9 +4466,9 @@ function GraphOverview({
4232
4466
  nodeLabelFilters
4233
4467
  );
4234
4468
  const handleEdgeToggle = toggleAllEdgeLabelsDisabled;
4235
- return /* @__PURE__ */ jsxs12(Fragment3, { children: [
4236
- /* @__PURE__ */ jsx13("p", { className: "mt-2 heading-component", children: "Overview" }),
4237
- allLabels.length > 0 && /* @__PURE__ */ jsx13("div", { children: buckets.map((bucket) => {
4469
+ return /* @__PURE__ */ jsxs14(Fragment3, { children: [
4470
+ /* @__PURE__ */ jsx15("p", { className: "mt-2 heading-component", children: "Overview" }),
4471
+ allLabels.length > 0 && /* @__PURE__ */ jsx15("div", { children: buckets.map((bucket) => {
4238
4472
  if (bucket.labels.length === 0) return null;
4239
4473
  const bucketAllDisabled = isBucketAllOff(
4240
4474
  bucket.labels,
@@ -4246,10 +4480,10 @@ function GraphOverview({
4246
4480
  shownNodeLabels,
4247
4481
  setNodeFilterState
4248
4482
  );
4249
- return /* @__PURE__ */ jsxs12("div", { className: "mb-2", children: [
4250
- /* @__PURE__ */ jsx13("p", { className: "heading-meta mb-2", children: bucket.title }),
4251
- /* @__PURE__ */ jsxs12("div", { className: "flex flex-col items-start gap-2", children: [
4252
- /* @__PURE__ */ jsx13(
4483
+ return /* @__PURE__ */ jsxs14("div", { className: "mb-2", children: [
4484
+ /* @__PURE__ */ jsx15("p", { className: "heading-meta mb-2", children: bucket.title }),
4485
+ /* @__PURE__ */ jsxs14("div", { className: "flex flex-col items-start gap-2", children: [
4486
+ /* @__PURE__ */ jsx15(
4253
4487
  AllToggleBadge,
4254
4488
  {
4255
4489
  allDisabled: bucketAllDisabled,
@@ -4259,7 +4493,7 @@ function GraphOverview({
4259
4493
  badgeKey: `all-${bucket.title}`
4260
4494
  }
4261
4495
  ),
4262
- /* @__PURE__ */ jsx13(
4496
+ /* @__PURE__ */ jsx15(
4263
4497
  NodeLabelBadges,
4264
4498
  {
4265
4499
  labels: bucket.labels,
@@ -4277,10 +4511,10 @@ function GraphOverview({
4277
4511
  ] })
4278
4512
  ] }, bucket.title);
4279
4513
  }) }),
4280
- allEdgeLabels.length > 0 && /* @__PURE__ */ jsxs12("div", { children: [
4281
- /* @__PURE__ */ jsx13("p", { className: "heading-meta mb-2", children: edgeSectionTitle }),
4282
- /* @__PURE__ */ jsxs12("div", { className: "flex flex-col items-start gap-2", children: [
4283
- /* @__PURE__ */ jsx13(
4514
+ allEdgeLabels.length > 0 && /* @__PURE__ */ jsxs14("div", { children: [
4515
+ /* @__PURE__ */ jsx15("p", { className: "heading-meta mb-2", children: edgeSectionTitle }),
4516
+ /* @__PURE__ */ jsxs14("div", { className: "flex flex-col items-start gap-2", children: [
4517
+ /* @__PURE__ */ jsx15(
4284
4518
  AllToggleBadge,
4285
4519
  {
4286
4520
  allDisabled: allEdgeLabelsDisabled,
@@ -4290,7 +4524,7 @@ function GraphOverview({
4290
4524
  badgeKey: "edge-all"
4291
4525
  }
4292
4526
  ),
4293
- /* @__PURE__ */ jsx13(
4527
+ /* @__PURE__ */ jsx15(
4294
4528
  EdgeLabelBadges,
4295
4529
  {
4296
4530
  labels: allEdgeLabels,
@@ -4316,16 +4550,16 @@ function Overview({ nodeBuckets, edgeSectionTitle } = {}) {
4316
4550
  );
4317
4551
  }, [baseNormalized]);
4318
4552
  if (stats && stats.values != null && stats.values > 0) {
4319
- return /* @__PURE__ */ jsxs12(Fragment3, { children: [
4320
- /* @__PURE__ */ jsx13("p", { className: "mt-2 heading-component", children: "Overview" }),
4321
- /* @__PURE__ */ jsxs12("p", { className: "text-sm text-muted-foreground", children: [
4553
+ return /* @__PURE__ */ jsxs14(Fragment3, { children: [
4554
+ /* @__PURE__ */ jsx15("p", { className: "mt-2 heading-component", children: "Overview" }),
4555
+ /* @__PURE__ */ jsxs14("p", { className: "text-sm text-muted-foreground", children: [
4322
4556
  formatNumber(stats.values),
4323
4557
  " Values"
4324
4558
  ] })
4325
4559
  ] });
4326
4560
  }
4327
4561
  if (stats && stats.nodes > 0 && colorPalette) {
4328
- return /* @__PURE__ */ jsx13(
4562
+ return /* @__PURE__ */ jsx15(
4329
4563
  GraphOverview,
4330
4564
  {
4331
4565
  stats,
@@ -4344,7 +4578,7 @@ var Overview_default = Overview;
4344
4578
 
4345
4579
  // src/components/ActionPanel/ActionPanel.tsx
4346
4580
  import { useMemo as useMemo5 } from "react";
4347
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
4581
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
4348
4582
  function ActionPanel({ actions }) {
4349
4583
  const useSelection3 = useWorkspaceSelection();
4350
4584
  const selectedNodeIds = useSelection3(
@@ -4360,18 +4594,18 @@ function ActionPanel({ actions }) {
4360
4594
  const secondNode = selectedNodes?.[1];
4361
4595
  const hasEdge = !!selectedEdge;
4362
4596
  const showOverview = !firstNode && !hasEdge;
4363
- return /* @__PURE__ */ jsxs13(
4597
+ return /* @__PURE__ */ jsxs15(
4364
4598
  Card5,
4365
4599
  {
4366
4600
  className: "h-full min-w-64 flex flex-col bg-card shadow-none p-4",
4367
4601
  style: { maxHeight: "100%", overflow: "hidden" },
4368
4602
  children: [
4369
- actions && /* @__PURE__ */ jsx14("div", { className: "flex-shrink-0 mb-2", children: actions }),
4370
- /* @__PURE__ */ jsx14("div", { className: "flex-1 overflow-auto pr-1", children: /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-3", children: [
4371
- firstNode && /* @__PURE__ */ jsx14(NodeInfoCard_default, { node: firstNode }),
4372
- hasEdge && selectedEdge && /* @__PURE__ */ jsx14(EdgeInfoCard_default, { edge: selectedEdge }),
4373
- secondNode && /* @__PURE__ */ jsx14(NodeInfoCard_default, { node: secondNode }),
4374
- showOverview && /* @__PURE__ */ jsx14(Overview_default, {})
4603
+ actions && /* @__PURE__ */ jsx16("div", { className: "flex-shrink-0 mb-2", children: actions }),
4604
+ /* @__PURE__ */ jsx16("div", { className: "flex-1 overflow-auto pr-1", children: /* @__PURE__ */ jsxs15("div", { className: "flex flex-col gap-3", children: [
4605
+ firstNode && /* @__PURE__ */ jsx16(NodeInfoCard_default, { node: firstNode }),
4606
+ hasEdge && selectedEdge && /* @__PURE__ */ jsx16(EdgeInfoCard_default, { edge: selectedEdge }),
4607
+ secondNode && /* @__PURE__ */ jsx16(NodeInfoCard_default, { node: secondNode }),
4608
+ showOverview && /* @__PURE__ */ jsx16(Overview_default, {})
4375
4609
  ] }) })
4376
4610
  ]
4377
4611
  }
@@ -4380,12 +4614,11 @@ function ActionPanel({ actions }) {
4380
4614
  var ActionPanel_default = ActionPanel;
4381
4615
 
4382
4616
  // src/components/GraphA11yList/GraphA11yList.tsx
4383
- import { useCallback as useCallback6, useMemo as useMemo6, useRef as useRef5 } from "react";
4617
+ import { useCallback as useCallback7, useMemo as useMemo6, useRef as useRef6 } from "react";
4384
4618
  import { useVirtualizer } from "@tanstack/react-virtual";
4385
4619
  import { cn as cn2 } from "@petrarca/sonnet-core";
4386
- import { jsx as jsx15 } from "react/jsx-runtime";
4620
+ import { jsx as jsx17 } from "react/jsx-runtime";
4387
4621
  var SR_ONLY = "absolute h-px w-px overflow-hidden whitespace-nowrap border-0 p-0 [clip:rect(0,0,0,0)] [clip-path:inset(50%)] -m-px";
4388
- var MAX_NODE_SELECTION = 2;
4389
4622
  function GraphA11yList({
4390
4623
  visible = false,
4391
4624
  rowHeight = 28,
@@ -4419,14 +4652,14 @@ function GraphA11yList({
4419
4652
  }));
4420
4653
  return [...nodeRows, ...edgeRows];
4421
4654
  }, [live.nodes, live.edges]);
4422
- const parentRef = useRef5(null);
4655
+ const parentRef = useRef6(null);
4423
4656
  const virtualizer = useVirtualizer({
4424
4657
  count: rows.length,
4425
4658
  getScrollElement: () => parentRef.current,
4426
4659
  estimateSize: () => rowHeight,
4427
4660
  overscan: 8
4428
4661
  });
4429
- const activateNode = useCallback6(
4662
+ const activateNode = useCallback7(
4430
4663
  (id, additive) => {
4431
4664
  setSelectedEdge(null);
4432
4665
  if (!additive) {
@@ -4437,11 +4670,11 @@ function GraphA11yList({
4437
4670
  setSelectedNodeIds(selectedNodeIds.filter((x) => x !== id));
4438
4671
  return;
4439
4672
  }
4440
- setSelectedNodeIds([...selectedNodeIds, id].slice(-MAX_NODE_SELECTION));
4673
+ setSelectedNodeIds(evictToMax(selectedNodeIds, id));
4441
4674
  },
4442
4675
  [selectedNodeIds, setSelectedNodeIds, setSelectedEdge]
4443
4676
  );
4444
- const selectRow = useCallback6(
4677
+ const selectRow = useCallback7(
4445
4678
  (row, e) => {
4446
4679
  if (row.kind === "node") {
4447
4680
  activateNode(row.id, e.metaKey || e.ctrlKey || e.shiftKey);
@@ -4453,19 +4686,19 @@ function GraphA11yList({
4453
4686
  [activateNode, setSelectedNodeIds, setSelectedEdge]
4454
4687
  );
4455
4688
  const isSelected = (row) => row.kind === "node" ? selectedNodeIds.includes(row.id) : selectedEdge?.id_ === row.edge.id_;
4456
- return /* @__PURE__ */ jsx15(
4689
+ return /* @__PURE__ */ jsx17(
4457
4690
  "div",
4458
4691
  {
4459
4692
  role: "listbox",
4460
4693
  "aria-label": "Graph elements",
4461
4694
  "aria-multiselectable": true,
4462
4695
  className: cn2(visible ? "rounded-md border" : SR_ONLY, className),
4463
- children: /* @__PURE__ */ jsx15(
4696
+ children: /* @__PURE__ */ jsx17(
4464
4697
  "div",
4465
4698
  {
4466
4699
  ref: parentRef,
4467
4700
  style: { maxHeight: visible ? maxHeight : void 0, overflow: "auto" },
4468
- children: /* @__PURE__ */ jsx15(
4701
+ children: /* @__PURE__ */ jsx17(
4469
4702
  "div",
4470
4703
  {
4471
4704
  style: {
@@ -4477,7 +4710,7 @@ function GraphA11yList({
4477
4710
  const row = rows[vi.index];
4478
4711
  if (!row) return null;
4479
4712
  const selected = isSelected(row);
4480
- return /* @__PURE__ */ jsx15(
4713
+ return /* @__PURE__ */ jsx17(
4481
4714
  "button",
4482
4715
  {
4483
4716
  type: "button",
@@ -4495,7 +4728,7 @@ function GraphA11yList({
4495
4728
  height: `${vi.size}px`,
4496
4729
  transform: `translateY(${vi.start}px)`
4497
4730
  },
4498
- children: /* @__PURE__ */ jsx15("span", { className: "truncate", children: row.label })
4731
+ children: /* @__PURE__ */ jsx17("span", { className: "truncate", children: row.label })
4499
4732
  },
4500
4733
  vi.key
4501
4734
  );
@@ -4633,16 +4866,16 @@ function reconcileFilter(current, model, baselineReset, showEdgeTypes) {
4633
4866
  }
4634
4867
 
4635
4868
  // src/graph-model/GraphModelEditor.tsx
4636
- import { useCallback as useCallback7, useEffect as useEffect5, useRef as useRef6, useState as useState13 } from "react";
4869
+ import { useCallback as useCallback8, useEffect as useEffect6, useRef as useRef7, useState as useState14 } from "react";
4637
4870
  import { ResizeHandle } from "@petrarca/sonnet-ui";
4638
4871
 
4639
4872
  // src/graph-model/ModelActionPanel.tsx
4640
- import { useMemo as useMemo8, useState as useState12 } from "react";
4641
- import { Button as Button6, IconButton as IconButton2 } from "@petrarca/sonnet-ui";
4873
+ import { useMemo as useMemo8, useState as useState13 } from "react";
4874
+ import { Button as Button8, IconButton as IconButton2 } from "@petrarca/sonnet-ui";
4642
4875
  import { GitBranchPlus, CirclePlus, Waypoints } from "lucide-react";
4643
4876
 
4644
4877
  // src/graph-model/TypeDetailPanel.tsx
4645
- import { useState as useState11 } from "react";
4878
+ import { useState as useState12 } from "react";
4646
4879
  import { Badge as Badge2, IconButton } from "@petrarca/sonnet-ui";
4647
4880
  import { Plus, Pencil, Trash2 } from "lucide-react";
4648
4881
 
@@ -4962,7 +5195,7 @@ import {
4962
5195
  SheetDescription
4963
5196
  } from "@petrarca/sonnet-ui";
4964
5197
  import { JsonSchemaFormRenderer } from "@petrarca/sonnet-forms";
4965
- import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
5198
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
4966
5199
  var TYPE_OPTIONS = [
4967
5200
  "string",
4968
5201
  "integer",
@@ -5052,18 +5285,18 @@ function PropertyFormSheet({
5052
5285
  const schema = useMemo7(() => buildSchema(widgets), [widgets]);
5053
5286
  const data = useMemo7(() => toFormData(property), [property]);
5054
5287
  const isEdit = !!property;
5055
- return /* @__PURE__ */ jsx16(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs14(SheetContent, { children: [
5056
- /* @__PURE__ */ jsxs14(SheetHeader, { children: [
5057
- /* @__PURE__ */ jsxs14(SheetTitle, { children: [
5288
+ return /* @__PURE__ */ jsx18(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs16(SheetContent, { children: [
5289
+ /* @__PURE__ */ jsxs16(SheetHeader, { children: [
5290
+ /* @__PURE__ */ jsxs16(SheetTitle, { children: [
5058
5291
  isEdit ? "Edit property" : "Add property",
5059
5292
  " ",
5060
5293
  "\u2014",
5061
5294
  " ",
5062
5295
  ownerLabel
5063
5296
  ] }),
5064
- /* @__PURE__ */ jsx16(SheetDescription, { children: "Define the property and how it is rendered." })
5297
+ /* @__PURE__ */ jsx18(SheetDescription, { children: "Define the property and how it is rendered." })
5065
5298
  ] }),
5066
- /* @__PURE__ */ jsx16(SheetBody, { children: /* @__PURE__ */ jsx16(
5299
+ /* @__PURE__ */ jsx18(SheetBody, { children: /* @__PURE__ */ jsx18(
5067
5300
  JsonSchemaFormRenderer,
5068
5301
  {
5069
5302
  schema,
@@ -5082,7 +5315,7 @@ function PropertyFormSheet({
5082
5315
  }
5083
5316
 
5084
5317
  // src/graph-model/RelationFormSheet.tsx
5085
- import { useState as useState9 } from "react";
5318
+ import { useState as useState10 } from "react";
5086
5319
  import {
5087
5320
  Sheet as Sheet2,
5088
5321
  SheetContent as SheetContent2,
@@ -5090,11 +5323,11 @@ import {
5090
5323
  SheetBody as SheetBody2,
5091
5324
  SheetTitle as SheetTitle2,
5092
5325
  SheetDescription as SheetDescription2,
5093
- Button as Button4
5326
+ Button as Button6
5094
5327
  } from "@petrarca/sonnet-ui";
5095
5328
  import { FormSelect } from "@petrarca/sonnet-forms";
5096
5329
  import { ArrowRight } from "lucide-react";
5097
- import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
5330
+ import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
5098
5331
  var CARDINALITIES = ["ONE_TO_MANY", "ONE_TO_ONE"];
5099
5332
  function RelationTypeField({
5100
5333
  relType,
@@ -5107,7 +5340,7 @@ function RelationTypeField({
5107
5340
  );
5108
5341
  const options = labels.map((l) => ({ value: l, label: l }));
5109
5342
  const canCreate = edgeTypeMode === "edit";
5110
- return /* @__PURE__ */ jsx17(
5343
+ return /* @__PURE__ */ jsx19(
5111
5344
  FormSelect,
5112
5345
  {
5113
5346
  label: "Relation type",
@@ -5129,14 +5362,14 @@ function DirectionField({
5129
5362
  canSwap,
5130
5363
  onSwap
5131
5364
  }) {
5132
- return /* @__PURE__ */ jsxs15("div", { children: [
5133
- /* @__PURE__ */ jsx17("label", { className: "mb-1 block text-xs font-medium", children: "Direction" }),
5134
- /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2 text-sm", children: [
5135
- /* @__PURE__ */ jsx17("span", { className: "font-medium", children: source }),
5136
- /* @__PURE__ */ jsx17(ArrowRight, { className: "h-4 w-4 text-muted-foreground" }),
5137
- /* @__PURE__ */ jsx17("span", { className: "font-medium", children: target }),
5138
- canSwap && /* @__PURE__ */ jsx17(
5139
- Button4,
5365
+ return /* @__PURE__ */ jsxs17("div", { children: [
5366
+ /* @__PURE__ */ jsx19("label", { className: "mb-1 block text-xs font-medium", children: "Direction" }),
5367
+ /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2 text-sm", children: [
5368
+ /* @__PURE__ */ jsx19("span", { className: "font-medium", children: source }),
5369
+ /* @__PURE__ */ jsx19(ArrowRight, { className: "h-4 w-4 text-muted-foreground" }),
5370
+ /* @__PURE__ */ jsx19("span", { className: "font-medium", children: target }),
5371
+ canSwap && /* @__PURE__ */ jsx19(
5372
+ Button6,
5140
5373
  {
5141
5374
  variant: "ghost",
5142
5375
  size: "sm",
@@ -5152,10 +5385,10 @@ function CardinalityField({
5152
5385
  cardinality,
5153
5386
  onChange
5154
5387
  }) {
5155
- return /* @__PURE__ */ jsxs15("div", { children: [
5156
- /* @__PURE__ */ jsx17("label", { className: "mb-1 block text-xs font-medium", children: "Cardinality" }),
5157
- /* @__PURE__ */ jsx17("div", { className: "flex gap-2", children: CARDINALITIES.map((c) => /* @__PURE__ */ jsx17(
5158
- Button4,
5388
+ return /* @__PURE__ */ jsxs17("div", { children: [
5389
+ /* @__PURE__ */ jsx19("label", { className: "mb-1 block text-xs font-medium", children: "Cardinality" }),
5390
+ /* @__PURE__ */ jsx19("div", { className: "flex gap-2", children: CARDINALITIES.map((c) => /* @__PURE__ */ jsx19(
5391
+ Button6,
5159
5392
  {
5160
5393
  variant: c === cardinality ? "default" : "outline",
5161
5394
  size: "sm",
@@ -5176,9 +5409,9 @@ function RelationFormSheet({
5176
5409
  edgeTypeMode = "edit",
5177
5410
  onSave
5178
5411
  }) {
5179
- const [relType, setRelType] = useState9(initial?.relType ?? "");
5180
- const [swapped, setSwapped] = useState9(false);
5181
- const [cardinality, setCardinality] = useState9(
5412
+ const [relType, setRelType] = useState10(initial?.relType ?? "");
5413
+ const [swapped, setSwapped] = useState10(false);
5414
+ const [cardinality, setCardinality] = useState10(
5182
5415
  initial?.cardinality ?? "ONE_TO_MANY"
5183
5416
  );
5184
5417
  const source = swapped ? nodeB : nodeA;
@@ -5194,13 +5427,13 @@ function RelationFormSheet({
5194
5427
  });
5195
5428
  onOpenChange(false);
5196
5429
  };
5197
- return /* @__PURE__ */ jsx17(Sheet2, { open, onOpenChange, children: /* @__PURE__ */ jsxs15(SheetContent2, { children: [
5198
- /* @__PURE__ */ jsxs15(SheetHeader2, { children: [
5199
- /* @__PURE__ */ jsx17(SheetTitle2, { children: isEdit ? "Edit relation" : "Create relation" }),
5200
- /* @__PURE__ */ jsx17(SheetDescription2, { children: "A relation is stored on the source node type." })
5430
+ return /* @__PURE__ */ jsx19(Sheet2, { open, onOpenChange, children: /* @__PURE__ */ jsxs17(SheetContent2, { children: [
5431
+ /* @__PURE__ */ jsxs17(SheetHeader2, { children: [
5432
+ /* @__PURE__ */ jsx19(SheetTitle2, { children: isEdit ? "Edit relation" : "Create relation" }),
5433
+ /* @__PURE__ */ jsx19(SheetDescription2, { children: "A relation is stored on the source node type." })
5201
5434
  ] }),
5202
- /* @__PURE__ */ jsxs15(SheetBody2, { className: "flex flex-col gap-4", children: [
5203
- /* @__PURE__ */ jsx17(
5435
+ /* @__PURE__ */ jsxs17(SheetBody2, { className: "flex flex-col gap-4", children: [
5436
+ /* @__PURE__ */ jsx19(
5204
5437
  RelationTypeField,
5205
5438
  {
5206
5439
  relType,
@@ -5209,7 +5442,7 @@ function RelationFormSheet({
5209
5442
  edgeTypeMode
5210
5443
  }
5211
5444
  ),
5212
- /* @__PURE__ */ jsx17(
5445
+ /* @__PURE__ */ jsx19(
5213
5446
  DirectionField,
5214
5447
  {
5215
5448
  source,
@@ -5218,23 +5451,23 @@ function RelationFormSheet({
5218
5451
  onSwap: () => setSwapped((s) => !s)
5219
5452
  }
5220
5453
  ),
5221
- /* @__PURE__ */ jsx17(
5454
+ /* @__PURE__ */ jsx19(
5222
5455
  CardinalityField,
5223
5456
  {
5224
5457
  cardinality,
5225
5458
  onChange: setCardinality
5226
5459
  }
5227
5460
  ),
5228
- /* @__PURE__ */ jsxs15("div", { className: "flex justify-end gap-2 pt-2", children: [
5229
- /* @__PURE__ */ jsx17(Button4, { variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
5230
- /* @__PURE__ */ jsx17(Button4, { disabled: !canSave, onClick: handleSave, children: isEdit ? "Save" : "Create relation" })
5461
+ /* @__PURE__ */ jsxs17("div", { className: "flex justify-end gap-2 pt-2", children: [
5462
+ /* @__PURE__ */ jsx19(Button6, { variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
5463
+ /* @__PURE__ */ jsx19(Button6, { disabled: !canSave, onClick: handleSave, children: isEdit ? "Save" : "Create relation" })
5231
5464
  ] })
5232
5465
  ] })
5233
5466
  ] }) });
5234
5467
  }
5235
5468
 
5236
5469
  // src/graph-model/NodeTypeNameSheet.tsx
5237
- import { useState as useState10 } from "react";
5470
+ import { useState as useState11 } from "react";
5238
5471
  import {
5239
5472
  Sheet as Sheet3,
5240
5473
  SheetContent as SheetContent3,
@@ -5242,10 +5475,10 @@ import {
5242
5475
  SheetBody as SheetBody3,
5243
5476
  SheetTitle as SheetTitle3,
5244
5477
  SheetDescription as SheetDescription3,
5245
- Button as Button5,
5478
+ Button as Button7,
5246
5479
  Input as Input2
5247
5480
  } from "@petrarca/sonnet-ui";
5248
- import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
5481
+ import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
5249
5482
  function NodeTypeNameSheet({
5250
5483
  open,
5251
5484
  onOpenChange,
@@ -5254,21 +5487,21 @@ function NodeTypeNameSheet({
5254
5487
  noun = "node",
5255
5488
  onSave
5256
5489
  }) {
5257
- const [name, setName] = useState10(initial ?? "");
5490
+ const [name, setName] = useState11(initial ?? "");
5258
5491
  const isEdit = initial !== void 0;
5259
5492
  const trimmed = name.trim();
5260
5493
  const duplicate = trimmed !== initial && existing.includes(trimmed);
5261
5494
  const canSave = trimmed.length > 0 && !duplicate;
5262
5495
  const typeNoun = noun === "edge" ? "edge type" : "node type";
5263
- return /* @__PURE__ */ jsx18(Sheet3, { open, onOpenChange, children: /* @__PURE__ */ jsxs16(SheetContent3, { children: [
5264
- /* @__PURE__ */ jsxs16(SheetHeader3, { children: [
5265
- /* @__PURE__ */ jsx18(SheetTitle3, { children: isEdit ? `Rename ${typeNoun}` : `Add ${typeNoun}` }),
5266
- /* @__PURE__ */ jsx18(SheetDescription3, { children: "The label identifies this type and its instances." })
5496
+ return /* @__PURE__ */ jsx20(Sheet3, { open, onOpenChange, children: /* @__PURE__ */ jsxs18(SheetContent3, { children: [
5497
+ /* @__PURE__ */ jsxs18(SheetHeader3, { children: [
5498
+ /* @__PURE__ */ jsx20(SheetTitle3, { children: isEdit ? `Rename ${typeNoun}` : `Add ${typeNoun}` }),
5499
+ /* @__PURE__ */ jsx20(SheetDescription3, { children: "The label identifies this type and its instances." })
5267
5500
  ] }),
5268
- /* @__PURE__ */ jsxs16(SheetBody3, { className: "flex flex-col gap-4", children: [
5269
- /* @__PURE__ */ jsxs16("div", { children: [
5270
- /* @__PURE__ */ jsx18("label", { className: "mb-1 block text-xs font-medium", children: "Label" }),
5271
- /* @__PURE__ */ jsx18(
5501
+ /* @__PURE__ */ jsxs18(SheetBody3, { className: "flex flex-col gap-4", children: [
5502
+ /* @__PURE__ */ jsxs18("div", { children: [
5503
+ /* @__PURE__ */ jsx20("label", { className: "mb-1 block text-xs font-medium", children: "Label" }),
5504
+ /* @__PURE__ */ jsx20(
5272
5505
  Input2,
5273
5506
  {
5274
5507
  value: name,
@@ -5277,12 +5510,12 @@ function NodeTypeNameSheet({
5277
5510
  autoFocus: true
5278
5511
  }
5279
5512
  ),
5280
- duplicate && /* @__PURE__ */ jsx18("p", { className: "mt-1 text-xs text-destructive", children: "A type with this label already exists." })
5513
+ duplicate && /* @__PURE__ */ jsx20("p", { className: "mt-1 text-xs text-destructive", children: "A type with this label already exists." })
5281
5514
  ] }),
5282
- /* @__PURE__ */ jsxs16("div", { className: "flex justify-end gap-2", children: [
5283
- /* @__PURE__ */ jsx18(Button5, { variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
5284
- /* @__PURE__ */ jsx18(
5285
- Button5,
5515
+ /* @__PURE__ */ jsxs18("div", { className: "flex justify-end gap-2", children: [
5516
+ /* @__PURE__ */ jsx20(Button7, { variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
5517
+ /* @__PURE__ */ jsx20(
5518
+ Button7,
5286
5519
  {
5287
5520
  disabled: !canSave,
5288
5521
  onClick: () => {
@@ -5298,7 +5531,7 @@ function NodeTypeNameSheet({
5298
5531
  }
5299
5532
 
5300
5533
  // src/graph-model/TypeDetailPanel.tsx
5301
- import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
5534
+ import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
5302
5535
  function relationViewOf(view) {
5303
5536
  return {
5304
5537
  relType: view.relType,
@@ -5311,9 +5544,9 @@ function SectionTitle({
5311
5544
  children,
5312
5545
  action
5313
5546
  }) {
5314
- return /* @__PURE__ */ jsxs17("div", { className: "mt-4 mb-2 flex items-center gap-2", children: [
5315
- /* @__PURE__ */ jsx19("span", { className: "heading-meta", children }),
5316
- action && /* @__PURE__ */ jsx19("span", { className: "ml-auto", children: action })
5547
+ return /* @__PURE__ */ jsxs19("div", { className: "mt-4 mb-2 flex items-center gap-2", children: [
5548
+ /* @__PURE__ */ jsx21("span", { className: "heading-meta", children }),
5549
+ action && /* @__PURE__ */ jsx21("span", { className: "ml-auto", children: action })
5317
5550
  ] });
5318
5551
  }
5319
5552
  function PropertyRows({
@@ -5324,37 +5557,37 @@ function PropertyRows({
5324
5557
  onDelete
5325
5558
  }) {
5326
5559
  if (properties.length === 0) {
5327
- return /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: emptyText });
5560
+ return /* @__PURE__ */ jsx21("p", { className: "text-xs text-muted-foreground", children: emptyText });
5328
5561
  }
5329
- return /* @__PURE__ */ jsx19("div", { className: "rounded-lg border divide-y", children: properties.map((p) => /* @__PURE__ */ jsxs17(
5562
+ return /* @__PURE__ */ jsx21("div", { className: "rounded-lg border divide-y", children: properties.map((p) => /* @__PURE__ */ jsxs19(
5330
5563
  "div",
5331
5564
  {
5332
5565
  className: "group/row flex items-center gap-2 px-3 py-2",
5333
5566
  children: [
5334
- /* @__PURE__ */ jsx19("span", { className: "text-sm font-medium min-w-24", children: p.name }),
5335
- /* @__PURE__ */ jsxs17("span", { className: "flex-1 font-mono text-xs text-muted-foreground", children: [
5567
+ /* @__PURE__ */ jsx21("span", { className: "text-sm font-medium min-w-24", children: p.name }),
5568
+ /* @__PURE__ */ jsxs19("span", { className: "flex-1 font-mono text-xs text-muted-foreground", children: [
5336
5569
  p.type,
5337
5570
  p.widget ? ` \xB7 ${p.widget}` : ""
5338
5571
  ] }),
5339
- p.required && /* @__PURE__ */ jsx19(Badge2, { badgeColor: "orange", children: "required" }),
5340
- editable && /* @__PURE__ */ jsxs17("span", { className: "flex gap-1", children: [
5341
- /* @__PURE__ */ jsx19(
5572
+ p.required && /* @__PURE__ */ jsx21(Badge2, { badgeColor: "orange", children: "required" }),
5573
+ editable && /* @__PURE__ */ jsxs19("span", { className: "flex gap-1", children: [
5574
+ /* @__PURE__ */ jsx21(
5342
5575
  IconButton,
5343
5576
  {
5344
5577
  label: "Edit property",
5345
5578
  revealOnHover: true,
5346
5579
  onClick: () => onEdit(p),
5347
- children: /* @__PURE__ */ jsx19(Pencil, {})
5580
+ children: /* @__PURE__ */ jsx21(Pencil, {})
5348
5581
  }
5349
5582
  ),
5350
- /* @__PURE__ */ jsx19(
5583
+ /* @__PURE__ */ jsx21(
5351
5584
  IconButton,
5352
5585
  {
5353
5586
  label: "Delete property",
5354
5587
  tone: "destructive",
5355
5588
  revealOnHover: true,
5356
5589
  onClick: () => onDelete(p),
5357
- children: /* @__PURE__ */ jsx19(Trash2, {})
5590
+ children: /* @__PURE__ */ jsx21(Trash2, {})
5358
5591
  }
5359
5592
  )
5360
5593
  ] })
@@ -5408,9 +5641,9 @@ function TypeDetailPanel({
5408
5641
  capabilities,
5409
5642
  widgetOptions
5410
5643
  }) {
5411
- const [sheet, setSheet] = useState11({ open: false });
5412
- const [relSheet, setRelSheet] = useState11(null);
5413
- const [renameOpen, setRenameOpen] = useState11(false);
5644
+ const [sheet, setSheet] = useState12({ open: false });
5645
+ const [relSheet, setRelSheet] = useState12(null);
5646
+ const [renameOpen, setRenameOpen] = useState12(false);
5414
5647
  const view = resolveView(model, selection);
5415
5648
  if (!view) return null;
5416
5649
  const editable = !readOnly && !!onChange;
@@ -5437,7 +5670,7 @@ function TypeDetailPanel({
5437
5670
  if (!onChange) return;
5438
5671
  onChange(updateRelation(model, source, prev.relType, prev.target, input));
5439
5672
  };
5440
- const propertiesSection = propsHidden ? null : /* @__PURE__ */ jsx19(
5673
+ const propertiesSection = propsHidden ? null : /* @__PURE__ */ jsx21(
5441
5674
  PropertiesSection,
5442
5675
  {
5443
5676
  view,
@@ -5447,8 +5680,8 @@ function TypeDetailPanel({
5447
5680
  onDelete: removeProperty
5448
5681
  }
5449
5682
  );
5450
- return /* @__PURE__ */ jsxs17("div", { children: [
5451
- view.kind === "node" ? /* @__PURE__ */ jsx19(
5683
+ return /* @__PURE__ */ jsxs19("div", { children: [
5684
+ view.kind === "node" ? /* @__PURE__ */ jsx21(
5452
5685
  NodeDetail,
5453
5686
  {
5454
5687
  view,
@@ -5460,7 +5693,7 @@ function TypeDetailPanel({
5460
5693
  onDeleteRelation: removeRelation,
5461
5694
  children: propertiesSection
5462
5695
  }
5463
- ) : view.kind === "edge" ? /* @__PURE__ */ jsx19(
5696
+ ) : view.kind === "edge" ? /* @__PURE__ */ jsx21(
5464
5697
  EdgeDetail,
5465
5698
  {
5466
5699
  view,
@@ -5469,7 +5702,7 @@ function TypeDetailPanel({
5469
5702
  onDelete: () => onChange?.(deleteEdgeType(model, view.label)),
5470
5703
  children: propertiesSection
5471
5704
  }
5472
- ) : /* @__PURE__ */ jsx19(
5705
+ ) : /* @__PURE__ */ jsx21(
5473
5706
  RelationDetail,
5474
5707
  {
5475
5708
  view,
@@ -5479,7 +5712,7 @@ function TypeDetailPanel({
5479
5712
  children: propertiesSection
5480
5713
  }
5481
5714
  ),
5482
- editable && /* @__PURE__ */ jsx19(
5715
+ editable && /* @__PURE__ */ jsx21(
5483
5716
  DetailSheets,
5484
5717
  {
5485
5718
  model,
@@ -5523,8 +5756,8 @@ function DetailSheets({
5523
5756
  onRename
5524
5757
  }) {
5525
5758
  const edgeTypeOptions = model.filter(isEdgeType).map(typeLabel);
5526
- return /* @__PURE__ */ jsxs17(Fragment4, { children: [
5527
- /* @__PURE__ */ jsx19(
5759
+ return /* @__PURE__ */ jsxs19(Fragment4, { children: [
5760
+ /* @__PURE__ */ jsx21(
5528
5761
  PropertyFormSheet,
5529
5762
  {
5530
5763
  open: sheet.open,
@@ -5535,7 +5768,7 @@ function DetailSheets({
5535
5768
  onSave: onSaveProperty
5536
5769
  }
5537
5770
  ),
5538
- relSheet && (view.kind === "node" || view.kind === "relation") && /* @__PURE__ */ jsx19(
5771
+ relSheet && (view.kind === "node" || view.kind === "relation") && /* @__PURE__ */ jsx21(
5539
5772
  RelationFormSheet,
5540
5773
  {
5541
5774
  open: !!relSheet,
@@ -5552,7 +5785,7 @@ function DetailSheets({
5552
5785
  },
5553
5786
  `${relSheet.relType}:${relSheet.target}`
5554
5787
  ),
5555
- (view.kind === "node" || view.kind === "edge") && renameOpen && /* @__PURE__ */ jsx19(
5788
+ (view.kind === "node" || view.kind === "edge") && renameOpen && /* @__PURE__ */ jsx21(
5556
5789
  NodeTypeNameSheet,
5557
5790
  {
5558
5791
  open: renameOpen,
@@ -5573,15 +5806,15 @@ function PropertiesSection({
5573
5806
  onEdit,
5574
5807
  onDelete
5575
5808
  }) {
5576
- return /* @__PURE__ */ jsxs17(Fragment4, { children: [
5577
- /* @__PURE__ */ jsx19(
5809
+ return /* @__PURE__ */ jsxs19(Fragment4, { children: [
5810
+ /* @__PURE__ */ jsx21(
5578
5811
  SectionTitle,
5579
5812
  {
5580
- action: editable ? /* @__PURE__ */ jsx19(IconButton, { label: "Add property", onClick: onAdd, children: /* @__PURE__ */ jsx19(Plus, {}) }) : void 0,
5813
+ action: editable ? /* @__PURE__ */ jsx21(IconButton, { label: "Add property", onClick: onAdd, children: /* @__PURE__ */ jsx21(Plus, {}) }) : void 0,
5581
5814
  children: "Properties"
5582
5815
  }
5583
5816
  ),
5584
- /* @__PURE__ */ jsx19(
5817
+ /* @__PURE__ */ jsx21(
5585
5818
  PropertyRows,
5586
5819
  {
5587
5820
  properties: view.properties,
@@ -5603,11 +5836,11 @@ function NodeDetail({
5603
5836
  onDeleteRelation,
5604
5837
  children
5605
5838
  }) {
5606
- return /* @__PURE__ */ jsxs17(Fragment4, { children: [
5607
- /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
5608
- /* @__PURE__ */ jsx19("span", { className: "text-base font-semibold", children: view.label }),
5609
- /* @__PURE__ */ jsx19(Badge2, { children: "NODE TYPE" }),
5610
- nodeTypeEditable && /* @__PURE__ */ jsx19(
5839
+ return /* @__PURE__ */ jsxs19(Fragment4, { children: [
5840
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", children: [
5841
+ /* @__PURE__ */ jsx21("span", { className: "text-base font-semibold", children: view.label }),
5842
+ /* @__PURE__ */ jsx21(Badge2, { children: "NODE TYPE" }),
5843
+ nodeTypeEditable && /* @__PURE__ */ jsx21(
5611
5844
  RenameDeleteActions,
5612
5845
  {
5613
5846
  noun: "node type",
@@ -5616,16 +5849,16 @@ function NodeDetail({
5616
5849
  }
5617
5850
  )
5618
5851
  ] }),
5619
- view.description && /* @__PURE__ */ jsx19("p", { className: "mt-1 text-sm text-muted-foreground", children: view.description }),
5852
+ view.description && /* @__PURE__ */ jsx21("p", { className: "mt-1 text-sm text-muted-foreground", children: view.description }),
5620
5853
  children,
5621
- /* @__PURE__ */ jsx19(SectionTitle, { children: "Relations" }),
5622
- view.relations.length === 0 ? /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: "No relations." }) : /* @__PURE__ */ jsx19("div", { className: "rounded-lg border divide-y", children: view.relations.map((r) => /* @__PURE__ */ jsxs17(
5854
+ /* @__PURE__ */ jsx21(SectionTitle, { children: "Relations" }),
5855
+ view.relations.length === 0 ? /* @__PURE__ */ jsx21("p", { className: "text-xs text-muted-foreground", children: "No relations." }) : /* @__PURE__ */ jsx21("div", { className: "rounded-lg border divide-y", children: view.relations.map((r) => /* @__PURE__ */ jsxs19(
5623
5856
  "div",
5624
5857
  {
5625
5858
  className: "group/row flex items-center gap-2 px-3 py-2",
5626
5859
  children: [
5627
- /* @__PURE__ */ jsx19("span", { className: "text-sm font-medium min-w-24", children: r.relType }),
5628
- /* @__PURE__ */ jsxs17("span", { className: "flex-1 font-mono text-xs text-muted-foreground", children: [
5860
+ /* @__PURE__ */ jsx21("span", { className: "text-sm font-medium min-w-24", children: r.relType }),
5861
+ /* @__PURE__ */ jsxs19("span", { className: "flex-1 font-mono text-xs text-muted-foreground", children: [
5629
5862
  "\u2192",
5630
5863
  " ",
5631
5864
  r.target,
@@ -5634,24 +5867,24 @@ function NodeDetail({
5634
5867
  " ",
5635
5868
  cardinalityLabel(r.cardinality)
5636
5869
  ] }),
5637
- relationsEditable && /* @__PURE__ */ jsxs17("span", { className: "flex gap-1", children: [
5638
- /* @__PURE__ */ jsx19(
5870
+ relationsEditable && /* @__PURE__ */ jsxs19("span", { className: "flex gap-1", children: [
5871
+ /* @__PURE__ */ jsx21(
5639
5872
  IconButton,
5640
5873
  {
5641
5874
  label: "Edit relation",
5642
5875
  revealOnHover: true,
5643
5876
  onClick: () => onEditRelation(r),
5644
- children: /* @__PURE__ */ jsx19(Pencil, {})
5877
+ children: /* @__PURE__ */ jsx21(Pencil, {})
5645
5878
  }
5646
5879
  ),
5647
- /* @__PURE__ */ jsx19(
5880
+ /* @__PURE__ */ jsx21(
5648
5881
  IconButton,
5649
5882
  {
5650
5883
  label: "Delete relation",
5651
5884
  tone: "destructive",
5652
5885
  revealOnHover: true,
5653
5886
  onClick: () => onDeleteRelation(r),
5654
- children: /* @__PURE__ */ jsx19(Trash2, {})
5887
+ children: /* @__PURE__ */ jsx21(Trash2, {})
5655
5888
  }
5656
5889
  )
5657
5890
  ] })
@@ -5664,7 +5897,7 @@ function NodeDetail({
5664
5897
  function EdgeTypeNote({
5665
5898
  children
5666
5899
  }) {
5667
- return /* @__PURE__ */ jsx19("p", { className: "mt-3 rounded-md bg-muted/50 px-3 py-2 text-xs text-muted-foreground", children });
5900
+ return /* @__PURE__ */ jsx21("p", { className: "mt-3 rounded-md bg-muted/50 px-3 py-2 text-xs text-muted-foreground", children });
5668
5901
  }
5669
5902
  function RenameDeleteActions({
5670
5903
  noun,
@@ -5672,15 +5905,15 @@ function RenameDeleteActions({
5672
5905
  onRename,
5673
5906
  onDelete
5674
5907
  }) {
5675
- return /* @__PURE__ */ jsxs17("span", { className: "ml-auto flex gap-1", children: [
5676
- /* @__PURE__ */ jsx19(IconButton, { label: `${editVerb} ${noun}`, onClick: onRename, children: /* @__PURE__ */ jsx19(Pencil, {}) }),
5677
- /* @__PURE__ */ jsx19(
5908
+ return /* @__PURE__ */ jsxs19("span", { className: "ml-auto flex gap-1", children: [
5909
+ /* @__PURE__ */ jsx21(IconButton, { label: `${editVerb} ${noun}`, onClick: onRename, children: /* @__PURE__ */ jsx21(Pencil, {}) }),
5910
+ /* @__PURE__ */ jsx21(
5678
5911
  IconButton,
5679
5912
  {
5680
5913
  label: `Delete ${noun}`,
5681
5914
  tone: "destructive",
5682
5915
  onClick: onDelete,
5683
- children: /* @__PURE__ */ jsx19(Trash2, {})
5916
+ children: /* @__PURE__ */ jsx21(Trash2, {})
5684
5917
  }
5685
5918
  )
5686
5919
  ] });
@@ -5692,11 +5925,11 @@ function EdgeDetail({
5692
5925
  onDelete,
5693
5926
  children
5694
5927
  }) {
5695
- return /* @__PURE__ */ jsxs17(Fragment4, { children: [
5696
- /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
5697
- /* @__PURE__ */ jsx19("span", { className: "text-base font-semibold", children: view.label }),
5698
- /* @__PURE__ */ jsx19(Badge2, { children: "EDGE TYPE" }),
5699
- editable && /* @__PURE__ */ jsx19(
5928
+ return /* @__PURE__ */ jsxs19(Fragment4, { children: [
5929
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", children: [
5930
+ /* @__PURE__ */ jsx21("span", { className: "text-base font-semibold", children: view.label }),
5931
+ /* @__PURE__ */ jsx21(Badge2, { children: "EDGE TYPE" }),
5932
+ editable && /* @__PURE__ */ jsx21(
5700
5933
  RenameDeleteActions,
5701
5934
  {
5702
5935
  noun: "edge type",
@@ -5705,11 +5938,11 @@ function EdgeDetail({
5705
5938
  }
5706
5939
  )
5707
5940
  ] }),
5708
- view.description && /* @__PURE__ */ jsx19("p", { className: "mt-1 text-sm text-muted-foreground", children: view.description }),
5709
- /* @__PURE__ */ jsxs17(EdgeTypeNote, { children: [
5941
+ view.description && /* @__PURE__ */ jsx21("p", { className: "mt-1 text-sm text-muted-foreground", children: view.description }),
5942
+ /* @__PURE__ */ jsxs19(EdgeTypeNote, { children: [
5710
5943
  "These properties belong to the",
5711
5944
  " ",
5712
- /* @__PURE__ */ jsx19("span", { className: "font-medium", children: view.label }),
5945
+ /* @__PURE__ */ jsx21("span", { className: "font-medium", children: view.label }),
5713
5946
  " edge type and are shared by every relation of this type."
5714
5947
  ] }),
5715
5948
  children
@@ -5722,11 +5955,11 @@ function RelationDetail({
5722
5955
  onDelete,
5723
5956
  children
5724
5957
  }) {
5725
- return /* @__PURE__ */ jsxs17(Fragment4, { children: [
5726
- /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
5727
- /* @__PURE__ */ jsx19("span", { className: "text-base font-semibold", children: view.relType }),
5728
- /* @__PURE__ */ jsx19(Badge2, { children: "RELATION" }),
5729
- editable && /* @__PURE__ */ jsx19(
5958
+ return /* @__PURE__ */ jsxs19(Fragment4, { children: [
5959
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", children: [
5960
+ /* @__PURE__ */ jsx21("span", { className: "text-base font-semibold", children: view.relType }),
5961
+ /* @__PURE__ */ jsx21(Badge2, { children: "RELATION" }),
5962
+ editable && /* @__PURE__ */ jsx21(
5730
5963
  RenameDeleteActions,
5731
5964
  {
5732
5965
  noun: "relation",
@@ -5736,17 +5969,17 @@ function RelationDetail({
5736
5969
  }
5737
5970
  )
5738
5971
  ] }),
5739
- /* @__PURE__ */ jsxs17("p", { className: "mt-1 text-sm text-muted-foreground", children: [
5740
- /* @__PURE__ */ jsx19("span", { className: "font-medium text-foreground", children: view.source }),
5972
+ /* @__PURE__ */ jsxs19("p", { className: "mt-1 text-sm text-muted-foreground", children: [
5973
+ /* @__PURE__ */ jsx21("span", { className: "font-medium text-foreground", children: view.source }),
5741
5974
  " \u2192 ",
5742
- /* @__PURE__ */ jsx19("span", { className: "font-medium text-foreground", children: view.target }),
5975
+ /* @__PURE__ */ jsx21("span", { className: "font-medium text-foreground", children: view.target }),
5743
5976
  " \xB7 ",
5744
5977
  cardinalityLabel(view.cardinality)
5745
5978
  ] }),
5746
- /* @__PURE__ */ jsxs17(EdgeTypeNote, { children: [
5979
+ /* @__PURE__ */ jsxs19(EdgeTypeNote, { children: [
5747
5980
  "Properties are defined on the",
5748
5981
  " ",
5749
- /* @__PURE__ */ jsx19("span", { className: "font-medium", children: view.relType }),
5982
+ /* @__PURE__ */ jsx21("span", { className: "font-medium", children: view.relType }),
5750
5983
  " edge type, shared by every relation of this type -- not on this single relation."
5751
5984
  ] }),
5752
5985
  children
@@ -5811,7 +6044,7 @@ function validateCapabilities(input) {
5811
6044
  }
5812
6045
 
5813
6046
  // src/graph-model/ModelActionPanel.tsx
5814
- import { Fragment as Fragment5, jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
6047
+ import { Fragment as Fragment5, jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
5815
6048
  function nodeTypeLabels(model) {
5816
6049
  return model.filter(isNodeType).map(typeLabel);
5817
6050
  }
@@ -5825,19 +6058,19 @@ function CreateRelationPrompt({
5825
6058
  edgeTypeMode,
5826
6059
  onChange
5827
6060
  }) {
5828
- const [open, setOpen] = useState12(false);
5829
- return /* @__PURE__ */ jsxs18("div", { children: [
5830
- /* @__PURE__ */ jsxs18("div", { className: "text-sm", children: [
5831
- /* @__PURE__ */ jsx20("span", { className: "font-medium", children: nodeA }),
6061
+ const [open, setOpen] = useState13(false);
6062
+ return /* @__PURE__ */ jsxs20("div", { children: [
6063
+ /* @__PURE__ */ jsxs20("div", { className: "text-sm", children: [
6064
+ /* @__PURE__ */ jsx22("span", { className: "font-medium", children: nodeA }),
5832
6065
  " and ",
5833
- /* @__PURE__ */ jsx20("span", { className: "font-medium", children: nodeB }),
6066
+ /* @__PURE__ */ jsx22("span", { className: "font-medium", children: nodeB }),
5834
6067
  " selected."
5835
6068
  ] }),
5836
- /* @__PURE__ */ jsxs18(Button6, { size: "sm", className: "mt-3 gap-1.5", onClick: () => setOpen(true), children: [
5837
- /* @__PURE__ */ jsx20(GitBranchPlus, { className: "h-4 w-4" }),
6069
+ /* @__PURE__ */ jsxs20(Button8, { size: "sm", className: "mt-3 gap-1.5", onClick: () => setOpen(true), children: [
6070
+ /* @__PURE__ */ jsx22(GitBranchPlus, { className: "h-4 w-4" }),
5838
6071
  "Create relation"
5839
6072
  ] }),
5840
- /* @__PURE__ */ jsx20(
6073
+ /* @__PURE__ */ jsx22(
5841
6074
  RelationFormSheet,
5842
6075
  {
5843
6076
  open,
@@ -5893,7 +6126,7 @@ function ModelActionPanel({
5893
6126
  const schema = findType(model, l);
5894
6127
  return schema ? isNodeType(schema) : false;
5895
6128
  }) : [];
5896
- return /* @__PURE__ */ jsx20("div", { className: "h-full overflow-auto p-4", children: /* @__PURE__ */ jsx20(
6129
+ return /* @__PURE__ */ jsx22("div", { className: "h-full overflow-auto p-4", children: /* @__PURE__ */ jsx22(
5897
6130
  PanelBody,
5898
6131
  {
5899
6132
  model,
@@ -5916,7 +6149,7 @@ function PanelBody({
5916
6149
  twoLabels
5917
6150
  }) {
5918
6151
  if (selection) {
5919
- return /* @__PURE__ */ jsx20(
6152
+ return /* @__PURE__ */ jsx22(
5920
6153
  TypeDetailPanel,
5921
6154
  {
5922
6155
  model,
@@ -5930,7 +6163,7 @@ function PanelBody({
5930
6163
  }
5931
6164
  const canEdit = !readOnly && !!onChange;
5932
6165
  if (twoLabels.length === 2 && canEdit && caps.relations === "edit") {
5933
- return /* @__PURE__ */ jsx20(
6166
+ return /* @__PURE__ */ jsx22(
5934
6167
  CreateRelationPrompt,
5935
6168
  {
5936
6169
  nodeA: twoLabels[0],
@@ -5941,8 +6174,8 @@ function PanelBody({
5941
6174
  }
5942
6175
  );
5943
6176
  }
5944
- return /* @__PURE__ */ jsxs18(Fragment5, { children: [
5945
- canEdit && /* @__PURE__ */ jsx20(
6177
+ return /* @__PURE__ */ jsxs20(Fragment5, { children: [
6178
+ canEdit && /* @__PURE__ */ jsx22(
5946
6179
  AddTypeToolbar,
5947
6180
  {
5948
6181
  model,
@@ -5951,7 +6184,7 @@ function PanelBody({
5951
6184
  canAddEdge: caps.edgeTypes === "edit"
5952
6185
  }
5953
6186
  ),
5954
- /* @__PURE__ */ jsx20(
6187
+ /* @__PURE__ */ jsx22(
5955
6188
  Overview_default,
5956
6189
  {
5957
6190
  nodeBuckets: [
@@ -5970,9 +6203,9 @@ function AddTypeIconButton({
5970
6203
  existing,
5971
6204
  onSave
5972
6205
  }) {
5973
- const [open, setOpen] = useState12(false);
5974
- return /* @__PURE__ */ jsxs18(Fragment5, { children: [
5975
- /* @__PURE__ */ jsx20(
6206
+ const [open, setOpen] = useState13(false);
6207
+ return /* @__PURE__ */ jsxs20(Fragment5, { children: [
6208
+ /* @__PURE__ */ jsx22(
5976
6209
  IconButton2,
5977
6210
  {
5978
6211
  label: tooltip,
@@ -5981,7 +6214,7 @@ function AddTypeIconButton({
5981
6214
  children: icon
5982
6215
  }
5983
6216
  ),
5984
- /* @__PURE__ */ jsx20(
6217
+ /* @__PURE__ */ jsx22(
5985
6218
  NodeTypeNameSheet,
5986
6219
  {
5987
6220
  open,
@@ -6001,22 +6234,22 @@ function AddTypeToolbar({
6001
6234
  }) {
6002
6235
  if (!canAddNode && !canAddEdge) return null;
6003
6236
  const existing = model.map(typeLabel);
6004
- return /* @__PURE__ */ jsxs18("div", { className: "mb-3 flex gap-1.5", children: [
6005
- canAddNode && /* @__PURE__ */ jsx20(
6237
+ return /* @__PURE__ */ jsxs20("div", { className: "mb-3 flex gap-1.5", children: [
6238
+ canAddNode && /* @__PURE__ */ jsx22(
6006
6239
  AddTypeIconButton,
6007
6240
  {
6008
6241
  tooltip: "Add node type",
6009
- icon: /* @__PURE__ */ jsx20(CirclePlus, {}),
6242
+ icon: /* @__PURE__ */ jsx22(CirclePlus, {}),
6010
6243
  noun: "node",
6011
6244
  existing,
6012
6245
  onSave: (name) => onChange(addNodeType(model, name))
6013
6246
  }
6014
6247
  ),
6015
- canAddEdge && /* @__PURE__ */ jsx20(
6248
+ canAddEdge && /* @__PURE__ */ jsx22(
6016
6249
  AddTypeIconButton,
6017
6250
  {
6018
6251
  tooltip: "Add edge type",
6019
- icon: /* @__PURE__ */ jsx20(Waypoints, {}),
6252
+ icon: /* @__PURE__ */ jsx22(Waypoints, {}),
6020
6253
  noun: "edge",
6021
6254
  existing,
6022
6255
  onSave: (name) => onChange(addEdgeType(model, name))
@@ -6026,7 +6259,7 @@ function AddTypeToolbar({
6026
6259
  }
6027
6260
 
6028
6261
  // src/graph-model/GraphModelEditor.tsx
6029
- import { Fragment as Fragment6, jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
6262
+ import { Fragment as Fragment6, jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
6030
6263
  function createEditorStores() {
6031
6264
  const graphFilter = createGraphFilterStore();
6032
6265
  const graphData = createGraphDataStore();
@@ -6040,7 +6273,7 @@ function createEditorStores() {
6040
6273
  expandNode: async () => ({ nodes: [], edges: [], has_more: false }),
6041
6274
  getNodes: async () => []
6042
6275
  });
6043
- return { graphFilter, graphData, graphExploration, selection };
6276
+ return { graphFilter, graphData, graphExploration, selection, graphFocus };
6044
6277
  }
6045
6278
  function loadModel(stores, model) {
6046
6279
  const { nodes: rawNodes, edges: rawEdges } = modelToGraph(model);
@@ -6063,14 +6296,14 @@ function GraphModelEditor({
6063
6296
  defaultRenderer = "cytoscape",
6064
6297
  hideActionPanel = false
6065
6298
  }) {
6066
- const storesRef = useRef6(null);
6299
+ const storesRef = useRef7(null);
6067
6300
  if (!storesRef.current) storesRef.current = createEditorStores();
6068
6301
  const stores = storesRef.current;
6069
6302
  const caps = resolveCapabilities(capabilities, readOnly);
6070
6303
  const showEdgeTypes = caps.showEdgeTypes;
6071
- const firstLoadRef = useRef6(true);
6072
- const prevShowEdgeTypesRef = useRef6(showEdgeTypes);
6073
- useEffect5(() => {
6304
+ const firstLoadRef = useRef7(true);
6305
+ const prevShowEdgeTypesRef = useRef7(showEdgeTypes);
6306
+ useEffect6(() => {
6074
6307
  loadModel(stores, value);
6075
6308
  const filter = stores.graphFilter.getState();
6076
6309
  const baselineReset = firstLoadRef.current || prevShowEdgeTypesRef.current !== showEdgeTypes;
@@ -6084,9 +6317,9 @@ function GraphModelEditor({
6084
6317
  firstLoadRef.current = false;
6085
6318
  prevShowEdgeTypesRef.current = showEdgeTypes;
6086
6319
  }, [stores, value, showEdgeTypes]);
6087
- const rowRef = useRef6(null);
6088
- const [panelWidth, setPanelWidth] = useState13(380);
6089
- const startResize = useCallback7((e) => {
6320
+ const rowRef = useRef7(null);
6321
+ const [panelWidth, setPanelWidth] = useState14(380);
6322
+ const startResize = useCallback8((e) => {
6090
6323
  e.preventDefault();
6091
6324
  const target = e.currentTarget;
6092
6325
  target.setPointerCapture(e.pointerId);
@@ -6101,10 +6334,10 @@ function GraphModelEditor({
6101
6334
  window.addEventListener("pointermove", move);
6102
6335
  window.addEventListener("pointerup", up);
6103
6336
  }, []);
6104
- return /* @__PURE__ */ jsx21(GraphStoresContext.Provider, { value: stores, children: /* @__PURE__ */ jsx21(GraphRendererProvider, { defaultRenderer, children: /* @__PURE__ */ jsxs19("div", { ref: rowRef, className: "flex w-full h-full overflow-hidden", children: [
6105
- /* @__PURE__ */ jsx21("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx21(GraphVisualizer_default, {}) }),
6106
- !hideActionPanel && /* @__PURE__ */ jsxs19(Fragment6, { children: [
6107
- /* @__PURE__ */ jsx21(
6337
+ return /* @__PURE__ */ jsx23(GraphStoresContext.Provider, { value: stores, children: /* @__PURE__ */ jsx23(GraphRendererProvider, { defaultRenderer, children: /* @__PURE__ */ jsxs21("div", { ref: rowRef, className: "flex w-full h-full overflow-hidden", children: [
6338
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx23(GraphVisualizer_default, {}) }),
6339
+ !hideActionPanel && /* @__PURE__ */ jsxs21(Fragment6, { children: [
6340
+ /* @__PURE__ */ jsx23(
6108
6341
  ResizeHandle,
6109
6342
  {
6110
6343
  orientation: "vertical",
@@ -6115,12 +6348,12 @@ function GraphModelEditor({
6115
6348
  onDoubleClick: () => setPanelWidth(380)
6116
6349
  }
6117
6350
  ),
6118
- /* @__PURE__ */ jsx21(
6351
+ /* @__PURE__ */ jsx23(
6119
6352
  "div",
6120
6353
  {
6121
6354
  style: { width: panelWidth },
6122
6355
  className: "shrink-0 border-l overflow-hidden bg-card",
6123
- children: /* @__PURE__ */ jsx21(
6356
+ children: /* @__PURE__ */ jsx23(
6124
6357
  ModelActionPanel,
6125
6358
  {
6126
6359
  model: value,
@@ -6284,17 +6517,17 @@ function enrichSchemaWithRelations(baseSchema, relations) {
6284
6517
  }
6285
6518
 
6286
6519
  // src/graph-edit/GraphEditToolbar.tsx
6287
- import { useCallback as useCallback11, useMemo as useMemo11, useState as useState17 } from "react";
6520
+ import { useCallback as useCallback12, useMemo as useMemo11, useState as useState18 } from "react";
6288
6521
  import { IconButton as IconButton4 } from "@petrarca/sonnet-ui";
6289
6522
  import { Plus as Plus3, Edit, Link, Trash2 as Trash23 } from "lucide-react";
6290
6523
 
6291
6524
  // src/graph-edit/NodeFormSheet.tsx
6292
6525
  import {
6293
- useCallback as useCallback8,
6294
- useEffect as useEffect6,
6526
+ useCallback as useCallback9,
6527
+ useEffect as useEffect7,
6295
6528
  useMemo as useMemo9,
6296
- useRef as useRef7,
6297
- useState as useState14
6529
+ useRef as useRef8,
6530
+ useState as useState15
6298
6531
  } from "react";
6299
6532
  import {
6300
6533
  Sheet as Sheet4,
@@ -6303,7 +6536,7 @@ import {
6303
6536
  SheetBody as SheetBody4,
6304
6537
  SheetTitle as SheetTitle4,
6305
6538
  SheetDescription as SheetDescription4,
6306
- Alert
6539
+ Alert as Alert2
6307
6540
  } from "@petrarca/sonnet-ui";
6308
6541
  import {
6309
6542
  FormSelect as FormSelect2,
@@ -6311,7 +6544,7 @@ import {
6311
6544
  JsonSchemaFormRenderer as JsonSchemaFormRenderer2
6312
6545
  } from "@petrarca/sonnet-forms";
6313
6546
  import { errorLog as errorLog4 } from "@petrarca/sonnet-core";
6314
- import { Fragment as Fragment7, jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
6547
+ import { Fragment as Fragment7, jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
6315
6548
  var EMPTY_RESOLVED = {
6316
6549
  schema: null,
6317
6550
  uiSchema: {},
@@ -6335,10 +6568,10 @@ function resolveBaseSchema(provided, node) {
6335
6568
  return { type: "object", properties: {} };
6336
6569
  }
6337
6570
  function useNodeSchema(schemaProvider, label, node) {
6338
- const [resolved, setResolved] = useState14(EMPTY_RESOLVED);
6339
- const nodeRef = useRef7(node);
6571
+ const [resolved, setResolved] = useState15(EMPTY_RESOLVED);
6572
+ const nodeRef = useRef8(node);
6340
6573
  nodeRef.current = node;
6341
- useEffect6(() => {
6574
+ useEffect7(() => {
6342
6575
  let cancelled = false;
6343
6576
  if (!label) {
6344
6577
  setResolved(EMPTY_RESOLVED);
@@ -6376,11 +6609,11 @@ function useNodeSchema(schemaProvider, label, node) {
6376
6609
  return resolved;
6377
6610
  }
6378
6611
  function useExistingFormData(port, node, relationTypes) {
6379
- const [data, setData] = useState14(null);
6612
+ const [data, setData] = useState15(null);
6380
6613
  const nodeId2 = node?.id_;
6381
- const propertiesRef = useRef7(node?.properties_);
6614
+ const propertiesRef = useRef8(node?.properties_);
6382
6615
  propertiesRef.current = node?.properties_;
6383
- useEffect6(() => {
6616
+ useEffect7(() => {
6384
6617
  let cancelled = false;
6385
6618
  if (nodeId2 == null) {
6386
6619
  setData(null);
@@ -6422,12 +6655,12 @@ function LabelField({
6422
6655
  onSelect
6423
6656
  }) {
6424
6657
  if (isEdit) {
6425
- return /* @__PURE__ */ jsxs20("div", { children: [
6426
- /* @__PURE__ */ jsx22("span", { className: "heading-meta block", children: "Label" }),
6427
- /* @__PURE__ */ jsx22("span", { className: "text-sm text-muted-foreground", children: selectedLabel })
6658
+ return /* @__PURE__ */ jsxs22("div", { children: [
6659
+ /* @__PURE__ */ jsx24("span", { className: "heading-meta block", children: "Label" }),
6660
+ /* @__PURE__ */ jsx24("span", { className: "text-sm text-muted-foreground", children: selectedLabel })
6428
6661
  ] });
6429
6662
  }
6430
- return /* @__PURE__ */ jsx22(
6663
+ return /* @__PURE__ */ jsx24(
6431
6664
  FormSelect2,
6432
6665
  {
6433
6666
  label: "Label",
@@ -6439,8 +6672,8 @@ function LabelField({
6439
6672
  );
6440
6673
  }
6441
6674
  function useCreateModeLabels(schemaProvider, open, isEdit) {
6442
- const [labels, setLabels] = useState14([]);
6443
- useEffect6(() => {
6675
+ const [labels, setLabels] = useState15([]);
6676
+ useEffect7(() => {
6444
6677
  let cancelled = false;
6445
6678
  if (!open || isEdit) return;
6446
6679
  void schemaProvider.listNodeLabels().then((ls) => {
@@ -6462,7 +6695,7 @@ function NodeForm({
6462
6695
  onSave,
6463
6696
  onCancel
6464
6697
  }) {
6465
- return /* @__PURE__ */ jsx22(
6698
+ return /* @__PURE__ */ jsx24(
6466
6699
  JsonSchemaFormRenderer2,
6467
6700
  {
6468
6701
  schema,
@@ -6496,13 +6729,13 @@ function NodeFormSheetBody({
6496
6729
  onSave,
6497
6730
  onCancel
6498
6731
  }) {
6499
- return /* @__PURE__ */ jsxs20(Fragment7, { children: [
6500
- /* @__PURE__ */ jsxs20(SheetHeader4, { children: [
6501
- /* @__PURE__ */ jsx22(SheetTitle4, { children: isEdit ? "Edit node" : "Add node" }),
6502
- /* @__PURE__ */ jsx22(SheetDescription4, { children: isEdit ? "Edit this node's properties and relations." : "Create a node and (optionally) its relations." })
6732
+ return /* @__PURE__ */ jsxs22(Fragment7, { children: [
6733
+ /* @__PURE__ */ jsxs22(SheetHeader4, { children: [
6734
+ /* @__PURE__ */ jsx24(SheetTitle4, { children: isEdit ? "Edit node" : "Add node" }),
6735
+ /* @__PURE__ */ jsx24(SheetDescription4, { children: isEdit ? "Edit this node's properties and relations." : "Create a node and (optionally) its relations." })
6503
6736
  ] }),
6504
- /* @__PURE__ */ jsxs20(SheetBody4, { className: "flex flex-col gap-4", children: [
6505
- /* @__PURE__ */ jsx22(
6737
+ /* @__PURE__ */ jsxs22(SheetBody4, { className: "flex flex-col gap-4", children: [
6738
+ /* @__PURE__ */ jsx24(
6506
6739
  LabelField,
6507
6740
  {
6508
6741
  isEdit,
@@ -6511,7 +6744,7 @@ function NodeFormSheetBody({
6511
6744
  onSelect: onSelectLabel
6512
6745
  }
6513
6746
  ),
6514
- schema && /* @__PURE__ */ jsx22(
6747
+ schema && /* @__PURE__ */ jsx24(
6515
6748
  NodeForm,
6516
6749
  {
6517
6750
  formKey,
@@ -6524,7 +6757,7 @@ function NodeFormSheetBody({
6524
6757
  onCancel
6525
6758
  }
6526
6759
  ),
6527
- !isEdit && /* @__PURE__ */ jsx22(
6760
+ !isEdit && /* @__PURE__ */ jsx24(
6528
6761
  FormCheckbox,
6529
6762
  {
6530
6763
  label: "Create another node",
@@ -6532,7 +6765,7 @@ function NodeFormSheetBody({
6532
6765
  onChange: (c) => onKeepOpenChange(c === true)
6533
6766
  }
6534
6767
  ),
6535
- error && /* @__PURE__ */ jsx22(Alert, { variant: "destructive", children: error })
6768
+ error && /* @__PURE__ */ jsx24(Alert2, { variant: "destructive", children: error })
6536
6769
  ] })
6537
6770
  ] });
6538
6771
  }
@@ -6546,14 +6779,14 @@ function NodeFormSheet({
6546
6779
  onSaved
6547
6780
  }) {
6548
6781
  const isEdit = !!node?.id_;
6549
- const [selectedLabel, setSelectedLabel] = useState14(
6782
+ const [selectedLabel, setSelectedLabel] = useState15(
6550
6783
  node?.label_ ?? null
6551
6784
  );
6552
- const [error, setError] = useState14("");
6553
- const [keepOpen, setKeepOpen] = useState14(false);
6554
- const [formNonce, setFormNonce] = useState14(0);
6785
+ const [error, setError] = useState15("");
6786
+ const [keepOpen, setKeepOpen] = useState15(false);
6787
+ const [formNonce, setFormNonce] = useState15(0);
6555
6788
  const labels = useCreateModeLabels(schemaProvider, open, isEdit);
6556
- useEffect6(() => {
6789
+ useEffect7(() => {
6557
6790
  if (open) {
6558
6791
  setSelectedLabel(node?.label_ ?? null);
6559
6792
  setError("");
@@ -6570,7 +6803,7 @@ function NodeFormSheet({
6570
6803
  const formData = useMemo9(() => existingData ?? {}, [existingData]);
6571
6804
  const stableOnSaved = useStableRef(onSaved);
6572
6805
  const stableOnOpenChange = useStableRef(onOpenChange);
6573
- const handleSave = useCallback8(
6806
+ const handleSave = useCallback9(
6574
6807
  (data) => {
6575
6808
  if (!selectedLabel) return;
6576
6809
  void (async () => {
@@ -6603,7 +6836,7 @@ function NodeFormSheet({
6603
6836
  stableOnOpenChange
6604
6837
  ]
6605
6838
  );
6606
- return /* @__PURE__ */ jsx22(Sheet4, { open, onOpenChange, children: /* @__PURE__ */ jsx22(SheetContent4, { children: /* @__PURE__ */ jsx22(
6839
+ return /* @__PURE__ */ jsx24(Sheet4, { open, onOpenChange, children: /* @__PURE__ */ jsx24(SheetContent4, { children: /* @__PURE__ */ jsx24(
6607
6840
  NodeFormSheetBody,
6608
6841
  {
6609
6842
  isEdit,
@@ -6660,7 +6893,7 @@ async function saveNode({
6660
6893
  }
6661
6894
 
6662
6895
  // src/graph-edit/EdgeFormSheet.tsx
6663
- import { useCallback as useCallback9, useEffect as useEffect7, useMemo as useMemo10, useState as useState15 } from "react";
6896
+ import { useCallback as useCallback10, useEffect as useEffect8, useMemo as useMemo10, useState as useState16 } from "react";
6664
6897
  import {
6665
6898
  Sheet as Sheet5,
6666
6899
  SheetContent as SheetContent5,
@@ -6674,20 +6907,20 @@ import {
6674
6907
  DialogTitle,
6675
6908
  DialogDescription,
6676
6909
  DialogFooter,
6677
- Button as Button7,
6910
+ Button as Button9,
6678
6911
  IconButton as IconButton3,
6679
- Alert as Alert2
6912
+ Alert as Alert3
6680
6913
  } from "@petrarca/sonnet-ui";
6681
6914
  import { Pencil as Pencil2, Plus as Plus2, Trash2 as Trash22, ArrowRight as ArrowRight2 } from "lucide-react";
6682
6915
  import {
6683
6916
  JsonSchemaFormRenderer as JsonSchemaFormRenderer3
6684
6917
  } from "@petrarca/sonnet-forms";
6685
6918
  import { errorLog as errorLog5 } from "@petrarca/sonnet-core";
6686
- import { jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
6919
+ import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
6687
6920
  function usePairState(port, schemaProvider, open, nodeAId, nodeALabel, nodeBId, nodeBLabel, reloadKey) {
6688
- const [active, setActive] = useState15([]);
6689
- const [available, setAvailable] = useState15([]);
6690
- useEffect7(() => {
6921
+ const [active, setActive] = useState16([]);
6922
+ const [available, setAvailable] = useState16([]);
6923
+ useEffect8(() => {
6691
6924
  let cancelled = false;
6692
6925
  if (!open || nodeAId == null || nodeBId == null) {
6693
6926
  setActive([]);
@@ -6752,9 +6985,9 @@ var EMPTY_EDGE_SCHEMA = {
6752
6985
  properties: {}
6753
6986
  };
6754
6987
  function useEdgeSchema(schemaProvider, draft) {
6755
- const [schema, setSchema] = useState15(null);
6988
+ const [schema, setSchema] = useState16(null);
6756
6989
  const label = useMemo10(() => draftLabel(draft), [draft]);
6757
- useEffect7(() => {
6990
+ useEffect8(() => {
6758
6991
  let cancelled = false;
6759
6992
  if (!label) {
6760
6993
  setSchema(null);
@@ -6774,12 +7007,12 @@ function useEdgeSchema(schemaProvider, draft) {
6774
7007
  return schema;
6775
7008
  }
6776
7009
  function useEditableEdgeTypes(schemaProvider, active) {
6777
- const [editable, setEditable] = useState15({});
7010
+ const [editable, setEditable] = useState16({});
6778
7011
  const labelsKey = useMemo10(
6779
7012
  () => Array.from(new Set(active.map((e) => e.label_))).sort().join("\0"),
6780
7013
  [active]
6781
7014
  );
6782
- useEffect7(() => {
7015
+ useEffect8(() => {
6783
7016
  let cancelled = false;
6784
7017
  const labels = labelsKey ? labelsKey.split("\0") : [];
6785
7018
  if (labels.length === 0) {
@@ -6808,7 +7041,7 @@ function useEditableEdgeTypes(schemaProvider, active) {
6808
7041
  function SectionTitle2({
6809
7042
  children
6810
7043
  }) {
6811
- return /* @__PURE__ */ jsx23("div", { className: "mb-2", children: /* @__PURE__ */ jsx23("span", { className: "heading-meta", children }) });
7044
+ return /* @__PURE__ */ jsx25("div", { className: "mb-2", children: /* @__PURE__ */ jsx25("span", { className: "heading-meta", children }) });
6812
7045
  }
6813
7046
  function EdgeRow({
6814
7047
  label,
@@ -6816,19 +7049,19 @@ function EdgeRow({
6816
7049
  onDelete,
6817
7050
  onAdd
6818
7051
  }) {
6819
- return /* @__PURE__ */ jsxs21("div", { className: "group/row flex items-center gap-2 px-3 py-2", children: [
6820
- /* @__PURE__ */ jsx23("span", { className: "flex-1 text-sm font-medium", children: label }),
6821
- /* @__PURE__ */ jsxs21("span", { className: "flex gap-1", children: [
6822
- onAdd && /* @__PURE__ */ jsx23(IconButton3, { label: "Create edge", revealOnHover: true, onClick: onAdd, children: /* @__PURE__ */ jsx23(Plus2, {}) }),
6823
- onEdit && /* @__PURE__ */ jsx23(IconButton3, { label: "Edit edge", revealOnHover: true, onClick: onEdit, children: /* @__PURE__ */ jsx23(Pencil2, {}) }),
6824
- onDelete && /* @__PURE__ */ jsx23(
7052
+ return /* @__PURE__ */ jsxs23("div", { className: "group/row flex items-center gap-2 px-3 py-2", children: [
7053
+ /* @__PURE__ */ jsx25("span", { className: "flex-1 text-sm font-medium", children: label }),
7054
+ /* @__PURE__ */ jsxs23("span", { className: "flex gap-1", children: [
7055
+ onAdd && /* @__PURE__ */ jsx25(IconButton3, { label: "Create edge", revealOnHover: true, onClick: onAdd, children: /* @__PURE__ */ jsx25(Plus2, {}) }),
7056
+ onEdit && /* @__PURE__ */ jsx25(IconButton3, { label: "Edit edge", revealOnHover: true, onClick: onEdit, children: /* @__PURE__ */ jsx25(Pencil2, {}) }),
7057
+ onDelete && /* @__PURE__ */ jsx25(
6825
7058
  IconButton3,
6826
7059
  {
6827
7060
  label: "Delete edge",
6828
7061
  tone: "destructive",
6829
7062
  revealOnHover: true,
6830
7063
  onClick: onDelete,
6831
- children: /* @__PURE__ */ jsx23(Trash22, {})
7064
+ children: /* @__PURE__ */ jsx25(Trash22, {})
6832
7065
  }
6833
7066
  )
6834
7067
  ] })
@@ -6856,17 +7089,17 @@ function PairLists({
6856
7089
  const to = entry.sourceId === nodeAId ? nodeBLabel : nodeALabel;
6857
7090
  return `${entry.label} (${from} -> ${to})`;
6858
7091
  };
6859
- return /* @__PURE__ */ jsxs21("div", { className: "flex flex-col gap-4", children: [
6860
- empty && /* @__PURE__ */ jsxs21("p", { className: "text-xs text-muted-foreground", children: [
7092
+ return /* @__PURE__ */ jsxs23("div", { className: "flex flex-col gap-4", children: [
7093
+ empty && /* @__PURE__ */ jsxs23("p", { className: "text-xs text-muted-foreground", children: [
6861
7094
  "No edges are defined between ",
6862
7095
  nodeALabel,
6863
7096
  " and ",
6864
7097
  nodeBLabel,
6865
7098
  "."
6866
7099
  ] }),
6867
- active.length > 0 && /* @__PURE__ */ jsxs21("div", { children: [
6868
- /* @__PURE__ */ jsx23(SectionTitle2, { children: "Active edges" }),
6869
- /* @__PURE__ */ jsx23("div", { className: "rounded-lg border divide-y", children: active.map((edge) => /* @__PURE__ */ jsx23(
7100
+ active.length > 0 && /* @__PURE__ */ jsxs23("div", { children: [
7101
+ /* @__PURE__ */ jsx25(SectionTitle2, { children: "Active edges" }),
7102
+ /* @__PURE__ */ jsx25("div", { className: "rounded-lg border divide-y", children: active.map((edge) => /* @__PURE__ */ jsx25(
6870
7103
  EdgeRow,
6871
7104
  {
6872
7105
  label: edge.label_,
@@ -6876,9 +7109,9 @@ function PairLists({
6876
7109
  edge.id_
6877
7110
  )) })
6878
7111
  ] }),
6879
- available.length > 0 && /* @__PURE__ */ jsxs21("div", { children: [
6880
- /* @__PURE__ */ jsx23(SectionTitle2, { children: "Available edges" }),
6881
- /* @__PURE__ */ jsx23("div", { className: "rounded-lg border divide-y", children: available.map((entry) => /* @__PURE__ */ jsx23(
7112
+ available.length > 0 && /* @__PURE__ */ jsxs23("div", { children: [
7113
+ /* @__PURE__ */ jsx25(SectionTitle2, { children: "Available edges" }),
7114
+ /* @__PURE__ */ jsx25("div", { className: "rounded-lg border divide-y", children: available.map((entry) => /* @__PURE__ */ jsx25(
6882
7115
  EdgeRow,
6883
7116
  {
6884
7117
  label: rowLabel(entry),
@@ -6898,13 +7131,13 @@ function DraftForm({
6898
7131
  }) {
6899
7132
  const label = draft.mode === "create" ? draft.entry.label : draft.edge.label_;
6900
7133
  const data = draft.mode === "edit" ? draft.edge.properties_ ?? {} : {};
6901
- return /* @__PURE__ */ jsxs21("div", { children: [
6902
- /* @__PURE__ */ jsxs21(SectionTitle2, { children: [
7134
+ return /* @__PURE__ */ jsxs23("div", { children: [
7135
+ /* @__PURE__ */ jsxs23(SectionTitle2, { children: [
6903
7136
  draft.mode === "create" ? "Add" : "Edit",
6904
7137
  " ",
6905
7138
  label
6906
7139
  ] }),
6907
- /* @__PURE__ */ jsx23(
7140
+ /* @__PURE__ */ jsx25(
6908
7141
  JsonSchemaFormRenderer3,
6909
7142
  {
6910
7143
  schema,
@@ -6932,10 +7165,10 @@ function EdgeFormSheet({
6932
7165
  widgets,
6933
7166
  onChanged
6934
7167
  }) {
6935
- const [reloadKey, setReloadKey] = useState15(0);
6936
- const [draft, setDraft] = useState15(null);
6937
- const [error, setError] = useState15("");
6938
- const [pendingDelete, setPendingDelete] = useState15(null);
7168
+ const [reloadKey, setReloadKey] = useState16(0);
7169
+ const [draft, setDraft] = useState16(null);
7170
+ const [error, setError] = useState16("");
7171
+ const [pendingDelete, setPendingDelete] = useState16(null);
6939
7172
  const { active, available } = usePairState(
6940
7173
  port,
6941
7174
  schemaProvider,
@@ -6950,18 +7183,18 @@ function EdgeFormSheet({
6950
7183
  const editableTypes = useEditableEdgeTypes(schemaProvider, active);
6951
7184
  const nodeAId = nodeA.id_;
6952
7185
  const nodeBId = nodeB.id_;
6953
- useEffect7(() => {
7186
+ useEffect8(() => {
6954
7187
  if (open) {
6955
7188
  setDraft(null);
6956
7189
  setError("");
6957
7190
  setPendingDelete(null);
6958
7191
  }
6959
7192
  }, [open, nodeAId, nodeBId]);
6960
- const reload = useCallback9(() => {
7193
+ const reload = useCallback10(() => {
6961
7194
  setReloadKey((k) => k + 1);
6962
7195
  onChanged?.();
6963
7196
  }, [onChanged]);
6964
- const handleSave = useCallback9(
7197
+ const handleSave = useCallback10(
6965
7198
  (formData) => {
6966
7199
  if (!draft) return;
6967
7200
  const { properties } = extractRelationFields(formData);
@@ -6993,7 +7226,7 @@ function EdgeFormSheet({
6993
7226
  },
6994
7227
  [draft, port, reload]
6995
7228
  );
6996
- const confirmDelete = useCallback9(async () => {
7229
+ const confirmDelete = useCallback10(async () => {
6997
7230
  const edge = pendingDelete;
6998
7231
  if (!edge || edge.id_ == null) return;
6999
7232
  try {
@@ -7006,19 +7239,19 @@ function EdgeFormSheet({
7006
7239
  setError(e instanceof Error ? e.message : "Failed to delete edge");
7007
7240
  }
7008
7241
  }, [pendingDelete, port, reload]);
7009
- return /* @__PURE__ */ jsxs21(Sheet5, { open, onOpenChange, children: [
7010
- /* @__PURE__ */ jsxs21(SheetContent5, { children: [
7011
- /* @__PURE__ */ jsxs21(SheetHeader5, { children: [
7012
- /* @__PURE__ */ jsx23(SheetTitle5, { children: "Edges" }),
7013
- /* @__PURE__ */ jsx23(SheetDescription5, { children: "Manage the edges between the two selected nodes." })
7242
+ return /* @__PURE__ */ jsxs23(Sheet5, { open, onOpenChange, children: [
7243
+ /* @__PURE__ */ jsxs23(SheetContent5, { children: [
7244
+ /* @__PURE__ */ jsxs23(SheetHeader5, { children: [
7245
+ /* @__PURE__ */ jsx25(SheetTitle5, { children: "Edges" }),
7246
+ /* @__PURE__ */ jsx25(SheetDescription5, { children: "Manage the edges between the two selected nodes." })
7014
7247
  ] }),
7015
- /* @__PURE__ */ jsxs21(SheetBody5, { className: "flex flex-col gap-4", children: [
7016
- /* @__PURE__ */ jsxs21("div", { className: "flex items-center gap-2 text-sm", children: [
7017
- /* @__PURE__ */ jsx23("span", { className: "font-medium", children: extractDisplayLabel(nodeA) }),
7018
- /* @__PURE__ */ jsx23(ArrowRight2, { className: "h-4 w-4 text-muted-foreground" }),
7019
- /* @__PURE__ */ jsx23("span", { className: "font-medium", children: extractDisplayLabel(nodeB) })
7248
+ /* @__PURE__ */ jsxs23(SheetBody5, { className: "flex flex-col gap-4", children: [
7249
+ /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2 text-sm", children: [
7250
+ /* @__PURE__ */ jsx25("span", { className: "font-medium", children: extractDisplayLabel(nodeA) }),
7251
+ /* @__PURE__ */ jsx25(ArrowRight2, { className: "h-4 w-4 text-muted-foreground" }),
7252
+ /* @__PURE__ */ jsx25("span", { className: "font-medium", children: extractDisplayLabel(nodeB) })
7020
7253
  ] }),
7021
- !draft && /* @__PURE__ */ jsx23(
7254
+ !draft && /* @__PURE__ */ jsx25(
7022
7255
  PairLists,
7023
7256
  {
7024
7257
  nodeALabel: nodeA.label_,
@@ -7032,7 +7265,7 @@ function EdgeFormSheet({
7032
7265
  onCreate: (entry) => setDraft({ mode: "create", entry })
7033
7266
  }
7034
7267
  ),
7035
- draft && schema && /* @__PURE__ */ jsx23(
7268
+ draft && schema && /* @__PURE__ */ jsx25(
7036
7269
  DraftForm,
7037
7270
  {
7038
7271
  draft,
@@ -7042,10 +7275,10 @@ function EdgeFormSheet({
7042
7275
  onCancel: () => setDraft(null)
7043
7276
  }
7044
7277
  ),
7045
- error && /* @__PURE__ */ jsx23(Alert2, { variant: "destructive", children: error })
7278
+ error && /* @__PURE__ */ jsx25(Alert3, { variant: "destructive", children: error })
7046
7279
  ] })
7047
7280
  ] }),
7048
- /* @__PURE__ */ jsx23(
7281
+ /* @__PURE__ */ jsx25(
7049
7282
  DeleteEdgeConfirm,
7050
7283
  {
7051
7284
  edge: pendingDelete,
@@ -7060,24 +7293,24 @@ function DeleteEdgeConfirm({
7060
7293
  onCancel,
7061
7294
  onConfirm
7062
7295
  }) {
7063
- return /* @__PURE__ */ jsx23(Dialog, { open: !!edge, onOpenChange: (o) => !o && onCancel(), children: /* @__PURE__ */ jsxs21(DialogContent, { children: [
7064
- /* @__PURE__ */ jsxs21(DialogHeader, { children: [
7065
- /* @__PURE__ */ jsx23(DialogTitle, { children: "Delete edge" }),
7066
- /* @__PURE__ */ jsxs21(DialogDescription, { children: [
7296
+ return /* @__PURE__ */ jsx25(Dialog, { open: !!edge, onOpenChange: (o) => !o && onCancel(), children: /* @__PURE__ */ jsxs23(DialogContent, { children: [
7297
+ /* @__PURE__ */ jsxs23(DialogHeader, { children: [
7298
+ /* @__PURE__ */ jsx25(DialogTitle, { children: "Delete edge" }),
7299
+ /* @__PURE__ */ jsxs23(DialogDescription, { children: [
7067
7300
  "Delete the edge ",
7068
- /* @__PURE__ */ jsx23("strong", { children: edge?.label_ }),
7301
+ /* @__PURE__ */ jsx25("strong", { children: edge?.label_ }),
7069
7302
  "? This removes the relationship between the two nodes."
7070
7303
  ] })
7071
7304
  ] }),
7072
- /* @__PURE__ */ jsxs21(DialogFooter, { children: [
7073
- /* @__PURE__ */ jsx23(Button7, { variant: "outline", onClick: onCancel, children: "Cancel" }),
7074
- /* @__PURE__ */ jsx23(Button7, { variant: "destructive", onClick: onConfirm, children: "Delete" })
7305
+ /* @__PURE__ */ jsxs23(DialogFooter, { children: [
7306
+ /* @__PURE__ */ jsx25(Button9, { variant: "outline", onClick: onCancel, children: "Cancel" }),
7307
+ /* @__PURE__ */ jsx25(Button9, { variant: "destructive", onClick: onConfirm, children: "Delete" })
7075
7308
  ] })
7076
7309
  ] }) });
7077
7310
  }
7078
7311
 
7079
7312
  // src/graph-edit/DeleteNodeDialog.tsx
7080
- import { useCallback as useCallback10, useEffect as useEffect8, useState as useState16 } from "react";
7313
+ import { useCallback as useCallback11, useEffect as useEffect9, useState as useState17 } from "react";
7081
7314
  import {
7082
7315
  Dialog as Dialog2,
7083
7316
  DialogContent as DialogContent2,
@@ -7085,19 +7318,19 @@ import {
7085
7318
  DialogTitle as DialogTitle2,
7086
7319
  DialogDescription as DialogDescription2,
7087
7320
  DialogFooter as DialogFooter2,
7088
- Button as Button8,
7089
- Alert as Alert3
7321
+ Button as Button10,
7322
+ Alert as Alert4
7090
7323
  } from "@petrarca/sonnet-ui";
7091
7324
  import { errorLog as errorLog6 } from "@petrarca/sonnet-core";
7092
- import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
7325
+ import { jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
7093
7326
  var EMPTY_DEPENDENT = {
7094
7327
  inbound: [],
7095
7328
  outbound: [],
7096
7329
  names: {}
7097
7330
  };
7098
7331
  function useDependentEdges(port, open, node) {
7099
- const [dependent, setDependent] = useState16(EMPTY_DEPENDENT);
7100
- useEffect8(() => {
7332
+ const [dependent, setDependent] = useState17(EMPTY_DEPENDENT);
7333
+ useEffect9(() => {
7101
7334
  if (!open) return;
7102
7335
  if (node.id_ == null) {
7103
7336
  setDependent(EMPTY_DEPENDENT);
@@ -7147,12 +7380,12 @@ function EdgeList({
7147
7380
  direction
7148
7381
  }) {
7149
7382
  if (edges.length === 0) return null;
7150
- return /* @__PURE__ */ jsxs22("div", { className: "mb-2", children: [
7151
- /* @__PURE__ */ jsx24("span", { className: "heading-meta block", children: title }),
7152
- /* @__PURE__ */ jsx24("ul", { className: "list-disc pl-6 text-sm", children: edges.map((edge) => {
7383
+ return /* @__PURE__ */ jsxs24("div", { className: "mb-2", children: [
7384
+ /* @__PURE__ */ jsx26("span", { className: "heading-meta block", children: title }),
7385
+ /* @__PURE__ */ jsx26("ul", { className: "list-disc pl-6 text-sm", children: edges.map((edge) => {
7153
7386
  const neighbour = direction === "inbound" ? names[edge.start_id_] : names[edge.end_id_];
7154
7387
  const text = direction === "inbound" ? `${neighbour ?? "?"} \u2192 ${edge.label_}` : `${edge.label_} \u2192 ${neighbour ?? "?"}`;
7155
- return /* @__PURE__ */ jsx24("li", { children: neighbour ? text : edge.label_ }, edge.id_);
7388
+ return /* @__PURE__ */ jsx26("li", { children: neighbour ? text : edge.label_ }, edge.id_);
7156
7389
  }) })
7157
7390
  ] });
7158
7391
  }
@@ -7164,9 +7397,9 @@ function DeleteNodeDialog({
7164
7397
  onDeleted
7165
7398
  }) {
7166
7399
  const { inbound, outbound, names } = useDependentEdges(port, open, node);
7167
- const [error, setError] = useState16("");
7400
+ const [error, setError] = useState17("");
7168
7401
  const hasEdges = inbound.length > 0 || outbound.length > 0;
7169
- const handleDelete = useCallback10(() => {
7402
+ const handleDelete = useCallback11(() => {
7170
7403
  if (node.id_ == null) return;
7171
7404
  void (async () => {
7172
7405
  try {
@@ -7179,17 +7412,17 @@ function DeleteNodeDialog({
7179
7412
  }
7180
7413
  })();
7181
7414
  }, [port, node.id_, onDeleted, onOpenChange]);
7182
- return /* @__PURE__ */ jsx24(Dialog2, { open, onOpenChange, children: /* @__PURE__ */ jsxs22(DialogContent2, { children: [
7183
- /* @__PURE__ */ jsxs22(DialogHeader2, { children: [
7184
- /* @__PURE__ */ jsx24(DialogTitle2, { children: "Delete node" }),
7185
- /* @__PURE__ */ jsxs22(DialogDescription2, { children: [
7415
+ return /* @__PURE__ */ jsx26(Dialog2, { open, onOpenChange, children: /* @__PURE__ */ jsxs24(DialogContent2, { children: [
7416
+ /* @__PURE__ */ jsxs24(DialogHeader2, { children: [
7417
+ /* @__PURE__ */ jsx26(DialogTitle2, { children: "Delete node" }),
7418
+ /* @__PURE__ */ jsxs24(DialogDescription2, { children: [
7186
7419
  "Delete the node ",
7187
- /* @__PURE__ */ jsx24("strong", { children: extractDisplayLabel(node) }),
7420
+ /* @__PURE__ */ jsx26("strong", { children: extractDisplayLabel(node) }),
7188
7421
  "?"
7189
7422
  ] })
7190
7423
  ] }),
7191
- !hasEdges && /* @__PURE__ */ jsx24("p", { className: "text-sm text-muted-foreground", children: "No connected edges." }),
7192
- /* @__PURE__ */ jsx24(
7424
+ !hasEdges && /* @__PURE__ */ jsx26("p", { className: "text-sm text-muted-foreground", children: "No connected edges." }),
7425
+ /* @__PURE__ */ jsx26(
7193
7426
  EdgeList,
7194
7427
  {
7195
7428
  title: "Inbound edges",
@@ -7198,7 +7431,7 @@ function DeleteNodeDialog({
7198
7431
  direction: "inbound"
7199
7432
  }
7200
7433
  ),
7201
- /* @__PURE__ */ jsx24(
7434
+ /* @__PURE__ */ jsx26(
7202
7435
  EdgeList,
7203
7436
  {
7204
7437
  title: "Outbound edges",
@@ -7207,16 +7440,16 @@ function DeleteNodeDialog({
7207
7440
  direction: "outbound"
7208
7441
  }
7209
7442
  ),
7210
- error && /* @__PURE__ */ jsx24(Alert3, { variant: "destructive", children: error }),
7211
- /* @__PURE__ */ jsxs22(DialogFooter2, { children: [
7212
- /* @__PURE__ */ jsx24(Button8, { variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
7213
- /* @__PURE__ */ jsx24(Button8, { variant: "destructive", onClick: handleDelete, children: "Delete" })
7443
+ error && /* @__PURE__ */ jsx26(Alert4, { variant: "destructive", children: error }),
7444
+ /* @__PURE__ */ jsxs24(DialogFooter2, { children: [
7445
+ /* @__PURE__ */ jsx26(Button10, { variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
7446
+ /* @__PURE__ */ jsx26(Button10, { variant: "destructive", onClick: handleDelete, children: "Delete" })
7214
7447
  ] })
7215
7448
  ] }) });
7216
7449
  }
7217
7450
 
7218
7451
  // src/graph-edit/GraphEditToolbar.tsx
7219
- import { Fragment as Fragment8, jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
7452
+ import { Fragment as Fragment8, jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
7220
7453
  function toPerms(caps) {
7221
7454
  const canCreateNode = caps.createNode === "edit";
7222
7455
  const canUpdateNode = caps.updateNode === "edit";
@@ -7235,38 +7468,38 @@ function ToolbarButtons({
7235
7468
  count,
7236
7469
  onOpen
7237
7470
  }) {
7238
- return /* @__PURE__ */ jsxs23("div", { className: "flex gap-0.5", children: [
7239
- perms.canCreateNode && /* @__PURE__ */ jsx25(
7471
+ return /* @__PURE__ */ jsxs25("div", { className: "flex gap-0.5", children: [
7472
+ perms.canCreateNode && /* @__PURE__ */ jsx27(
7240
7473
  IconButton4,
7241
7474
  {
7242
7475
  label: "Add node",
7243
7476
  variant: "outline",
7244
7477
  disabled: count > 0,
7245
7478
  onClick: () => onOpen("node-create"),
7246
- children: /* @__PURE__ */ jsx25(Plus3, {})
7479
+ children: /* @__PURE__ */ jsx27(Plus3, {})
7247
7480
  }
7248
7481
  ),
7249
- perms.canUpdateNode && /* @__PURE__ */ jsx25(
7482
+ perms.canUpdateNode && /* @__PURE__ */ jsx27(
7250
7483
  IconButton4,
7251
7484
  {
7252
7485
  label: "Edit node",
7253
7486
  variant: "outline",
7254
7487
  disabled: count !== 1,
7255
7488
  onClick: () => onOpen("node-edit"),
7256
- children: /* @__PURE__ */ jsx25(Edit, {})
7489
+ children: /* @__PURE__ */ jsx27(Edit, {})
7257
7490
  }
7258
7491
  ),
7259
- perms.canEditEdges && /* @__PURE__ */ jsx25(
7492
+ perms.canEditEdges && /* @__PURE__ */ jsx27(
7260
7493
  IconButton4,
7261
7494
  {
7262
7495
  label: "Edges",
7263
7496
  variant: "outline",
7264
7497
  disabled: count !== 2,
7265
7498
  onClick: () => onOpen("edge"),
7266
- children: /* @__PURE__ */ jsx25(Link, {})
7499
+ children: /* @__PURE__ */ jsx27(Link, {})
7267
7500
  }
7268
7501
  ),
7269
- perms.canDeleteNode && /* @__PURE__ */ jsx25(
7502
+ perms.canDeleteNode && /* @__PURE__ */ jsx27(
7270
7503
  IconButton4,
7271
7504
  {
7272
7505
  label: "Delete node",
@@ -7274,7 +7507,7 @@ function ToolbarButtons({
7274
7507
  tone: "destructive",
7275
7508
  disabled: count !== 1,
7276
7509
  onClick: () => onOpen("delete"),
7277
- children: /* @__PURE__ */ jsx25(Trash23, {})
7510
+ children: /* @__PURE__ */ jsx27(Trash23, {})
7278
7511
  }
7279
7512
  )
7280
7513
  ] });
@@ -7290,14 +7523,14 @@ function ToolbarDialogs({
7290
7523
  onClose,
7291
7524
  onMutated
7292
7525
  }) {
7293
- const onChange = useCallback11(
7526
+ const onChange = useCallback12(
7294
7527
  (o) => {
7295
7528
  if (!o) onClose();
7296
7529
  },
7297
7530
  [onClose]
7298
7531
  );
7299
- return /* @__PURE__ */ jsxs23(Fragment8, { children: [
7300
- (perms.canCreateNode || perms.canUpdateNode) && /* @__PURE__ */ jsx25(
7532
+ return /* @__PURE__ */ jsxs25(Fragment8, { children: [
7533
+ (perms.canCreateNode || perms.canUpdateNode) && /* @__PURE__ */ jsx27(
7301
7534
  NodeFormSheet,
7302
7535
  {
7303
7536
  open: dialog === "node-create" || dialog === "node-edit",
@@ -7309,7 +7542,7 @@ function ToolbarDialogs({
7309
7542
  onSaved: onMutated
7310
7543
  }
7311
7544
  ),
7312
- perms.canEditEdges && firstNode && secondNode && /* @__PURE__ */ jsx25(
7545
+ perms.canEditEdges && firstNode && secondNode && /* @__PURE__ */ jsx27(
7313
7546
  EdgeFormSheet,
7314
7547
  {
7315
7548
  open: dialog === "edge",
@@ -7322,7 +7555,7 @@ function ToolbarDialogs({
7322
7555
  onChanged: onMutated
7323
7556
  }
7324
7557
  ),
7325
- perms.canDeleteNode && firstNode && /* @__PURE__ */ jsx25(
7558
+ perms.canDeleteNode && firstNode && /* @__PURE__ */ jsx27(
7326
7559
  DeleteNodeDialog,
7327
7560
  {
7328
7561
  open: dialog === "delete",
@@ -7347,7 +7580,7 @@ function GraphEditToolbar({
7347
7580
  (s) => s.selectedNodeIds
7348
7581
  );
7349
7582
  const live = useLiveGraphData();
7350
- const [dialog, setDialog] = useState17(null);
7583
+ const [dialog, setDialog] = useState18(null);
7351
7584
  const perms = useMemo11(
7352
7585
  () => toPerms(resolveGraphCapabilities(capabilities, readOnly)),
7353
7586
  [capabilities, readOnly]
@@ -7358,8 +7591,8 @@ function GraphEditToolbar({
7358
7591
  );
7359
7592
  const stableOnMutated = useStableRef(onMutated);
7360
7593
  if (!perms.anyEditable) return null;
7361
- return /* @__PURE__ */ jsxs23(Fragment8, { children: [
7362
- /* @__PURE__ */ jsx25(
7594
+ return /* @__PURE__ */ jsxs25(Fragment8, { children: [
7595
+ /* @__PURE__ */ jsx27(
7363
7596
  ToolbarButtons,
7364
7597
  {
7365
7598
  perms,
@@ -7367,7 +7600,7 @@ function GraphEditToolbar({
7367
7600
  onOpen: setDialog
7368
7601
  }
7369
7602
  ),
7370
- /* @__PURE__ */ jsx25(
7603
+ /* @__PURE__ */ jsx27(
7371
7604
  ToolbarDialogs,
7372
7605
  {
7373
7606
  perms,
@@ -7383,6 +7616,662 @@ function GraphEditToolbar({
7383
7616
  )
7384
7617
  ] });
7385
7618
  }
7619
+
7620
+ // src/finder/GraphFinder.tsx
7621
+ import { FormSelect as FormSelect3, FormTextarea } from "@petrarca/sonnet-forms";
7622
+ import {
7623
+ Alert as Alert5,
7624
+ AlertDescription,
7625
+ AlertTitle,
7626
+ Button as Button11,
7627
+ SimpleTooltip as SimpleTooltip5
7628
+ } from "@petrarca/sonnet-ui";
7629
+ import { Search } from "lucide-react";
7630
+
7631
+ // src/finder/useFinderState.ts
7632
+ import { useState as useState20, useEffect as useEffect11 } from "react";
7633
+ import mistql from "mistql";
7634
+
7635
+ // src/finder/flattenGraphData.ts
7636
+ function flattenGraphData(normalized) {
7637
+ const flattenedNodes = normalized.nodeIds.map((id) => {
7638
+ const node = normalized.nodesById[id];
7639
+ return {
7640
+ ...node.properties_,
7641
+ id_: node.id_,
7642
+ label_: node.label_,
7643
+ displayName: node.visual.displayName,
7644
+ color: node.visual.color
7645
+ };
7646
+ });
7647
+ const flattenedEdges = normalized.edgeIds.map((id) => {
7648
+ const edge = normalized.edgesById[id];
7649
+ return {
7650
+ ...edge.properties_ || {},
7651
+ id_: edge.id_,
7652
+ label_: edge.label_,
7653
+ start_id_: edge.start_id_,
7654
+ end_id_: edge.end_id_
7655
+ };
7656
+ });
7657
+ const nodesByLabel = groupByLabel(flattenedNodes);
7658
+ const edgesByLabel = groupByLabel(flattenedEdges);
7659
+ return {
7660
+ nodes: flattenedNodes,
7661
+ edges: flattenedEdges,
7662
+ graph: [...flattenedNodes, ...flattenedEdges],
7663
+ _labels: Object.keys(nodesByLabel),
7664
+ _edgeLabels: Object.keys(edgesByLabel),
7665
+ ...nodesByLabel,
7666
+ ...edgesByLabel
7667
+ };
7668
+ }
7669
+ function groupByLabel(items) {
7670
+ const byLabel = {};
7671
+ for (const item of items) {
7672
+ (byLabel[item.label_] ??= []).push(item);
7673
+ }
7674
+ return byLabel;
7675
+ }
7676
+
7677
+ // src/finder/useGraphSearch.ts
7678
+ import { useRef as useRef9, useState as useState19, useEffect as useEffect10 } from "react";
7679
+ import { errorLog as errorLog7 } from "@petrarca/sonnet-core";
7680
+
7681
+ // src/finder/OramaSearchService.ts
7682
+ import { create as create8, insert, search } from "@orama/orama";
7683
+
7684
+ // src/finder/oramaSchemaBuilder.ts
7685
+ function buildOramaSchema() {
7686
+ return {
7687
+ id_: "string",
7688
+ type: "string",
7689
+ label_: "string",
7690
+ displayName: "string",
7691
+ searchableText: "string",
7692
+ properties_json: "string",
7693
+ isVisible: "boolean",
7694
+ start_id_: "string",
7695
+ end_id_: "string"
7696
+ };
7697
+ }
7698
+ function getDefaultFieldBoosting() {
7699
+ return {
7700
+ displayName: 2,
7701
+ searchableText: 1.5,
7702
+ properties_json: 1
7703
+ };
7704
+ }
7705
+
7706
+ // src/finder/oramaTransformers.ts
7707
+ function transformNodeToOramaDoc(node, normalized) {
7708
+ return {
7709
+ id_: String(node.id_),
7710
+ type: "node",
7711
+ label_: node.label_,
7712
+ displayName: node.visual.displayName || node.label_,
7713
+ searchableText: buildSearchableText(node),
7714
+ properties_json: JSON.stringify(node.properties_ || {}),
7715
+ isVisible: normalized ? !normalized.hiddenNodeIds.has(String(node.id_)) : true
7716
+ };
7717
+ }
7718
+ function transformEdgeToOramaDoc(edge, normalized) {
7719
+ return {
7720
+ id_: String(edge.id_),
7721
+ type: "edge",
7722
+ label_: edge.label_,
7723
+ displayName: edge.label_,
7724
+ searchableText: buildSearchableTextEdge(edge),
7725
+ properties_json: JSON.stringify(edge.properties_ || {}),
7726
+ isVisible: normalized ? !normalized.hiddenEdgeIds.has(String(edge.id_)) : true,
7727
+ start_id_: String(edge.start_id_),
7728
+ end_id_: String(edge.end_id_)
7729
+ };
7730
+ }
7731
+ function buildSearchableText(node) {
7732
+ const parts = [node.label_, node.visual.displayName || ""];
7733
+ appendPropertyValues(parts, node.properties_);
7734
+ return parts.filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
7735
+ }
7736
+ function buildSearchableTextEdge(edge) {
7737
+ const parts = [edge.label_];
7738
+ appendPropertyValues(parts, edge.properties_);
7739
+ return parts.filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
7740
+ }
7741
+ function appendPropertyValues(parts, properties) {
7742
+ if (!properties) return;
7743
+ for (const value of Object.values(properties)) {
7744
+ if (value == null) continue;
7745
+ if (Array.isArray(value)) {
7746
+ parts.push(...value.map(String));
7747
+ } else if (typeof value === "object") {
7748
+ parts.push(JSON.stringify(value));
7749
+ } else {
7750
+ parts.push(String(value));
7751
+ }
7752
+ }
7753
+ }
7754
+
7755
+ // src/finder/OramaSearchService.ts
7756
+ function buildWhereClause(options) {
7757
+ const where = {};
7758
+ const includeNodes = options.includeNodes ?? true;
7759
+ const includeEdges = options.includeEdges ?? true;
7760
+ if (includeNodes !== includeEdges) {
7761
+ where.type = includeNodes ? "node" : "edge";
7762
+ }
7763
+ const labels = options.labels;
7764
+ if (labels?.length) {
7765
+ where.label_ = labels.length === 1 ? labels[0] : { in: labels };
7766
+ }
7767
+ if (options.visibleOnly) {
7768
+ where.isVisible = true;
7769
+ }
7770
+ return where;
7771
+ }
7772
+ var OramaSearchService = class {
7773
+ db = null;
7774
+ config;
7775
+ isDirty = false;
7776
+ isInitialized = false;
7777
+ lastIndexBuild = null;
7778
+ stats = { totalIndexed: 0, nodesIndexed: 0, edgesIndexed: 0 };
7779
+ constructor(config = {}) {
7780
+ this.config = {
7781
+ debug: config.debug ?? false,
7782
+ maxNodes: config.maxNodes ?? 1e5,
7783
+ lazyInit: config.lazyInit ?? true
7784
+ };
7785
+ }
7786
+ // ---- lifecycle ----------------------------------------------------------
7787
+ /** Create the empty index with the finder schema (idempotent). */
7788
+ async initialize() {
7789
+ if (this.isInitialized) return;
7790
+ this.db = await create8({
7791
+ schema: buildOramaSchema(),
7792
+ components: {
7793
+ tokenizer: {
7794
+ stemming: true,
7795
+ stopWords: ["the", "a", "an", "and", "or", "but"]
7796
+ }
7797
+ }
7798
+ });
7799
+ this.isInitialized = true;
7800
+ this.isDirty = false;
7801
+ }
7802
+ /** Drop all documents (recreates the index; Orama has no bulk delete). */
7803
+ async clear() {
7804
+ if (!this.db) return;
7805
+ this.isInitialized = false;
7806
+ await this.initialize();
7807
+ this.stats = { totalIndexed: 0, nodesIndexed: 0, edgesIndexed: 0 };
7808
+ this.lastIndexBuild = null;
7809
+ this.isDirty = false;
7810
+ }
7811
+ /** Mark the index stale so the next search rebuilds it. */
7812
+ markDirty() {
7813
+ this.isDirty = true;
7814
+ }
7815
+ /** Whether the index needs a (re)build before searching. */
7816
+ needsRebuild() {
7817
+ return this.isDirty || !this.isInitialized;
7818
+ }
7819
+ // ---- indexing -----------------------------------------------------------
7820
+ /** Rebuild the whole index from the given nodes and edges. */
7821
+ async rebuildIndex(nodes, edges, normalized) {
7822
+ if (!this.isInitialized) await this.initialize();
7823
+ await this.clear();
7824
+ if (!this.db) throw new Error("Finder index not initialized");
7825
+ let indexedNodes = nodes;
7826
+ if (nodes.length > this.config.maxNodes) {
7827
+ if (this.config.debug) {
7828
+ console.warn(
7829
+ `[GraphFinder] node count (${nodes.length}) exceeds maxNodes (${this.config.maxNodes}); truncating`
7830
+ );
7831
+ }
7832
+ indexedNodes = nodes.slice(0, this.config.maxNodes);
7833
+ }
7834
+ for (const node of indexedNodes) {
7835
+ await insert(this.db, transformNodeToOramaDoc(node, normalized));
7836
+ }
7837
+ for (const edge of edges) {
7838
+ await insert(this.db, transformEdgeToOramaDoc(edge, normalized));
7839
+ }
7840
+ this.stats = {
7841
+ totalIndexed: indexedNodes.length + edges.length,
7842
+ nodesIndexed: indexedNodes.length,
7843
+ edgesIndexed: edges.length
7844
+ };
7845
+ this.lastIndexBuild = Date.now();
7846
+ this.isDirty = false;
7847
+ }
7848
+ // ---- search -------------------------------------------------------------
7849
+ /** Run a full-text query against the index. */
7850
+ async search(options) {
7851
+ if (!this.db || !this.isInitialized || this.isDirty) return [];
7852
+ const { term, limit = 50, boost } = options;
7853
+ const where = buildWhereClause(options);
7854
+ const results = await search(this.db, {
7855
+ term,
7856
+ where: Object.keys(where).length > 0 ? where : void 0,
7857
+ limit,
7858
+ boost: boost || getDefaultFieldBoosting(),
7859
+ tolerance: 1
7860
+ // allow a 1-character typo
7861
+ });
7862
+ return results.hits.map((hit) => ({
7863
+ document: hit.document,
7864
+ score: hit.score
7865
+ }));
7866
+ }
7867
+ // ---- status -------------------------------------------------------------
7868
+ /** Current index statistics. */
7869
+ getStats() {
7870
+ return {
7871
+ ...this.stats,
7872
+ lastIndexBuild: this.lastIndexBuild,
7873
+ isReady: this.isInitialized && !this.isDirty
7874
+ };
7875
+ }
7876
+ /** Whether the index is ready to serve searches. */
7877
+ isReady() {
7878
+ return this.isInitialized && !this.isDirty;
7879
+ }
7880
+ };
7881
+
7882
+ // src/finder/useGraphSearch.ts
7883
+ function useGraphSearch() {
7884
+ const { baseNormalized } = useLiveGraphData();
7885
+ const serviceRef = useRef9(null);
7886
+ const [isReady, setIsReady] = useState19(false);
7887
+ const [isIndexing, setIsIndexing] = useState19(false);
7888
+ const [error, setError] = useState19(null);
7889
+ const [stats, setStats] = useState19(null);
7890
+ const lastDataKeyRef = useRef9("");
7891
+ useEffect10(() => {
7892
+ const nodeCount = Object.keys(baseNormalized.nodesById).length;
7893
+ const edgeCount = Object.keys(baseNormalized.edgesById).length;
7894
+ const currentKey = `${nodeCount}-${edgeCount}`;
7895
+ if (!serviceRef.current) serviceRef.current = new OramaSearchService();
7896
+ const service = serviceRef.current;
7897
+ const changed = currentKey !== lastDataKeyRef.current;
7898
+ if (changed && lastDataKeyRef.current !== "") {
7899
+ lastDataKeyRef.current = currentKey;
7900
+ service.markDirty();
7901
+ setIsReady(false);
7902
+ setStats(service.getStats());
7903
+ } else if (lastDataKeyRef.current === "") {
7904
+ lastDataKeyRef.current = currentKey;
7905
+ }
7906
+ }, [baseNormalized]);
7907
+ const ensureIndexReady = async () => {
7908
+ const nodes = Object.values(baseNormalized.nodesById);
7909
+ const edges = Object.values(baseNormalized.edgesById);
7910
+ const service = serviceRef.current;
7911
+ if (!service) return false;
7912
+ if (service.isReady() && !service.needsRebuild()) return true;
7913
+ setIsIndexing(true);
7914
+ setError(null);
7915
+ try {
7916
+ if (!service.isReady()) await service.initialize();
7917
+ await service.rebuildIndex(nodes, edges);
7918
+ setIsReady(true);
7919
+ setStats(service.getStats());
7920
+ return true;
7921
+ } catch (err) {
7922
+ setError(err instanceof Error ? err : new Error(String(err)));
7923
+ setIsReady(false);
7924
+ return false;
7925
+ } finally {
7926
+ setIsIndexing(false);
7927
+ }
7928
+ };
7929
+ const search2 = async (query, searchOptions) => {
7930
+ const term = query.trim();
7931
+ if (!term) return [];
7932
+ try {
7933
+ const ready = await ensureIndexReady();
7934
+ if (!ready || !serviceRef.current) return [];
7935
+ return await serviceRef.current.search({ term, ...searchOptions });
7936
+ } catch (err) {
7937
+ errorLog7("[GraphFinder] search error:", err);
7938
+ setError(err instanceof Error ? err : new Error(String(err)));
7939
+ return [];
7940
+ }
7941
+ };
7942
+ return { search: search2, isReady, isIndexing, stats, error };
7943
+ }
7944
+
7945
+ // src/finder/useFinderState.ts
7946
+ function isNodeRow(item) {
7947
+ return "displayName" in item && !("start_id_" in item);
7948
+ }
7949
+ function isEdgeRow(item) {
7950
+ return "start_id_" in item && "end_id_" in item;
7951
+ }
7952
+ function deduplicateResults(items) {
7953
+ const seen = /* @__PURE__ */ new Set();
7954
+ const deduped = [];
7955
+ for (const raw of items) {
7956
+ if (raw && typeof raw === "object" && "id_" in raw) {
7957
+ const item = raw;
7958
+ const key = `${isEdgeRow(item) ? "edge" : "node"}:${String(item.id_)}`;
7959
+ if (!seen.has(key)) {
7960
+ seen.add(key);
7961
+ deduped.push(item);
7962
+ }
7963
+ } else {
7964
+ deduped.push(raw);
7965
+ }
7966
+ }
7967
+ return deduped;
7968
+ }
7969
+ function oramaHitToRow(r) {
7970
+ const doc = r.document;
7971
+ const properties = doc.properties_json ? JSON.parse(doc.properties_json) : {};
7972
+ return {
7973
+ id_: doc.id_,
7974
+ label_: doc.label_,
7975
+ displayName: doc.displayName,
7976
+ ...doc.type === "edge" ? { start_id_: doc.start_id_, end_id_: doc.end_id_ } : {},
7977
+ ...properties
7978
+ };
7979
+ }
7980
+ function useFinderState() {
7981
+ const useGraphFilter2 = useWorkspaceGraphFilter();
7982
+ const [searchMode, setSearchMode] = useState20("fulltext");
7983
+ const [expressionQuery, setExpressionQuery] = useState20("");
7984
+ const [fulltextQuery, setFulltextQuery] = useState20("");
7985
+ const [results, setResults] = useState20([]);
7986
+ const [error, setError] = useState20(null);
7987
+ const [hasSearched, setHasSearched] = useState20(false);
7988
+ const [searchInfo, setSearchInfo] = useState20("");
7989
+ const { baseNormalized, normalized } = useLiveGraphData();
7990
+ const lastDataChangeTime = useGraphFilter2((s) => s.lastDataChangeTime);
7991
+ const { search: fullTextSearch, isIndexing, stats } = useGraphSearch();
7992
+ useEffect11(() => {
7993
+ setExpressionQuery("");
7994
+ setFulltextQuery("");
7995
+ setResults([]);
7996
+ setError(null);
7997
+ setHasSearched(false);
7998
+ setSearchInfo("");
7999
+ }, [lastDataChangeTime]);
8000
+ const buildQueryData = () => {
8001
+ const all = flattenGraphData(baseNormalized);
8002
+ const visible = flattenGraphData(normalized);
8003
+ return {
8004
+ ...all,
8005
+ vnodes: visible.nodes,
8006
+ vedges: visible.edges,
8007
+ vgraph: visible.graph
8008
+ };
8009
+ };
8010
+ const runExpression = () => {
8011
+ const result = mistql.query(expressionQuery, buildQueryData());
8012
+ const arr = Array.isArray(result) ? result : [result];
8013
+ setResults(deduplicateResults(arr));
8014
+ setHasSearched(true);
8015
+ };
8016
+ const runFullText = async () => {
8017
+ const hits = await fullTextSearch(fulltextQuery);
8018
+ setResults(deduplicateResults(hits.map(oramaHitToRow)));
8019
+ setHasSearched(true);
8020
+ };
8021
+ const runBoth = async () => {
8022
+ const exprResult = expressionQuery.trim() ? mistql.query(expressionQuery, buildQueryData()) : buildQueryData().graph;
8023
+ const exprArray = Array.isArray(exprResult) ? exprResult : [exprResult];
8024
+ if (!fulltextQuery.trim()) {
8025
+ setResults(deduplicateResults(exprArray));
8026
+ setSearchInfo(`${exprArray.length} items after expression filter`);
8027
+ setHasSearched(true);
8028
+ return;
8029
+ }
8030
+ const exprIds = new Set(
8031
+ exprArray.filter((i) => i && typeof i === "object" && "id_" in i).map((i) => String(i.id_))
8032
+ );
8033
+ const hits = await fullTextSearch(fulltextQuery);
8034
+ const filtered = hits.filter((h) => exprIds.has(h.document.id_));
8035
+ const deduped = deduplicateResults(filtered.map(oramaHitToRow));
8036
+ setResults(deduped);
8037
+ setSearchInfo(
8038
+ `${exprArray.length} items after expression -> ${deduped.length} matches in full-text search`
8039
+ );
8040
+ setHasSearched(true);
8041
+ };
8042
+ const activeQuery = () => {
8043
+ if (searchMode === "expression") return expressionQuery;
8044
+ if (searchMode === "fulltext") return fulltextQuery;
8045
+ return expressionQuery || fulltextQuery;
8046
+ };
8047
+ const runActiveMode = async () => {
8048
+ if (searchMode === "expression") return runExpression();
8049
+ if (searchMode === "fulltext") return runFullText();
8050
+ return runBoth();
8051
+ };
8052
+ const handleSearch = async () => {
8053
+ if (!activeQuery().trim()) {
8054
+ setError(null);
8055
+ setResults([]);
8056
+ setHasSearched(false);
8057
+ setSearchInfo("");
8058
+ return;
8059
+ }
8060
+ try {
8061
+ setError(null);
8062
+ setSearchInfo("");
8063
+ await runActiveMode();
8064
+ } catch (err) {
8065
+ setError(err instanceof Error ? err.message : "Search execution failed");
8066
+ setResults([]);
8067
+ setHasSearched(true);
8068
+ setSearchInfo("");
8069
+ }
8070
+ };
8071
+ return {
8072
+ searchMode,
8073
+ setSearchMode,
8074
+ expressionQuery,
8075
+ setExpressionQuery,
8076
+ fulltextQuery,
8077
+ setFulltextQuery,
8078
+ results,
8079
+ error,
8080
+ hasSearched,
8081
+ searchInfo,
8082
+ isIndexing,
8083
+ statsLabel: stats && stats.isReady ? ` - ${stats.totalIndexed} indexed` : "",
8084
+ handleSearch
8085
+ };
8086
+ }
8087
+
8088
+ // src/finder/GraphFinder.tsx
8089
+ import { Fragment as Fragment9, jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
8090
+ function GraphFinder() {
8091
+ const {
8092
+ searchMode,
8093
+ setSearchMode,
8094
+ expressionQuery,
8095
+ setExpressionQuery,
8096
+ fulltextQuery,
8097
+ setFulltextQuery,
8098
+ results,
8099
+ error,
8100
+ hasSearched,
8101
+ searchInfo,
8102
+ isIndexing,
8103
+ statsLabel,
8104
+ handleSearch
8105
+ } = useFinderState();
8106
+ const handleKeyDown = (e) => {
8107
+ if ((e.ctrlKey || e.metaKey) && e.key === "Enter") handleSearch();
8108
+ };
8109
+ const both = searchMode === "both";
8110
+ return /* @__PURE__ */ jsxs26("div", { className: "h-full flex flex-col p-4", children: [
8111
+ /* @__PURE__ */ jsx28("div", { className: "mb-3 flex-shrink-0", children: /* @__PURE__ */ jsx28(
8112
+ FormSelect3,
8113
+ {
8114
+ label: "Search Mode",
8115
+ value: searchMode,
8116
+ onChange: (value) => setSearchMode(value || "fulltext"),
8117
+ options: [
8118
+ { value: "fulltext", label: "Full-Text" },
8119
+ { value: "expression", label: "Expression" },
8120
+ { value: "both", label: "Both" }
8121
+ ],
8122
+ clearable: false
8123
+ }
8124
+ ) }),
8125
+ /* @__PURE__ */ jsxs26("div", { className: "mb-4 flex-shrink-0 space-y-3", children: [
8126
+ (searchMode === "expression" || both) && /* @__PURE__ */ jsx28(
8127
+ ExpressionInput,
8128
+ {
8129
+ value: expressionQuery,
8130
+ onChange: setExpressionQuery,
8131
+ onKeyDown: handleKeyDown,
8132
+ both
8133
+ }
8134
+ ),
8135
+ (searchMode === "fulltext" || both) && /* @__PURE__ */ jsx28(
8136
+ FullTextInput,
8137
+ {
8138
+ value: fulltextQuery,
8139
+ onChange: setFulltextQuery,
8140
+ onKeyDown: handleKeyDown,
8141
+ onSearch: handleSearch,
8142
+ both,
8143
+ isIndexing,
8144
+ statsLabel
8145
+ }
8146
+ )
8147
+ ] }),
8148
+ error && /* @__PURE__ */ jsxs26(Alert5, { variant: "destructive", className: "mb-4 flex-shrink-0", children: [
8149
+ /* @__PURE__ */ jsx28(AlertTitle, { children: "Query Error" }),
8150
+ /* @__PURE__ */ jsx28(AlertDescription, { children: error })
8151
+ ] }),
8152
+ hasSearched && !error && /* @__PURE__ */ jsx28(ResultsPanel, { results, searchInfo })
8153
+ ] });
8154
+ }
8155
+ function ExpressionInput({
8156
+ value,
8157
+ onChange,
8158
+ onKeyDown,
8159
+ both
8160
+ }) {
8161
+ return /* @__PURE__ */ jsxs26("div", { children: [
8162
+ both && /* @__PURE__ */ jsx28("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Expression filter (applied first):" }),
8163
+ /* @__PURE__ */ jsx28(
8164
+ FormTextarea,
8165
+ {
8166
+ placeholder: 'e.g., Person | filter name == "John"',
8167
+ value,
8168
+ onChange: (e) => onChange(e.target.value),
8169
+ onKeyDown,
8170
+ autosize: true,
8171
+ minRows: both ? 2 : 3
8172
+ }
8173
+ ),
8174
+ !both && /* @__PURE__ */ jsx28("div", { className: "text-xs text-muted-foreground mt-1", children: "Use pipes (|) to filter and transform. Available: nodes, edges, graph, and each label." })
8175
+ ] });
8176
+ }
8177
+ function FullTextInput({
8178
+ value,
8179
+ onChange,
8180
+ onKeyDown,
8181
+ onSearch,
8182
+ both,
8183
+ isIndexing,
8184
+ statsLabel
8185
+ }) {
8186
+ return /* @__PURE__ */ jsxs26("div", { children: [
8187
+ both && /* @__PURE__ */ jsx28("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Full-text search (on filtered results):" }),
8188
+ /* @__PURE__ */ jsx28(
8189
+ FormTextarea,
8190
+ {
8191
+ placeholder: "e.g., software engineer machine learning",
8192
+ value,
8193
+ onChange: (e) => onChange(e.target.value),
8194
+ onKeyDown,
8195
+ autosize: true,
8196
+ minRows: both ? 2 : 3,
8197
+ rightSection: /* @__PURE__ */ jsx28(SimpleTooltip5, { label: "Search", children: /* @__PURE__ */ jsx28(Button11, { variant: "ghost", size: "icon", onClick: onSearch, children: /* @__PURE__ */ jsx28(Search, { size: 16 }) }) }),
8198
+ rightSectionWidth: 48
8199
+ }
8200
+ ),
8201
+ !both && /* @__PURE__ */ jsxs26("div", { className: "text-xs text-muted-foreground mt-1", children: [
8202
+ "Natural language search with typo tolerance",
8203
+ isIndexing && " - building index...",
8204
+ statsLabel
8205
+ ] })
8206
+ ] });
8207
+ }
8208
+ function ResultsPanel({
8209
+ results,
8210
+ searchInfo
8211
+ }) {
8212
+ return /* @__PURE__ */ jsxs26("div", { className: "flex flex-col flex-1 min-h-0", children: [
8213
+ /* @__PURE__ */ jsxs26("div", { className: "mb-2 flex-shrink-0", children: [
8214
+ /* @__PURE__ */ jsxs26("div", { className: "text-sm font-semibold", children: [
8215
+ "Results: ",
8216
+ results.length,
8217
+ " ",
8218
+ results.length === 1 ? "item" : "items"
8219
+ ] }),
8220
+ searchInfo && /* @__PURE__ */ jsx28("div", { className: "text-xs text-muted-foreground mt-1", children: searchInfo })
8221
+ ] }),
8222
+ results.length === 0 ? /* @__PURE__ */ jsx28("div", { className: "text-sm text-muted-foreground italic", children: "No results found" }) : /* @__PURE__ */ jsx28("div", { className: "space-y-2 overflow-y-auto flex-1", children: results.map((item, index) => /* @__PURE__ */ jsx28(ResultItem, { item }, resultKey(item, index))) })
8223
+ ] });
8224
+ }
8225
+ function resultKey(item, index) {
8226
+ if (item && typeof item === "object" && "id_" in item) {
8227
+ return `${isEdgeRow(item) ? "edge" : "node"}:${String(item.id_)}`;
8228
+ }
8229
+ return String(index);
8230
+ }
8231
+ function ResultItem({ item }) {
8232
+ const useGraphFocus2 = useWorkspaceGraphFocus();
8233
+ const requestFocus = useGraphFocus2((s) => s.requestFocus);
8234
+ if (typeof item !== "object" || item === null) {
8235
+ return /* @__PURE__ */ jsx28("div", { className: "p-3 bg-muted/40 rounded border border-border", children: /* @__PURE__ */ jsx28("div", { className: "text-sm font-medium", children: String(item) }) });
8236
+ }
8237
+ const isNodeItem = isNodeRow(item);
8238
+ const isEdgeItem = isEdgeRow(item);
8239
+ const isGraphElement = isNodeItem || isEdgeItem;
8240
+ const focus = (shouldSelect) => {
8241
+ if (!isGraphElement) return;
8242
+ requestFocus(String(item.id_), isNodeItem ? "node" : "edge", shouldSelect);
8243
+ };
8244
+ return /* @__PURE__ */ jsx28(
8245
+ "div",
8246
+ {
8247
+ className: `p-3 bg-background rounded border border-border transition-colors ${isGraphElement ? "hover:border-primary hover:bg-accent cursor-pointer" : ""}`,
8248
+ onClick: () => focus(false),
8249
+ onDoubleClick: () => focus(true),
8250
+ title: isGraphElement ? "Click to focus, double-click to focus and select" : void 0,
8251
+ children: /* @__PURE__ */ jsxs26("div", { className: "flex items-start justify-between", children: [
8252
+ /* @__PURE__ */ jsxs26("div", { className: "flex items-center gap-2", children: [
8253
+ isNodeItem && /* @__PURE__ */ jsxs26(Fragment9, { children: [
8254
+ /* @__PURE__ */ jsx28("span", { className: "text-xs font-semibold px-2 py-0.5 bg-accent text-accent-foreground rounded", children: item.label_ }),
8255
+ /* @__PURE__ */ jsx28("span", { className: "text-sm font-medium", children: item.displayName })
8256
+ ] }),
8257
+ isEdgeItem && /* @__PURE__ */ jsxs26(Fragment9, { children: [
8258
+ /* @__PURE__ */ jsx28("span", { className: "text-xs font-semibold px-2 py-0.5 bg-accent text-accent-foreground rounded", children: item.label_ }),
8259
+ /* @__PURE__ */ jsxs26("span", { className: "text-xs text-muted-foreground", children: [
8260
+ String(item.start_id_),
8261
+ " -> ",
8262
+ String(item.end_id_)
8263
+ ] })
8264
+ ] }),
8265
+ !isGraphElement && /* @__PURE__ */ jsx28("span", { className: "text-sm font-medium", children: "Object" })
8266
+ ] }),
8267
+ isGraphElement && /* @__PURE__ */ jsxs26("span", { className: "text-xs text-muted-foreground", children: [
8268
+ "ID: ",
8269
+ String(item.id_)
8270
+ ] })
8271
+ ] })
8272
+ }
8273
+ );
8274
+ }
7386
8275
  export {
7387
8276
  ActionPanel_default as ActionPanel,
7388
8277
  DeleteNodeDialog,
@@ -7391,6 +8280,7 @@ export {
7391
8280
  EdgeInfoCard_default as EdgeInfoCard,
7392
8281
  GraphA11yList,
7393
8282
  GraphEditToolbar,
8283
+ GraphFinder,
7394
8284
  GraphModelEditor,
7395
8285
  GraphRendererProvider,
7396
8286
  GraphStoresContext,
@@ -7399,8 +8289,11 @@ export {
7399
8289
  NodeFormSheet,
7400
8290
  NodeInfoCard_default as NodeInfoCard,
7401
8291
  NodeTypeNameSheet,
8292
+ OramaSearchService,
7402
8293
  Overview_default as Overview,
7403
8294
  PropertyFormSheet,
8295
+ QueryEditor,
8296
+ QueryHistory,
7404
8297
  RelationFormSheet,
7405
8298
  TypeDetailPanel,
7406
8299
  addEdgeType,
@@ -7415,6 +8308,7 @@ export {
7415
8308
  createGraphExplorationStore,
7416
8309
  createGraphFilterStore,
7417
8310
  createGraphFocusStore,
8311
+ createQueryHistoryStore,
7418
8312
  createSelectionStore,
7419
8313
  deleteEdgeType,
7420
8314
  deleteEdgeTypeProperty,
@@ -7430,9 +8324,11 @@ export {
7430
8324
  enrichNodes,
7431
8325
  enrichSchemaWithRelations,
7432
8326
  extractDisplayLabel,
8327
+ extractGraphFromRows,
7433
8328
  extractRelationFields,
7434
8329
  findEdgeType,
7435
8330
  findType,
8331
+ flattenGraphData,
7436
8332
  graphCapabilitiesSchema,
7437
8333
  groupEdgesByRelation,
7438
8334
  hasProperties,
@@ -7470,12 +8366,14 @@ export {
7470
8366
  useCytoscapeData,
7471
8367
  GraphFocus_default as useGraphFocus,
7472
8368
  useGraphRenderer,
8369
+ useGraphSearch,
7473
8370
  useLiveGraphData,
7474
8371
  useReagraphData,
7475
8372
  useStableRef,
7476
8373
  useWorkspaceGraphData,
7477
8374
  useWorkspaceGraphExploration,
7478
8375
  useWorkspaceGraphFilter,
8376
+ useWorkspaceGraphFocus,
7479
8377
  useWorkspaceSelection,
7480
8378
  validateCapabilities,
7481
8379
  validateGraphCapabilities