@petrarca/sonnet-graph 0.4.0 → 0.4.1

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
@@ -436,7 +436,8 @@ var useGraphFilter = createGraphFilterStore();
436
436
 
437
437
  // src/stores/GraphExploration.ts
438
438
  import { create as create4 } from "zustand";
439
- import { devLog as devLog3 } from "@petrarca/sonnet-core";
439
+ import { devLog as devLog3, errorLog } from "@petrarca/sonnet-core";
440
+ import { toast } from "sonner";
440
441
  function collectReachable(startId, direction, allEdges, includeStart) {
441
442
  const nodes = /* @__PURE__ */ new Set();
442
443
  const edges = /* @__PURE__ */ new Set();
@@ -542,6 +543,23 @@ function ensureNodeLabelInFilters(enrichedNodes, nodeId, graphFilterStore) {
542
543
  graphFilterStore.getState().setNodeLabelFilters([...currentFilters, nodeLabel]);
543
544
  }
544
545
  }
546
+ function mergeExpandedLabelsIntoFilters(stores, enrichedNodes, nodeId) {
547
+ const graphFilterState = stores.graphFilter.getState();
548
+ const currentFilters = graphFilterState.nodeLabelFilters;
549
+ if (currentFilters.length === 0 || enrichedNodes.length === 0) return;
550
+ const newLabels = new Set(enrichedNodes.map((n) => n.label_));
551
+ const labelsToAdd = Array.from(newLabels).filter(
552
+ (label) => !currentFilters.includes(label)
553
+ );
554
+ if (labelsToAdd.length > 0) {
555
+ devLog3("[GraphExploration] Adding expanded node labels to active filters", {
556
+ nodeId,
557
+ labelsToAdd,
558
+ currentFilters
559
+ });
560
+ stores.graphFilter.getState().setNodeLabelFilters([...currentFilters, ...labelsToAdd]);
561
+ }
562
+ }
545
563
  function createGraphExplorationStore(deps) {
546
564
  const stores = deps;
547
565
  return create4()((set, get) => ({
@@ -841,10 +859,10 @@ function createGraphExplorationStore(deps) {
841
859
  isNodeExpanded: (nodeId) => {
842
860
  return get().expandedNodeIds.has(nodeId);
843
861
  },
844
- expandNode: async (nodeId) => {
845
- devLog3("[GraphExploration] expandNode", { nodeId });
862
+ expandNode: async (nodeId, options) => {
863
+ devLog3("[GraphExploration] expandNode", { nodeId, options });
846
864
  try {
847
- const result = await stores.expandNode(Number(nodeId));
865
+ const result = await stores.expandNode(Number(nodeId), options);
848
866
  devLog3("[GraphExploration] Expansion API result", {
849
867
  nodeId,
850
868
  newNodes: result.nodes.length,
@@ -866,25 +884,7 @@ function createGraphExplorationStore(deps) {
866
884
  );
867
885
  const enrichedEdges = enrichEdges(result.edges);
868
886
  stores.graphData.getState().mergeExpandedData(enrichedNodes, enrichedEdges);
869
- const graphFilterState = stores.graphFilter.getState();
870
- const currentFilters = graphFilterState.nodeLabelFilters;
871
- if (currentFilters.length > 0 && enrichedNodes.length > 0) {
872
- const newLabels = new Set(enrichedNodes.map((n) => n.label_));
873
- const labelsToAdd = Array.from(newLabels).filter(
874
- (label) => !currentFilters.includes(label)
875
- );
876
- if (labelsToAdd.length > 0) {
877
- devLog3(
878
- "[GraphExploration] Adding expanded node labels to active filters",
879
- {
880
- nodeId,
881
- labelsToAdd,
882
- currentFilters
883
- }
884
- );
885
- stores.graphFilter.getState().setNodeLabelFilters([...currentFilters, ...labelsToAdd]);
886
- }
887
- }
887
+ mergeExpandedLabelsIntoFilters(stores, enrichedNodes, nodeId);
888
888
  get().markNodeExpanded(nodeId);
889
889
  const currentState = get();
890
890
  if (currentState.isFocusMode) {
@@ -900,13 +900,19 @@ function createGraphExplorationStore(deps) {
900
900
  });
901
901
  set({ focusedNodeIds: newFocusedIds });
902
902
  }
903
+ if (result.has_more) {
904
+ toast.info(
905
+ `Showing first ${enrichedNodes.length} neighbors \u2014 more exist. Refine filters or increase the limit.`
906
+ );
907
+ }
903
908
  devLog3("[GraphExploration] Expansion complete", {
904
909
  nodeId,
905
910
  addedNodes: enrichedNodes.length,
906
- addedEdges: enrichedEdges.length
911
+ addedEdges: enrichedEdges.length,
912
+ hasMore: result.has_more
907
913
  });
908
914
  } catch (error) {
909
- devLog3("[GraphExploration] Expansion failed", { nodeId, error });
915
+ errorLog("[GraphExploration] Expansion failed", { nodeId, error });
910
916
  throw error;
911
917
  }
912
918
  },
@@ -1439,10 +1445,10 @@ function GraphRendererProvider({
1439
1445
 
1440
1446
  // src/components/rea/ReaGraph.tsx
1441
1447
  import {
1442
- useEffect as useEffect2,
1443
- useRef,
1444
- useState as useState3,
1445
- useCallback as useCallback2
1448
+ useEffect as useEffect3,
1449
+ useRef as useRef2,
1450
+ useState as useState5,
1451
+ useCallback as useCallback3
1446
1452
  } from "react";
1447
1453
  import { devLog as devLog8 } from "@petrarca/sonnet-core";
1448
1454
  import {
@@ -1662,16 +1668,194 @@ function NodeHoverInfo({ node }) {
1662
1668
  var NodeHoverInfo_default = NodeHoverInfo;
1663
1669
 
1664
1670
  // src/components/rea/ReaContextMenu.tsx
1665
- import React2 from "react";
1671
+ import React3, { useState as useState4 } from "react";
1666
1672
  import { devLog as devLog7 } from "@petrarca/sonnet-core";
1673
+
1674
+ // src/components/rea/ExpandOptionsPanel.tsx
1675
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef, useState as useState3 } from "react";
1676
+ import { Checkbox, Input, Label } from "@petrarca/sonnet-ui";
1667
1677
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1678
+ function ExpandOptionsPanel({
1679
+ nodeLabel,
1680
+ onExpand,
1681
+ onBack,
1682
+ getEdgeTypes
1683
+ }) {
1684
+ const [direction, setDirection] = useState3("both");
1685
+ const [limit, setLimit] = useState3(100);
1686
+ const [edgeTypes, setEdgeTypes] = useState3([]);
1687
+ const [selectedTypes, setSelectedTypes] = useState3([]);
1688
+ const [loading, setLoading] = useState3(!!getEdgeTypes);
1689
+ const [offset, setOffset] = useState3({ x: 0, y: 0 });
1690
+ const dragRef = useRef(null);
1691
+ const onPointerDown = useCallback2(
1692
+ (e) => {
1693
+ if (e.target.closest("button")) return;
1694
+ e.preventDefault();
1695
+ e.target.setPointerCapture(e.pointerId);
1696
+ dragRef.current = {
1697
+ startX: e.clientX,
1698
+ startY: e.clientY,
1699
+ origX: offset.x,
1700
+ origY: offset.y
1701
+ };
1702
+ },
1703
+ [offset]
1704
+ );
1705
+ const onPointerMove = useCallback2((e) => {
1706
+ if (!dragRef.current) return;
1707
+ const dx = e.clientX - dragRef.current.startX;
1708
+ const dy = e.clientY - dragRef.current.startY;
1709
+ setOffset({ x: dragRef.current.origX + dx, y: dragRef.current.origY + dy });
1710
+ }, []);
1711
+ const onPointerUp = useCallback2(() => {
1712
+ dragRef.current = null;
1713
+ }, []);
1714
+ useEffect2(() => {
1715
+ if (!getEdgeTypes) return;
1716
+ setLoading(true);
1717
+ getEdgeTypes().then((types) => {
1718
+ setEdgeTypes(types);
1719
+ setSelectedTypes(types);
1720
+ }).finally(() => setLoading(false));
1721
+ }, [getEdgeTypes]);
1722
+ const handleToggleType = (type) => {
1723
+ setSelectedTypes(
1724
+ (prev) => prev.includes(type) ? prev.filter((t) => t !== type) : [...prev, type]
1725
+ );
1726
+ };
1727
+ const handleExpand = () => {
1728
+ const options = {};
1729
+ if (direction !== "both") options.direction = direction;
1730
+ const clampedLimit = Math.max(1, Math.min(1e4, limit));
1731
+ if (clampedLimit !== 100) options.limit = clampedLimit;
1732
+ if (selectedTypes.length > 0 && selectedTypes.length < edgeTypes.length) {
1733
+ options.relTypes = selectedTypes;
1734
+ }
1735
+ onExpand(options);
1736
+ };
1737
+ const allSelected = selectedTypes.length === edgeTypes.length;
1738
+ const noneSelected = edgeTypes.length > 0 && selectedTypes.length === 0;
1739
+ return /* @__PURE__ */ jsxs4(
1740
+ "div",
1741
+ {
1742
+ className: "bg-background border border-border rounded-md shadow-md p-2.5 min-w-48 space-y-2",
1743
+ style: { transform: `translate(${offset.x}px, ${offset.y}px)` },
1744
+ children: [
1745
+ /* @__PURE__ */ jsxs4(
1746
+ "div",
1747
+ {
1748
+ className: "flex items-center justify-between gap-2 cursor-grab active:cursor-grabbing select-none",
1749
+ onPointerDown,
1750
+ onPointerMove,
1751
+ onPointerUp,
1752
+ children: [
1753
+ /* @__PURE__ */ jsx5(
1754
+ "button",
1755
+ {
1756
+ className: "text-[11px] text-muted-foreground hover:text-foreground cursor-pointer",
1757
+ onClick: onBack,
1758
+ children: "Back"
1759
+ }
1760
+ ),
1761
+ /* @__PURE__ */ jsx5("span", { className: "text-[11px] font-medium text-foreground truncate max-w-32", children: nodeLabel })
1762
+ ]
1763
+ }
1764
+ ),
1765
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
1766
+ /* @__PURE__ */ jsx5(Label, { className: "text-[11px] text-muted-foreground", children: "Direction" }),
1767
+ /* @__PURE__ */ jsx5("div", { className: "flex gap-0.5", children: ["both", "out", "in"].map((dir) => /* @__PURE__ */ jsx5(
1768
+ "button",
1769
+ {
1770
+ 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"}`,
1771
+ onClick: () => setDirection(dir),
1772
+ children: dir
1773
+ },
1774
+ dir
1775
+ )) })
1776
+ ] }),
1777
+ !loading && edgeTypes.length > 1 && /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
1778
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between", children: [
1779
+ /* @__PURE__ */ jsx5(Label, { className: "text-[11px] text-muted-foreground", children: "Rel Types" }),
1780
+ /* @__PURE__ */ jsx5(
1781
+ "button",
1782
+ {
1783
+ className: "text-[10px] text-muted-foreground hover:text-foreground cursor-pointer",
1784
+ onClick: () => setSelectedTypes(allSelected ? [] : [...edgeTypes]),
1785
+ children: allSelected ? "None" : "All"
1786
+ }
1787
+ )
1788
+ ] }),
1789
+ /* @__PURE__ */ jsx5("div", { className: "space-y-0.5 max-h-32 overflow-y-auto", children: edgeTypes.map((type) => /* @__PURE__ */ jsxs4(
1790
+ "label",
1791
+ {
1792
+ className: "flex items-center gap-1.5 text-[11px] cursor-pointer",
1793
+ children: [
1794
+ /* @__PURE__ */ jsx5(
1795
+ Checkbox,
1796
+ {
1797
+ checked: selectedTypes.includes(type),
1798
+ onCheckedChange: () => handleToggleType(type),
1799
+ className: "h-3 w-3"
1800
+ }
1801
+ ),
1802
+ /* @__PURE__ */ jsx5("span", { className: "truncate", children: type })
1803
+ ]
1804
+ },
1805
+ type
1806
+ )) })
1807
+ ] }),
1808
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
1809
+ /* @__PURE__ */ jsx5(Label, { className: "text-[11px] text-muted-foreground", children: "Limit" }),
1810
+ /* @__PURE__ */ jsx5(
1811
+ Input,
1812
+ {
1813
+ type: "number",
1814
+ min: 1,
1815
+ max: 1e4,
1816
+ value: limit,
1817
+ onChange: (e) => {
1818
+ const v = Number(e.target.value);
1819
+ if (!Number.isNaN(v)) setLimit(Math.max(1, Math.min(1e4, v)));
1820
+ },
1821
+ className: "h-6 text-[11px]"
1822
+ }
1823
+ )
1824
+ ] }),
1825
+ /* @__PURE__ */ jsx5(
1826
+ "button",
1827
+ {
1828
+ className: "w-full py-1.5 text-[11px] font-medium rounded bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer",
1829
+ onClick: handleExpand,
1830
+ disabled: loading || noneSelected,
1831
+ title: noneSelected ? "Select at least one relationship type" : void 0,
1832
+ children: "Expand"
1833
+ }
1834
+ )
1835
+ ]
1836
+ }
1837
+ );
1838
+ }
1839
+ var ExpandOptionsPanel_default = ExpandOptionsPanel;
1840
+
1841
+ // src/components/rea/ReaContextMenu.tsx
1842
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1668
1843
  function ReaContextMenu({
1669
1844
  data,
1670
1845
  onClose,
1671
1846
  elementType,
1672
1847
  useGraphExploration,
1673
- onCopyNode
1848
+ onCopyNode,
1849
+ getNodeEdgeTypes
1674
1850
  }) {
1851
+ const [showExpandPanel, setShowExpandPanel] = useState4(false);
1852
+ React3.useEffect(() => {
1853
+ const handleKey = (e) => {
1854
+ if (e.key === "Escape") onClose();
1855
+ };
1856
+ document.addEventListener("keydown", handleKey);
1857
+ return () => document.removeEventListener("keydown", handleKey);
1858
+ }, [onClose]);
1675
1859
  const handleFocus = () => {
1676
1860
  if (elementType === "node") {
1677
1861
  const nodeId = data.id;
@@ -1724,11 +1908,11 @@ function ReaContextMenu({
1724
1908
  }
1725
1909
  onClose();
1726
1910
  };
1727
- const handleExpand = () => {
1911
+ const handleExpandWithOptions = (options) => {
1728
1912
  if (elementType === "node") {
1729
1913
  const nodeId = data.id;
1730
- devLog7("[ReaContextMenu] Expand node:", nodeId);
1731
- useGraphExploration.getState().expandNode(nodeId);
1914
+ devLog7("[ReaContextMenu] Expand node with options:", nodeId, options);
1915
+ useGraphExploration.getState().expandNode(nodeId, options);
1732
1916
  }
1733
1917
  onClose();
1734
1918
  };
@@ -1763,7 +1947,7 @@ function ReaContextMenu({
1763
1947
  { label: "Hide Node", action: handleHideNode },
1764
1948
  { label: "Hide Incoming", action: handleHideIncoming },
1765
1949
  { label: "Hide Outgoing", action: handleHideOutgoing },
1766
- { label: "Expand", action: handleExpand }
1950
+ { label: "Expand...", action: () => setShowExpandPanel(true) }
1767
1951
  ];
1768
1952
  const edgeMenuItems = [
1769
1953
  { label: "Focus", action: handleFocus },
@@ -1773,23 +1957,46 @@ function ReaContextMenu({
1773
1957
  { label: "Hide Reverse Path", action: handleHideReversePath }
1774
1958
  ];
1775
1959
  const menuItems = elementType === "node" ? nodeMenuItems : edgeMenuItems;
1776
- return /* @__PURE__ */ jsx5("div", { className: "bg-white border border-gray-200 rounded-md shadow-lg py-1 min-w-48", children: menuItems.map((item, index) => /* @__PURE__ */ jsxs4(React2.Fragment, { children: [
1777
- /* @__PURE__ */ jsx5(
1960
+ if (showExpandPanel && elementType === "node") {
1961
+ const nodeLabel = data.label || data.id;
1962
+ return /* @__PURE__ */ jsx6(
1963
+ ExpandOptionsPanel_default,
1964
+ {
1965
+ nodeLabel: String(nodeLabel),
1966
+ onExpand: handleExpandWithOptions,
1967
+ onBack: () => setShowExpandPanel(false),
1968
+ getEdgeTypes: getNodeEdgeTypes ? () => getNodeEdgeTypes(Number(data.id)) : void 0
1969
+ }
1970
+ );
1971
+ }
1972
+ return /* @__PURE__ */ jsxs5("div", { className: "bg-background border border-border rounded-md shadow-lg py-1 min-w-48", children: [
1973
+ menuItems.map((item, index) => /* @__PURE__ */ jsxs5(React3.Fragment, { children: [
1974
+ /* @__PURE__ */ jsx6(
1975
+ "button",
1976
+ {
1977
+ className: "w-full text-left px-3 py-2 text-sm hover:bg-muted/50 cursor-pointer",
1978
+ onClick: item.action,
1979
+ children: item.label
1980
+ }
1981
+ ),
1982
+ index === 1 && /* @__PURE__ */ jsx6("div", { className: "border-t border-border my-1" })
1983
+ ] }, item.label)),
1984
+ /* @__PURE__ */ jsx6("div", { className: "border-t border-border my-1" }),
1985
+ /* @__PURE__ */ jsx6(
1778
1986
  "button",
1779
1987
  {
1780
- className: "w-full text-left px-3 py-2 text-sm hover:bg-gray-100 cursor-pointer",
1781
- onClick: item.action,
1782
- children: item.label
1988
+ className: "w-full text-left px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted/50 cursor-pointer",
1989
+ onClick: onClose,
1990
+ children: "Cancel"
1783
1991
  }
1784
- ),
1785
- index === 1 && /* @__PURE__ */ jsx5("div", { className: "border-t border-gray-200 my-1" })
1786
- ] }, item.label)) });
1992
+ )
1993
+ ] });
1787
1994
  }
1788
1995
  var ReaContextMenu_default = ReaContextMenu;
1789
1996
 
1790
1997
  // src/components/rea/ReaGraph.tsx
1791
- import { toast } from "sonner";
1792
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1998
+ import { toast as toast2 } from "sonner";
1999
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1793
2000
  function findElementInGraphData(elementId, elementType, nodes, edges) {
1794
2001
  if (elementType === "node") {
1795
2002
  const node = nodes.find((n) => n.id === elementId);
@@ -1851,22 +2058,26 @@ function processFocusAction(focusAction, canvasRef, nodes, edges, setSelectedNod
1851
2058
  }
1852
2059
  return true;
1853
2060
  }
1854
- function ReaGraph({ onCopyNode, extraToolbarContent }) {
1855
- const isMountedRef = useRef(false);
2061
+ function ReaGraph({
2062
+ onCopyNode,
2063
+ extraToolbarContent,
2064
+ getNodeEdgeTypes
2065
+ }) {
2066
+ const isMountedRef = useRef2(false);
1856
2067
  const { nodes, edges } = useReagraphData();
1857
2068
  const { normalized } = useLiveGraphData();
1858
- useEffect2(() => {
2069
+ useEffect3(() => {
1859
2070
  devLog8("[ReGraph] counts", { nodes: nodes.length, edges: edges.length });
1860
2071
  if (nodes.length > 0) devLog8("[ReGraph] sample node", nodes[0]);
1861
2072
  if (edges.length > 0) devLog8("[ReGraph] sample edge", edges[0]);
1862
2073
  }, [nodes, edges]);
1863
- const canvas = useRef(null);
1864
- const [hoveredNode, setHoveredNode] = useState3(
2074
+ const canvas = useRef2(null);
2075
+ const [hoveredNode, setHoveredNode] = useState5(
1865
2076
  void 0
1866
2077
  );
1867
- const [layout, setLayout] = useState3("forceDirected2d");
1868
- const [fullscreen, setFullscreen] = useState3(false);
1869
- const [pathSelectionType, setPathSelectionType] = useState3("direct");
2078
+ const [layout, setLayout] = useState5("forceDirected2d");
2079
+ const [fullscreen, setFullscreen] = useState5(false);
2080
+ const [pathSelectionType, setPathSelectionType] = useState5("direct");
1870
2081
  const useGraphSelection = useWorkspaceSelection();
1871
2082
  const useGraphFilter2 = useWorkspaceGraphFilter();
1872
2083
  const useGraphExploration = useWorkspaceGraphExploration();
@@ -1892,7 +2103,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
1892
2103
  pathSelectionType,
1893
2104
  type: "multi"
1894
2105
  });
1895
- useEffect2(() => {
2106
+ useEffect3(() => {
1896
2107
  const unsubscribe = GraphFocus_default.subscribe((state) => {
1897
2108
  const focusAction = state.focusAction;
1898
2109
  if (!focusAction || !canvas.current) return;
@@ -1916,12 +2127,12 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
1916
2127
  });
1917
2128
  return unsubscribe;
1918
2129
  }, [nodes, edges, setSelectedNodeIds, setSelectedEdge]);
1919
- useEffect2(() => {
2130
+ useEffect3(() => {
1920
2131
  if (!canvas.current) return;
1921
2132
  clearSelections();
1922
2133
  clearSelectionStore();
1923
2134
  }, [lastDataChangeTime, clearSelections, clearSelectionStore]);
1924
- const fitToScreen = useCallback2(
2135
+ const fitToScreen = useCallback3(
1925
2136
  (reason) => {
1926
2137
  if (!isMountedRef.current) return;
1927
2138
  if (nodes.length === 0) return;
@@ -1947,7 +2158,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
1947
2158
  },
1948
2159
  [nodes.length]
1949
2160
  );
1950
- useEffect2(() => {
2161
+ useEffect3(() => {
1951
2162
  isMountedRef.current = true;
1952
2163
  fitToScreen("mount");
1953
2164
  const state = useGraphSelection.getState();
@@ -1967,7 +2178,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
1967
2178
  isMountedRef.current = false;
1968
2179
  };
1969
2180
  }, [fitToScreen, setSelections, setSelectedEdge, useGraphSelection]);
1970
- useEffect2(() => {
2181
+ useEffect3(() => {
1971
2182
  if (isMountedRef.current) fitToScreen("counts-changed");
1972
2183
  }, [nodes.length, edges.length, fitToScreen]);
1973
2184
  function updateLayout(next) {
@@ -2009,14 +2220,14 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2009
2220
  setSelectedEdge(null);
2010
2221
  if (onCanvasClick) onCanvasClick(e);
2011
2222
  }
2012
- useEffect2(() => {
2223
+ useEffect3(() => {
2013
2224
  if (!isMountedRef.current) return;
2014
2225
  if (selections.length > 0 && selections.length <= 2) {
2015
2226
  const selectedNodes = selections.map((id) => nodes.find((n) => n.id === id)).filter(Boolean);
2016
2227
  setSelectedNodeIds(selectedNodes.map((n) => n.id));
2017
2228
  }
2018
2229
  }, [selections, nodes]);
2019
- useEffect2(() => {
2230
+ useEffect3(() => {
2020
2231
  if (!isMountedRef.current) return;
2021
2232
  const currentSelectedIds = useGraphSelection.getState().selectedNodeIds;
2022
2233
  if (!currentSelectedIds.length) return;
@@ -2035,7 +2246,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2035
2246
  setSelectedEdge,
2036
2247
  useGraphSelection
2037
2248
  ]);
2038
- useEffect2(() => {
2249
+ useEffect3(() => {
2039
2250
  if (!isMountedRef.current) return;
2040
2251
  const currentSelectedIds = useGraphSelection.getState().selectedNodeIds;
2041
2252
  if (!currentSelectedIds.length) return;
@@ -2047,7 +2258,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2047
2258
  if (stillPresentIds.length < 2) setSelectedEdge(null);
2048
2259
  }
2049
2260
  }, [nodes, setSelectedNodeIds, setSelectedEdge, useGraphSelection]);
2050
- useEffect2(() => {
2261
+ useEffect3(() => {
2051
2262
  if (!fullscreen) return;
2052
2263
  const prevOverflow = document.body.style.overflow;
2053
2264
  document.body.style.overflow = "hidden";
@@ -2061,7 +2272,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2061
2272
  window.removeEventListener("keydown", handleKey);
2062
2273
  };
2063
2274
  }, [fullscreen, fitToScreen]);
2064
- useEffect2(() => {
2275
+ useEffect3(() => {
2065
2276
  if (!fullscreen) return;
2066
2277
  const id = setTimeout(() => {
2067
2278
  if (canvas.current) {
@@ -2070,7 +2281,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2070
2281
  }, 50);
2071
2282
  return () => clearTimeout(id);
2072
2283
  }, [fullscreen, fitToScreen]);
2073
- useEffect2(() => {
2284
+ useEffect3(() => {
2074
2285
  if (!isMountedRef.current) return;
2075
2286
  const handleResize = () => {
2076
2287
  if (isMountedRef.current && canvas.current) {
@@ -2082,8 +2293,8 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2082
2293
  window.removeEventListener("resize", handleResize);
2083
2294
  };
2084
2295
  }, [fitToScreen]);
2085
- const firstDataRef = useRef(true);
2086
- useEffect2(() => {
2296
+ const firstDataRef = useRef2(true);
2297
+ useEffect3(() => {
2087
2298
  if (!isMountedRef.current) return;
2088
2299
  if (nodes.length > 0) {
2089
2300
  if (firstDataRef.current) {
@@ -2096,18 +2307,18 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2096
2307
  }, [nodes.length, fitToScreen]);
2097
2308
  if (nodes.length === 0) {
2098
2309
  devLog8("[ReGraph] empty state render");
2099
- return /* @__PURE__ */ jsxs5(Card2, { className: "relative w-full h-full items-center justify-center text-slate-500 gap-2 shadow-xs p-4", children: [
2100
- /* @__PURE__ */ jsx6("div", { children: "No data to display." }),
2101
- /* @__PURE__ */ jsxs5("div", { children: [
2310
+ return /* @__PURE__ */ jsxs6(Card2, { className: "relative w-full h-full items-center justify-center text-slate-500 gap-2 shadow-xs p-4", children: [
2311
+ /* @__PURE__ */ jsx7("div", { children: "No data to display." }),
2312
+ /* @__PURE__ */ jsxs6("div", { children: [
2102
2313
  "Please",
2103
2314
  " ",
2104
- /* @__PURE__ */ jsx6("span", { className: "font-semibold", children: "run a query, check filters, or add a node" }),
2315
+ /* @__PURE__ */ jsx7("span", { className: "font-semibold", children: "run a query, check filters, or add a node" }),
2105
2316
  " ",
2106
2317
  "to the workbench to populate the graph."
2107
2318
  ] })
2108
2319
  ] });
2109
2320
  }
2110
- return /* @__PURE__ */ jsxs5(
2321
+ return /* @__PURE__ */ jsxs6(
2111
2322
  "div",
2112
2323
  {
2113
2324
  className: cn(
@@ -2115,7 +2326,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2115
2326
  fullscreen ? "fixed inset-0 z-50 w-screen h-screen p-0 m-0" : "relative w-full h-full"
2116
2327
  ),
2117
2328
  children: [
2118
- /* @__PURE__ */ jsx6(Card2, { className: "h-full shadow-xs p-4", children: /* @__PURE__ */ jsx6(
2329
+ /* @__PURE__ */ jsx7(Card2, { className: "h-full shadow-xs p-4", children: /* @__PURE__ */ jsx7(
2119
2330
  GraphCanvas,
2120
2331
  {
2121
2332
  ref: canvas,
@@ -2140,21 +2351,22 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2140
2351
  elementId: data.id,
2141
2352
  elementType
2142
2353
  });
2143
- return /* @__PURE__ */ jsx6(
2354
+ return /* @__PURE__ */ jsx7(
2144
2355
  ReaContextMenu_default,
2145
2356
  {
2146
2357
  data,
2147
2358
  onClose,
2148
2359
  elementType,
2149
2360
  useGraphExploration,
2150
- onCopyNode
2361
+ onCopyNode,
2362
+ getNodeEdgeTypes
2151
2363
  }
2152
2364
  );
2153
2365
  }
2154
2366
  }
2155
2367
  ) }),
2156
- hoveredNode && /* @__PURE__ */ jsx6(NodeHoverInfo_default, { node: hoveredNode }),
2157
- /* @__PURE__ */ jsx6(
2368
+ hoveredNode && /* @__PURE__ */ jsx7(NodeHoverInfo_default, { node: hoveredNode }),
2369
+ /* @__PURE__ */ jsx7(
2158
2370
  LayoutAndCameraControls_default,
2159
2371
  {
2160
2372
  fitToScreen,
@@ -2166,7 +2378,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2166
2378
  isFocusMode,
2167
2379
  onResetView: () => {
2168
2380
  useGraphExploration.getState().resetView();
2169
- toast.success(
2381
+ toast2.success(
2170
2382
  isFocusMode ? "Focus mode cleared" : "Hidden elements restored"
2171
2383
  );
2172
2384
  },
@@ -2181,12 +2393,12 @@ var ReaGraph_default = ReaGraph;
2181
2393
 
2182
2394
  // src/components/cyto/CytoscapeGraph.tsx
2183
2395
  import {
2184
- useEffect as useEffect3,
2185
- useRef as useRef2,
2186
- useState as useState4,
2187
- useCallback as useCallback3
2396
+ useEffect as useEffect4,
2397
+ useRef as useRef3,
2398
+ useState as useState6,
2399
+ useCallback as useCallback4
2188
2400
  } from "react";
2189
- import { devLog as devLog10, errorLog as errorLog2 } from "@petrarca/sonnet-core";
2401
+ import { devLog as devLog10, errorLog as errorLog3 } from "@petrarca/sonnet-core";
2190
2402
  import cytoscape from "cytoscape";
2191
2403
  import fcose from "cytoscape-fcose";
2192
2404
  import cola from "cytoscape-cola";
@@ -2208,7 +2420,7 @@ import {
2208
2420
  SimpleTooltip as SimpleTooltip3
2209
2421
  } from "@petrarca/sonnet-ui";
2210
2422
  import { LayoutPanelTop as LayoutPanelTop2, Monitor, Frame, Eye as Eye2, Minus } from "lucide-react";
2211
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2423
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2212
2424
  function ViewControls2({
2213
2425
  zoomIn,
2214
2426
  zoomOut,
@@ -2219,17 +2431,17 @@ function ViewControls2({
2219
2431
  isFocusMode,
2220
2432
  onResetView
2221
2433
  }) {
2222
- return /* @__PURE__ */ jsxs6(Fragment2, { children: [
2223
- /* @__PURE__ */ jsx7(SimpleTooltip3, { label: "Zoom In", side: "left", children: /* @__PURE__ */ jsx7(Button3, { variant: "ghost", size: "icon", onClick: zoomIn, children: /* @__PURE__ */ jsx7("span", { style: { fontWeight: 600 }, children: "+" }) }) }),
2224
- /* @__PURE__ */ jsx7(SimpleTooltip3, { label: "Zoom Out", side: "left", children: /* @__PURE__ */ jsx7(Button3, { variant: "ghost", size: "icon", onClick: zoomOut, children: /* @__PURE__ */ jsx7(Minus, {}) }) }),
2225
- /* @__PURE__ */ jsx7(SimpleTooltip3, { label: "Fullscreen", side: "left", children: /* @__PURE__ */ jsx7(Button3, { variant: "ghost", size: "icon", onClick: toggleFullscreen, children: !fullscreen ? /* @__PURE__ */ jsx7(Monitor, {}) : /* @__PURE__ */ jsx7(Frame, {}) }) }),
2226
- /* @__PURE__ */ jsx7(SimpleTooltip3, { label: "Fit to screen", side: "left", children: /* @__PURE__ */ jsx7(Button3, { variant: "ghost", size: "icon", onClick: fitToScreen, children: /* @__PURE__ */ jsx7(Frame, {}) }) }),
2227
- (hasHiddenElements || isFocusMode) && onResetView && /* @__PURE__ */ jsx7(
2434
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
2435
+ /* @__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: "+" }) }) }),
2436
+ /* @__PURE__ */ jsx8(SimpleTooltip3, { label: "Zoom Out", side: "left", children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: zoomOut, children: /* @__PURE__ */ jsx8(Minus, {}) }) }),
2437
+ /* @__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, {}) }) }),
2438
+ /* @__PURE__ */ jsx8(SimpleTooltip3, { label: "Fit to screen", side: "left", children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: fitToScreen, children: /* @__PURE__ */ jsx8(Frame, {}) }) }),
2439
+ (hasHiddenElements || isFocusMode) && onResetView && /* @__PURE__ */ jsx8(
2228
2440
  SimpleTooltip3,
2229
2441
  {
2230
2442
  label: isFocusMode ? "Reset Focus" : "Show All Hidden",
2231
2443
  side: "left",
2232
- children: /* @__PURE__ */ jsx7(Button3, { variant: "ghost", size: "icon", onClick: onResetView, children: /* @__PURE__ */ jsx7(Eye2, {}) })
2444
+ children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: onResetView, children: /* @__PURE__ */ jsx8(Eye2, {}) })
2233
2445
  }
2234
2446
  )
2235
2447
  ] });
@@ -2264,12 +2476,12 @@ var LAYOUT_GROUPS = [
2264
2476
  }
2265
2477
  ];
2266
2478
  function LayoutDropdown2({ currentLayout, updateLayout }) {
2267
- return /* @__PURE__ */ jsxs6(DropdownMenu3, { children: [
2268
- /* @__PURE__ */ jsx7(SimpleTooltip3, { label: "Layout", side: "left", children: /* @__PURE__ */ jsx7(DropdownMenuTrigger3, { asChild: true, children: /* @__PURE__ */ jsx7(Button3, { variant: "ghost", size: "icon", children: /* @__PURE__ */ jsx7(LayoutPanelTop2, {}) }) }) }),
2269
- /* @__PURE__ */ jsx7(DropdownMenuContent3, { side: "left", align: "start", children: LAYOUT_GROUPS.map((group, groupIdx) => /* @__PURE__ */ jsxs6("div", { children: [
2270
- groupIdx > 0 && /* @__PURE__ */ jsx7(DropdownMenuSeparator2, {}),
2271
- /* @__PURE__ */ jsx7(DropdownMenuLabel2, { children: group.label }),
2272
- group.items.map((item) => /* @__PURE__ */ jsx7(
2479
+ return /* @__PURE__ */ jsxs7(DropdownMenu3, { children: [
2480
+ /* @__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, {}) }) }) }),
2481
+ /* @__PURE__ */ jsx8(DropdownMenuContent3, { side: "left", align: "start", children: LAYOUT_GROUPS.map((group, groupIdx) => /* @__PURE__ */ jsxs7("div", { children: [
2482
+ groupIdx > 0 && /* @__PURE__ */ jsx8(DropdownMenuSeparator2, {}),
2483
+ /* @__PURE__ */ jsx8(DropdownMenuLabel2, { children: group.label }),
2484
+ group.items.map((item) => /* @__PURE__ */ jsx8(
2273
2485
  DropdownMenuItem3,
2274
2486
  {
2275
2487
  onClick: () => updateLayout(item.layout),
@@ -2294,9 +2506,9 @@ function CytoscapeControls({
2294
2506
  onResetView,
2295
2507
  extraToolbarContent
2296
2508
  }) {
2297
- return /* @__PURE__ */ jsxs6("div", { className: "absolute right-2 top-2 flex flex-col border bg-white rounded-md shadow-sm z-10", children: [
2298
- /* @__PURE__ */ jsx7(RendererDropdown, {}),
2299
- /* @__PURE__ */ jsx7(
2509
+ return /* @__PURE__ */ jsxs7("div", { className: "absolute right-2 top-2 flex flex-col border bg-white rounded-md shadow-sm z-10", children: [
2510
+ /* @__PURE__ */ jsx8(RendererDropdown, {}),
2511
+ /* @__PURE__ */ jsx8(
2300
2512
  ViewControls2,
2301
2513
  {
2302
2514
  zoomIn,
@@ -2310,7 +2522,7 @@ function CytoscapeControls({
2310
2522
  }
2311
2523
  ),
2312
2524
  extraToolbarContent,
2313
- /* @__PURE__ */ jsx7(
2525
+ /* @__PURE__ */ jsx8(
2314
2526
  LayoutDropdown2,
2315
2527
  {
2316
2528
  currentLayout,
@@ -2590,12 +2802,13 @@ function getLayoutPreset(options) {
2590
2802
  }
2591
2803
 
2592
2804
  // src/components/cyto/contextMenuConfig.ts
2593
- import { devLog as devLog9, errorLog } from "@petrarca/sonnet-core";
2594
- import { toast as toast2 } from "sonner";
2805
+ import { devLog as devLog9, errorLog as errorLog2 } from "@petrarca/sonnet-core";
2806
+ import { toast as toast3 } from "sonner";
2595
2807
  var createNodeContextMenu = ({
2596
2808
  cy,
2597
2809
  useGraphExploration,
2598
- onCopyNode
2810
+ onCopyNode,
2811
+ onExpandRequest
2599
2812
  }) => {
2600
2813
  devLog9("[CxtMenu] Initializing node context menu");
2601
2814
  return cy.cxtmenu({
@@ -2617,7 +2830,7 @@ var createNodeContextMenu = ({
2617
2830
  if (visualGraph) {
2618
2831
  onCopyNode?.(visualGraph);
2619
2832
  } else {
2620
- errorLog("[CxtMenu] visualGraph not found in element data");
2833
+ errorLog2("[CxtMenu] visualGraph not found in element data");
2621
2834
  }
2622
2835
  }
2623
2836
  },
@@ -2647,21 +2860,23 @@ var createNodeContextMenu = ({
2647
2860
  },
2648
2861
  {
2649
2862
  content: "Expand",
2650
- select: async (ele) => {
2863
+ select: (ele) => {
2651
2864
  const nodeId = ele.id();
2652
- devLog9("[CxtMenu] Expanding node:", nodeId);
2653
- try {
2654
- await useGraphExploration.getState().expandNode(nodeId);
2655
- devLog9("[CxtMenu] Expansion complete");
2656
- } catch (error) {
2657
- errorLog("[CxtMenu] Expansion failed:", error);
2658
- toast2.error("Failed to expand node");
2865
+ devLog9("[CxtMenu] Expand request for node:", nodeId);
2866
+ if (onExpandRequest) {
2867
+ const pos = ele.renderedPosition();
2868
+ const label = ele.data("label") || nodeId;
2869
+ onExpandRequest({ nodeId, nodeLabel: label, x: pos.x, y: pos.y });
2870
+ } else {
2871
+ useGraphExploration.getState().expandNode(nodeId).catch((error) => {
2872
+ errorLog2("[CxtMenu] Expansion failed:", error);
2873
+ toast3.error("Failed to expand node");
2874
+ });
2659
2875
  }
2660
2876
  }
2661
2877
  }
2662
2878
  ],
2663
2879
  menuRadius: 60,
2664
- // Increased for more items (default is 100)
2665
2880
  fillColor: "rgba(0, 0, 0, 0.75)",
2666
2881
  activeFillColor: "rgba(59, 130, 246, 0.75)",
2667
2882
  // blue-500
@@ -2746,8 +2961,49 @@ var createEdgeContextMenu = ({
2746
2961
  };
2747
2962
 
2748
2963
  // src/components/cyto/CytoscapeGraph.tsx
2749
- import { toast as toast3 } from "sonner";
2750
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2964
+ import { toast as toast4 } from "sonner";
2965
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2966
+ function CytoscapeExpandOverlay({
2967
+ expandRequest,
2968
+ containerRef,
2969
+ useGraphExploration,
2970
+ getNodeEdgeTypes,
2971
+ onClose
2972
+ }) {
2973
+ return /* @__PURE__ */ jsx9(
2974
+ "div",
2975
+ {
2976
+ className: "absolute z-50",
2977
+ style: {
2978
+ left: Math.min(
2979
+ Math.max(expandRequest.x, 120),
2980
+ (containerRef.current?.clientWidth ?? 800) - 120
2981
+ ),
2982
+ top: Math.min(
2983
+ Math.max(expandRequest.y, 10),
2984
+ (containerRef.current?.clientHeight ?? 600) - 260
2985
+ ),
2986
+ transform: "translate(-50%, 12px)"
2987
+ },
2988
+ children: /* @__PURE__ */ jsx9(
2989
+ ExpandOptionsPanel_default,
2990
+ {
2991
+ nodeLabel: expandRequest.nodeLabel,
2992
+ onExpand: (options) => {
2993
+ const { nodeId } = expandRequest;
2994
+ onClose();
2995
+ useGraphExploration.getState().expandNode(nodeId, options).catch((err) => {
2996
+ errorLog3("[CxtMenu] Expansion failed:", err);
2997
+ toast4.error("Failed to expand node");
2998
+ });
2999
+ },
3000
+ onBack: onClose,
3001
+ getEdgeTypes: getNodeEdgeTypes ? () => getNodeEdgeTypes(Number(expandRequest.nodeId)) : void 0
3002
+ }
3003
+ )
3004
+ }
3005
+ );
3006
+ }
2751
3007
  try {
2752
3008
  cytoscape.use(fcose);
2753
3009
  cytoscape.use(cola);
@@ -2825,12 +3081,12 @@ function applyLayout({
2825
3081
  try {
2826
3082
  cy.layout(layoutOpts).run();
2827
3083
  } catch (e) {
2828
- errorLog2("Layout error, falling back to dagre:", e);
3084
+ errorLog3("Layout error, falling back to dagre:", e);
2829
3085
  if (chosen === "klay") {
2830
3086
  try {
2831
3087
  cy.layout(getPreset("dagre")).run();
2832
3088
  } catch (inner) {
2833
- errorLog2("Fallback layout also failed, using grid:", inner);
3089
+ errorLog3("Fallback layout also failed, using grid:", inner);
2834
3090
  cy.layout(getPreset("grid")).run();
2835
3091
  }
2836
3092
  } else {
@@ -3037,8 +3293,8 @@ function computeGraphViewState({
3037
3293
  };
3038
3294
  }
3039
3295
  function useFullscreen(fitToScreen, cyRef) {
3040
- const [fullscreen, setFullscreen] = useState4(false);
3041
- useEffect3(() => {
3296
+ const [fullscreen, setFullscreen] = useState6(false);
3297
+ useEffect4(() => {
3042
3298
  if (fullscreen) {
3043
3299
  const prevOverflow = document.body.style.overflow;
3044
3300
  document.body.style.overflow = "hidden";
@@ -3053,7 +3309,7 @@ function useFullscreen(fitToScreen, cyRef) {
3053
3309
  };
3054
3310
  }
3055
3311
  }, [fullscreen, fitToScreen]);
3056
- useEffect3(() => {
3312
+ useEffect4(() => {
3057
3313
  if (!fullscreen) return;
3058
3314
  const id = setTimeout(() => {
3059
3315
  if (cyRef.current) {
@@ -3066,7 +3322,7 @@ function useFullscreen(fitToScreen, cyRef) {
3066
3322
  return {
3067
3323
  fullscreen,
3068
3324
  setFullscreen,
3069
- toggleFullscreen: useCallback3(() => setFullscreen((f) => !f), [])
3325
+ toggleFullscreen: useCallback4(() => setFullscreen((f) => !f), [])
3070
3326
  };
3071
3327
  }
3072
3328
  function setupCytoscapeEventHandlers({
@@ -3078,7 +3334,8 @@ function setupCytoscapeEventHandlers({
3078
3334
  setSelectedEdgeStore,
3079
3335
  clearSelectionStore,
3080
3336
  useGraphExploration,
3081
- onCopyNode
3337
+ onCopyNode,
3338
+ onExpandRequest
3082
3339
  }) {
3083
3340
  const updateSelectedNodeIds = (nodeIds) => {
3084
3341
  setSelectedNodeIdsStore(nodeIds);
@@ -3166,22 +3423,31 @@ function setupCytoscapeEventHandlers({
3166
3423
  cy.on("tap", (evt) => {
3167
3424
  if (evt.target === cy) clearAll();
3168
3425
  });
3169
- createNodeContextMenu({ cy, useGraphExploration, onCopyNode });
3426
+ createNodeContextMenu({
3427
+ cy,
3428
+ useGraphExploration,
3429
+ onCopyNode,
3430
+ onExpandRequest
3431
+ });
3170
3432
  createEdgeContextMenu({ cy, useGraphExploration });
3171
3433
  }
3172
3434
  function CytoscapeGraph({
3173
3435
  layout: initialLayout = "auto",
3174
3436
  fitPadding = 30,
3175
3437
  onCopyNode,
3176
- extraToolbarContent
3438
+ extraToolbarContent,
3439
+ getNodeEdgeTypes
3177
3440
  }) {
3178
- const containerRef = useRef2(null);
3179
- const cyRef = useRef2(null);
3180
- const isInitializedRef = useRef2(false);
3181
- const [currentLayout, setCurrentLayout] = useState4(initialLayout);
3182
- const nodesRef = useRef2([]);
3183
- const edgesRef = useRef2([]);
3184
- const pendingFocusActionRef = useRef2(null);
3441
+ const containerRef = useRef3(null);
3442
+ const cyRef = useRef3(null);
3443
+ const isInitializedRef = useRef3(false);
3444
+ const [currentLayout, setCurrentLayout] = useState6(initialLayout);
3445
+ const [expandRequest, setExpandRequest] = useState6(
3446
+ null
3447
+ );
3448
+ const nodesRef = useRef3([]);
3449
+ const edgesRef = useRef3([]);
3450
+ const pendingFocusActionRef = useRef3(null);
3185
3451
  const cytoData = useCytoscapeData();
3186
3452
  const live = useLiveGraphData();
3187
3453
  const nodes = live.nodes;
@@ -3201,13 +3467,13 @@ function CytoscapeGraph({
3201
3467
  setSelectedEdge: setSelectedEdgeStore,
3202
3468
  clear: clearSelectionStore
3203
3469
  } = useSelection3.getState();
3204
- const selectedNodeIdsRef = useRef2(
3470
+ const selectedNodeIdsRef = useRef3(
3205
3471
  useSelection3.getState().selectedNodeIds
3206
3472
  );
3207
- const selectedEdgeRef = useRef2(
3473
+ const selectedEdgeRef = useRef3(
3208
3474
  useSelection3.getState().selectedEdge
3209
3475
  );
3210
- useEffect3(() => {
3476
+ useEffect4(() => {
3211
3477
  const state = useSelection3.getState();
3212
3478
  selectedNodeIdsRef.current = state.selectedNodeIds;
3213
3479
  selectedEdgeRef.current = state.selectedEdge;
@@ -3227,13 +3493,13 @@ function CytoscapeGraph({
3227
3493
  });
3228
3494
  }
3229
3495
  }, [useSelection3]);
3230
- useEffect3(() => {
3496
+ useEffect4(() => {
3231
3497
  nodesRef.current = nodes;
3232
3498
  }, [nodes]);
3233
- useEffect3(() => {
3499
+ useEffect4(() => {
3234
3500
  edgesRef.current = edges;
3235
3501
  }, [edges]);
3236
- useEffect3(() => {
3502
+ useEffect4(() => {
3237
3503
  if (!containerRef.current || cyRef.current) return;
3238
3504
  isInitializedRef.current = true;
3239
3505
  cyRef.current = cytoscape({
@@ -3384,7 +3650,8 @@ function CytoscapeGraph({
3384
3650
  setSelectedEdgeStore,
3385
3651
  clearSelectionStore,
3386
3652
  useGraphExploration,
3387
- onCopyNode
3653
+ onCopyNode,
3654
+ onExpandRequest: setExpandRequest
3388
3655
  });
3389
3656
  }
3390
3657
  return () => {
@@ -3392,10 +3659,10 @@ function CytoscapeGraph({
3392
3659
  cyRef.current = null;
3393
3660
  };
3394
3661
  }, []);
3395
- const elementIdsRef = useRef2("");
3396
- const lastLayoutRef = useRef2("");
3397
- const lastFitPaddingRef = useRef2(fitPadding);
3398
- useEffect3(() => {
3662
+ const elementIdsRef = useRef3("");
3663
+ const lastLayoutRef = useRef3("");
3664
+ const lastFitPaddingRef = useRef3(fitPadding);
3665
+ useEffect4(() => {
3399
3666
  const cy = cyRef.current;
3400
3667
  if (!cy || !isInitializedRef.current) return;
3401
3668
  const currentIds = nodes.map((n) => n.id_).sort().join(",") + "|" + edges.map((e) => e.id_).sort().join(",");
@@ -3468,12 +3735,12 @@ function CytoscapeGraph({
3468
3735
  });
3469
3736
  }
3470
3737
  }, [nodes, edges, currentLayout, fitPadding]);
3471
- useEffect3(() => {
3738
+ useEffect4(() => {
3472
3739
  if (!cyRef.current) return;
3473
3740
  cyRef.current.elements().unselect();
3474
3741
  clearSelectionStore();
3475
3742
  }, [lastDataChangeTime, clearSelectionStore]);
3476
- useEffect3(() => {
3743
+ useEffect4(() => {
3477
3744
  const unsubscribe = GraphFocus_default.subscribe((state) => {
3478
3745
  handleFocusStateChange({
3479
3746
  focusAction: state.focusAction,
@@ -3492,15 +3759,15 @@ function CytoscapeGraph({
3492
3759
  const updateLayout = (newLayout) => {
3493
3760
  setCurrentLayout(newLayout);
3494
3761
  };
3495
- const fitToScreen = useCallback3(() => {
3762
+ const fitToScreen = useCallback4(() => {
3496
3763
  if (cyRef.current) {
3497
3764
  cyRef.current.fit(void 0, fitPadding);
3498
3765
  }
3499
3766
  }, [fitPadding]);
3500
- useEffect3(() => {
3767
+ useEffect4(() => {
3501
3768
  fitToScreen();
3502
3769
  }, [nodes.length, fitToScreen]);
3503
- useEffect3(() => {
3770
+ useEffect4(() => {
3504
3771
  if (!isInitializedRef.current) return;
3505
3772
  const handleResize = () => cyRef.current?.resize();
3506
3773
  window.addEventListener("resize", handleResize);
@@ -3519,18 +3786,28 @@ function CytoscapeGraph({
3519
3786
  isFocusMode,
3520
3787
  fullscreen
3521
3788
  });
3522
- return /* @__PURE__ */ jsx8("div", { className: outerClass, children: /* @__PURE__ */ jsxs7("div", { className: innerClass, children: [
3523
- /* @__PURE__ */ jsx8("div", { ref: containerRef, className: "absolute inset-0" }),
3524
- showEmptyMessage && /* @__PURE__ */ jsx8("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)" }),
3525
- showFilteredMessage && /* @__PURE__ */ jsx8("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)' }),
3526
- showControls && /* @__PURE__ */ jsx8(
3789
+ return /* @__PURE__ */ jsx9("div", { className: outerClass, children: /* @__PURE__ */ jsxs8("div", { className: innerClass, children: [
3790
+ /* @__PURE__ */ jsx9("div", { ref: containerRef, className: "absolute inset-0" }),
3791
+ expandRequest && /* @__PURE__ */ jsx9(
3792
+ CytoscapeExpandOverlay,
3793
+ {
3794
+ expandRequest,
3795
+ containerRef,
3796
+ useGraphExploration,
3797
+ getNodeEdgeTypes,
3798
+ onClose: () => setExpandRequest(null)
3799
+ }
3800
+ ),
3801
+ 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)" }),
3802
+ 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)' }),
3803
+ showControls && /* @__PURE__ */ jsx9(
3527
3804
  CytoscapeControls_default,
3528
3805
  {
3529
3806
  updateLayout,
3530
3807
  fitToScreen,
3531
3808
  onResetView: () => {
3532
3809
  resetView();
3533
- toast3.success(
3810
+ toast4.success(
3534
3811
  isFocusMode ? "Focus mode cleared" : "Hidden elements restored"
3535
3812
  );
3536
3813
  },
@@ -3571,26 +3848,29 @@ function CytoscapeGraph({
3571
3848
  var CytoscapeGraph_default = CytoscapeGraph;
3572
3849
 
3573
3850
  // src/components/GraphVisualizer.tsx
3574
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3851
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3575
3852
  function GraphVisualizer({
3576
3853
  onCopyNode,
3577
- extraToolbarContent
3854
+ extraToolbarContent,
3855
+ getNodeEdgeTypes
3578
3856
  }) {
3579
3857
  const { renderer } = useGraphRenderer();
3580
- return /* @__PURE__ */ jsx9("div", { className: "relative w-full h-full flex flex-col overflow-hidden", children: /* @__PURE__ */ jsxs8("div", { className: "flex-1 h-full", children: [
3581
- renderer === "reagraph" && /* @__PURE__ */ jsx9(
3858
+ 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: [
3859
+ renderer === "reagraph" && /* @__PURE__ */ jsx10(
3582
3860
  ReaGraph_default,
3583
3861
  {
3584
3862
  onCopyNode,
3585
- extraToolbarContent
3863
+ extraToolbarContent,
3864
+ getNodeEdgeTypes
3586
3865
  },
3587
3866
  "reagraph"
3588
3867
  ),
3589
- renderer === "cytoscape" && /* @__PURE__ */ jsx9(
3868
+ renderer === "cytoscape" && /* @__PURE__ */ jsx10(
3590
3869
  CytoscapeGraph_default,
3591
3870
  {
3592
3871
  onCopyNode,
3593
- extraToolbarContent
3872
+ extraToolbarContent,
3873
+ getNodeEdgeTypes
3594
3874
  },
3595
3875
  "cytoscape"
3596
3876
  )
@@ -3602,18 +3882,19 @@ var GraphVisualizer_default = GraphVisualizer;
3602
3882
  import { Card as Card5 } from "@petrarca/sonnet-ui";
3603
3883
 
3604
3884
  // src/components/ActionPanel/NodeInfoCard.tsx
3605
- import { useState as useState5 } from "react";
3885
+ import { useState as useState7 } from "react";
3606
3886
  import {
3607
3887
  Card as Card3,
3608
3888
  Collapsible,
3609
3889
  CollapsibleContent,
3610
- CollapsibleTrigger
3890
+ CollapsibleTrigger,
3891
+ PropertyList
3611
3892
  } from "@petrarca/sonnet-ui";
3612
3893
  import { copyTextToClipboard } from "@petrarca/sonnet-core";
3613
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3894
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3614
3895
  function NodeInfoCard({ node }) {
3615
- const [showProperties, setShowProperties] = useState5(false);
3616
- return /* @__PURE__ */ jsxs9(
3896
+ const [showProperties, setShowProperties] = useState7(false);
3897
+ return /* @__PURE__ */ jsxs10(
3617
3898
  Card3,
3618
3899
  {
3619
3900
  className: "w-full rounded-lg overflow-hidden bg-muted/30 border shadow-none p-3",
@@ -3621,9 +3902,9 @@ function NodeInfoCard({ node }) {
3621
3902
  borderLeft: `4px solid ${node.visual.color}`
3622
3903
  },
3623
3904
  children: [
3624
- /* @__PURE__ */ jsx10("p", { className: "text-sm leading-tight font-medium mb-0", children: node.label_ }),
3625
- /* @__PURE__ */ jsx10("p", { className: "text-sm leading-tight mb-1", children: node.visual.displayName }),
3626
- /* @__PURE__ */ jsx10(
3905
+ /* @__PURE__ */ jsx11("p", { className: "text-sm leading-tight font-medium mb-0", children: node.label_ }),
3906
+ /* @__PURE__ */ jsx11("p", { className: "text-sm leading-tight mb-1", children: node.visual.displayName }),
3907
+ /* @__PURE__ */ jsx11(
3627
3908
  "p",
3628
3909
  {
3629
3910
  className: "text-xs text-muted-foreground cursor-pointer select-all mb-2",
@@ -3632,23 +3913,9 @@ function NodeInfoCard({ node }) {
3632
3913
  children: node.id_
3633
3914
  }
3634
3915
  ),
3635
- /* @__PURE__ */ jsxs9(Collapsible, { open: showProperties, onOpenChange: setShowProperties, children: [
3636
- /* @__PURE__ */ jsx10(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsx10("button", { className: "text-xs cursor-pointer", children: showProperties ? "Hide" : "Show more" }) }),
3637
- /* @__PURE__ */ jsx10(CollapsibleContent, { children: /* @__PURE__ */ jsxs9("ul", { className: "list-none mt-2", children: [
3638
- /* @__PURE__ */ jsx10("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-1", children: "Properties" }),
3639
- Object.entries(node.properties_ || {}).map(([key, value]) => /* @__PURE__ */ jsxs9("li", { className: "mb-2 leading-tight", children: [
3640
- /* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground uppercase tracking-wide", children: key }),
3641
- /* @__PURE__ */ jsx10(
3642
- "p",
3643
- {
3644
- className: "text-sm truncate max-w-[190px] cursor-pointer",
3645
- onClick: () => copyTextToClipboard(String(value)),
3646
- title: "Click to copy value",
3647
- children: String(value)
3648
- }
3649
- )
3650
- ] }, key))
3651
- ] }) })
3916
+ /* @__PURE__ */ jsxs10(Collapsible, { open: showProperties, onOpenChange: setShowProperties, children: [
3917
+ /* @__PURE__ */ jsx11(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsx11("button", { className: "text-xs cursor-pointer", children: showProperties ? "Hide" : "Show more" }) }),
3918
+ /* @__PURE__ */ jsx11(CollapsibleContent, { children: /* @__PURE__ */ jsx11("div", { className: "mt-2", children: /* @__PURE__ */ jsx11(PropertyList, { properties: node.properties_ || {}, showHeader: true }) }) })
3652
3919
  ] })
3653
3920
  ]
3654
3921
  }
@@ -3657,19 +3924,20 @@ function NodeInfoCard({ node }) {
3657
3924
  var NodeInfoCard_default = NodeInfoCard;
3658
3925
 
3659
3926
  // src/components/ActionPanel/EdgeInfoCard.tsx
3660
- import { useState as useState6 } from "react";
3927
+ import { useState as useState8 } from "react";
3661
3928
  import {
3662
3929
  Card as Card4,
3663
3930
  Collapsible as Collapsible2,
3664
3931
  CollapsibleContent as CollapsibleContent2,
3665
- CollapsibleTrigger as CollapsibleTrigger2
3932
+ CollapsibleTrigger as CollapsibleTrigger2,
3933
+ PropertyList as PropertyList2
3666
3934
  } from "@petrarca/sonnet-ui";
3667
3935
  import { ArrowDown } from "lucide-react";
3668
3936
  import { copyTextToClipboard as copyTextToClipboard2 } from "@petrarca/sonnet-core";
3669
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3937
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3670
3938
  function EdgeInfoCard({ edge }) {
3671
- const [showProperties, setShowProperties] = useState6(false);
3672
- return /* @__PURE__ */ jsxs10(
3939
+ const [showProperties, setShowProperties] = useState8(false);
3940
+ return /* @__PURE__ */ jsxs11(
3673
3941
  Card4,
3674
3942
  {
3675
3943
  className: "w-full rounded-lg overflow-hidden bg-muted/30 border shadow-none p-3",
@@ -3677,11 +3945,11 @@ function EdgeInfoCard({ edge }) {
3677
3945
  borderLeft: `4px solid ${edge.visual.color}`
3678
3946
  },
3679
3947
  children: [
3680
- /* @__PURE__ */ jsxs10("p", { className: "text-sm leading-tight mb-1", children: [
3681
- /* @__PURE__ */ jsx11(ArrowDown, { className: "inline-block mr-1 opacity-70 align-middle h-3.5 w-3.5" }),
3682
- /* @__PURE__ */ jsx11("strong", { className: "font-medium align-middle", children: edge.label_ })
3948
+ /* @__PURE__ */ jsxs11("p", { className: "text-sm leading-tight mb-1", children: [
3949
+ /* @__PURE__ */ jsx12(ArrowDown, { className: "inline-block mr-1 opacity-70 align-middle h-3.5 w-3.5" }),
3950
+ /* @__PURE__ */ jsx12("strong", { className: "font-medium align-middle", children: edge.label_ })
3683
3951
  ] }),
3684
- edge.id_ && /* @__PURE__ */ jsx11(
3952
+ edge.id_ && /* @__PURE__ */ jsx12(
3685
3953
  "p",
3686
3954
  {
3687
3955
  className: "text-xs text-muted-foreground select-all cursor-pointer mt-1 mb-2",
@@ -3690,23 +3958,9 @@ function EdgeInfoCard({ edge }) {
3690
3958
  children: edge.id_
3691
3959
  }
3692
3960
  ),
3693
- edge.properties_ && Object.keys(edge.properties_).length > 0 && /* @__PURE__ */ jsxs10(Collapsible2, { open: showProperties, onOpenChange: setShowProperties, children: [
3694
- /* @__PURE__ */ jsx11(CollapsibleTrigger2, { asChild: true, children: /* @__PURE__ */ jsx11("button", { className: "text-xs cursor-pointer", children: showProperties ? "Hide" : "Show more" }) }),
3695
- /* @__PURE__ */ jsx11(CollapsibleContent2, { children: /* @__PURE__ */ jsxs10("ul", { className: "list-none mt-2", children: [
3696
- /* @__PURE__ */ jsx11("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-1", children: "Properties" }),
3697
- Object.entries(edge.properties_ || {}).map(([key, value]) => /* @__PURE__ */ jsxs10("li", { className: "mb-2 leading-tight", children: [
3698
- /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground uppercase tracking-wide", children: key }),
3699
- /* @__PURE__ */ jsx11(
3700
- "p",
3701
- {
3702
- className: "text-sm truncate max-w-[190px] cursor-pointer",
3703
- onClick: () => copyTextToClipboard2(String(value)),
3704
- title: "Click to copy value",
3705
- children: String(value)
3706
- }
3707
- )
3708
- ] }, key))
3709
- ] }) })
3961
+ edge.properties_ && Object.keys(edge.properties_).length > 0 && /* @__PURE__ */ jsxs11(Collapsible2, { open: showProperties, onOpenChange: setShowProperties, children: [
3962
+ /* @__PURE__ */ jsx12(CollapsibleTrigger2, { asChild: true, children: /* @__PURE__ */ jsx12("button", { className: "text-xs cursor-pointer", children: showProperties ? "Hide" : "Show more" }) }),
3963
+ /* @__PURE__ */ jsx12(CollapsibleContent2, { children: /* @__PURE__ */ jsx12("div", { className: "mt-2", children: /* @__PURE__ */ jsx12(PropertyList2, { properties: edge.properties_ || {}, showHeader: true }) }) })
3710
3964
  ] })
3711
3965
  ]
3712
3966
  }
@@ -3718,7 +3972,7 @@ var EdgeInfoCard_default = EdgeInfoCard;
3718
3972
  import { Badge } from "@petrarca/sonnet-ui";
3719
3973
  import { formatNumber, devLog as devLog11 } from "@petrarca/sonnet-core";
3720
3974
  import { useMemo as useMemo4 } from "react";
3721
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3975
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
3722
3976
  function computeEdgeToNodeLabels(nodesById, edgesById) {
3723
3977
  const mapping = {};
3724
3978
  for (const edge of Object.values(edgesById)) {
@@ -3751,7 +4005,7 @@ function AllToggleBadge({
3751
4005
  badgeKey
3752
4006
  }) {
3753
4007
  const bgColor = allDisabled ? colorPalette["faded"] : colorPalette["neutral"];
3754
- return /* @__PURE__ */ jsxs11(
4008
+ return /* @__PURE__ */ jsxs12(
3755
4009
  Badge,
3756
4010
  {
3757
4011
  className: "cursor-pointer text-xs px-2.5 py-0.5",
@@ -3786,7 +4040,7 @@ function NodeLabelBadges({
3786
4040
  colorPalette,
3787
4041
  label
3788
4042
  );
3789
- return /* @__PURE__ */ jsxs11(
4043
+ return /* @__PURE__ */ jsxs12(
3790
4044
  Badge,
3791
4045
  {
3792
4046
  className: "cursor-pointer text-xs px-2.5 py-0.5",
@@ -3824,7 +4078,7 @@ function EdgeLabelBadges({
3824
4078
  colorPalette,
3825
4079
  "neutral"
3826
4080
  );
3827
- return /* @__PURE__ */ jsxs11(
4081
+ return /* @__PURE__ */ jsxs12(
3828
4082
  Badge,
3829
4083
  {
3830
4084
  className: "cursor-pointer text-xs px-2.5 py-0.5",
@@ -3901,12 +4155,12 @@ function GraphOverview({
3901
4155
  toggleAllEdgeLabelsDisabled();
3902
4156
  }
3903
4157
  };
3904
- return /* @__PURE__ */ jsxs11(Fragment3, { children: [
3905
- /* @__PURE__ */ jsx12("p", { className: "mt-2 text-sm font-semibold", children: "Overview" }),
3906
- allLabels.length > 0 && /* @__PURE__ */ jsxs11("div", { children: [
3907
- /* @__PURE__ */ jsx12("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2", children: "Node Labels" }),
3908
- /* @__PURE__ */ jsxs11("div", { className: "flex flex-col items-start gap-2", children: [
3909
- /* @__PURE__ */ jsx12(
4158
+ return /* @__PURE__ */ jsxs12(Fragment3, { children: [
4159
+ /* @__PURE__ */ jsx13("p", { className: "mt-2 text-sm font-semibold", children: "Overview" }),
4160
+ allLabels.length > 0 && /* @__PURE__ */ jsxs12("div", { children: [
4161
+ /* @__PURE__ */ jsx13("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2", children: "Node Labels" }),
4162
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col items-start gap-2", children: [
4163
+ /* @__PURE__ */ jsx13(
3910
4164
  AllToggleBadge,
3911
4165
  {
3912
4166
  allDisabled: allLabelsDisabled,
@@ -3916,7 +4170,7 @@ function GraphOverview({
3916
4170
  badgeKey: "all"
3917
4171
  }
3918
4172
  ),
3919
- /* @__PURE__ */ jsx12(
4173
+ /* @__PURE__ */ jsx13(
3920
4174
  NodeLabelBadges,
3921
4175
  {
3922
4176
  labels: allLabels,
@@ -3929,10 +4183,10 @@ function GraphOverview({
3929
4183
  )
3930
4184
  ] })
3931
4185
  ] }),
3932
- allEdgeLabels.length > 0 && /* @__PURE__ */ jsxs11("div", { children: [
3933
- /* @__PURE__ */ jsx12("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2", children: "Edge Types" }),
3934
- /* @__PURE__ */ jsxs11("div", { className: "flex flex-col items-start gap-2", children: [
3935
- /* @__PURE__ */ jsx12(
4186
+ allEdgeLabels.length > 0 && /* @__PURE__ */ jsxs12("div", { children: [
4187
+ /* @__PURE__ */ jsx13("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2", children: "Edge Types" }),
4188
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col items-start gap-2", children: [
4189
+ /* @__PURE__ */ jsx13(
3936
4190
  AllToggleBadge,
3937
4191
  {
3938
4192
  allDisabled: allEdgeLabelsDisabled,
@@ -3942,7 +4196,7 @@ function GraphOverview({
3942
4196
  badgeKey: "edge-all"
3943
4197
  }
3944
4198
  ),
3945
- /* @__PURE__ */ jsx12(
4199
+ /* @__PURE__ */ jsx13(
3946
4200
  EdgeLabelBadges,
3947
4201
  {
3948
4202
  labels: allEdgeLabels,
@@ -3968,16 +4222,16 @@ function Overview() {
3968
4222
  );
3969
4223
  }, [baseNormalized]);
3970
4224
  if (stats && stats.values != null && stats.values > 0) {
3971
- return /* @__PURE__ */ jsxs11(Fragment3, { children: [
3972
- /* @__PURE__ */ jsx12("p", { className: "mt-2 text-sm font-semibold", children: "Overview" }),
3973
- /* @__PURE__ */ jsxs11("p", { className: "text-sm text-muted-foreground", children: [
4225
+ return /* @__PURE__ */ jsxs12(Fragment3, { children: [
4226
+ /* @__PURE__ */ jsx13("p", { className: "mt-2 text-sm font-semibold", children: "Overview" }),
4227
+ /* @__PURE__ */ jsxs12("p", { className: "text-sm text-muted-foreground", children: [
3974
4228
  formatNumber(stats.values),
3975
4229
  " Values"
3976
4230
  ] })
3977
4231
  ] });
3978
4232
  }
3979
4233
  if (stats && stats.nodes > 0 && colorPalette) {
3980
- return /* @__PURE__ */ jsx12(
4234
+ return /* @__PURE__ */ jsx13(
3981
4235
  GraphOverview,
3982
4236
  {
3983
4237
  stats,
@@ -3994,7 +4248,7 @@ var Overview_default = Overview;
3994
4248
 
3995
4249
  // src/components/ActionPanel/ActionPanel.tsx
3996
4250
  import { useMemo as useMemo5 } from "react";
3997
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
4251
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
3998
4252
  function ActionPanel({ actions }) {
3999
4253
  const useSelection3 = useWorkspaceSelection();
4000
4254
  const selectedNodeIds = useSelection3(
@@ -4010,18 +4264,18 @@ function ActionPanel({ actions }) {
4010
4264
  const secondNode = selectedNodes?.[1];
4011
4265
  const hasEdge = !!selectedEdge;
4012
4266
  const showOverview = !firstNode && !hasEdge;
4013
- return /* @__PURE__ */ jsxs12(
4267
+ return /* @__PURE__ */ jsxs13(
4014
4268
  Card5,
4015
4269
  {
4016
4270
  className: "h-full min-w-64 flex flex-col bg-card shadow-none p-4",
4017
4271
  style: { maxHeight: "100%", overflow: "hidden" },
4018
4272
  children: [
4019
- actions && /* @__PURE__ */ jsx13("div", { className: "flex-shrink-0 mb-2", children: actions }),
4020
- /* @__PURE__ */ jsx13("div", { className: "flex-1 overflow-auto pr-1", children: /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-3", children: [
4021
- firstNode && /* @__PURE__ */ jsx13(NodeInfoCard_default, { node: firstNode }),
4022
- hasEdge && selectedEdge && /* @__PURE__ */ jsx13(EdgeInfoCard_default, { edge: selectedEdge }),
4023
- secondNode && /* @__PURE__ */ jsx13(NodeInfoCard_default, { node: secondNode }),
4024
- showOverview && /* @__PURE__ */ jsx13(Overview_default, {})
4273
+ actions && /* @__PURE__ */ jsx14("div", { className: "flex-shrink-0 mb-2", children: actions }),
4274
+ /* @__PURE__ */ jsx14("div", { className: "flex-1 overflow-auto pr-1", children: /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-3", children: [
4275
+ firstNode && /* @__PURE__ */ jsx14(NodeInfoCard_default, { node: firstNode }),
4276
+ hasEdge && selectedEdge && /* @__PURE__ */ jsx14(EdgeInfoCard_default, { edge: selectedEdge }),
4277
+ secondNode && /* @__PURE__ */ jsx14(NodeInfoCard_default, { node: secondNode }),
4278
+ showOverview && /* @__PURE__ */ jsx14(Overview_default, {})
4025
4279
  ] }) })
4026
4280
  ]
4027
4281
  }
@@ -4030,10 +4284,10 @@ function ActionPanel({ actions }) {
4030
4284
  var ActionPanel_default = ActionPanel;
4031
4285
 
4032
4286
  // src/components/GraphA11yList/GraphA11yList.tsx
4033
- import { useMemo as useMemo6, useRef as useRef3 } from "react";
4287
+ import { useMemo as useMemo6, useRef as useRef4 } from "react";
4034
4288
  import { useVirtualizer } from "@tanstack/react-virtual";
4035
4289
  import { cn as cn2 } from "@petrarca/sonnet-core";
4036
- import { jsx as jsx14 } from "react/jsx-runtime";
4290
+ import { jsx as jsx15 } from "react/jsx-runtime";
4037
4291
  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";
4038
4292
  function GraphA11yList({
4039
4293
  visible = false,
@@ -4068,7 +4322,7 @@ function GraphA11yList({
4068
4322
  }));
4069
4323
  return [...nodeRows, ...edgeRows];
4070
4324
  }, [live.nodes, live.edges]);
4071
- const parentRef = useRef3(null);
4325
+ const parentRef = useRef4(null);
4072
4326
  const virtualizer = useVirtualizer({
4073
4327
  count: rows.length,
4074
4328
  getScrollElement: () => parentRef.current,
@@ -4085,18 +4339,18 @@ function GraphA11yList({
4085
4339
  }
4086
4340
  };
4087
4341
  const isSelected = (row) => row.kind === "node" ? selectedNodeIds.includes(row.id) : selectedEdge?.id_ === row.edge.id_;
4088
- return /* @__PURE__ */ jsx14(
4342
+ return /* @__PURE__ */ jsx15(
4089
4343
  "div",
4090
4344
  {
4091
4345
  role: "listbox",
4092
4346
  "aria-label": "Graph elements",
4093
4347
  className: cn2(visible ? "rounded-md border" : SR_ONLY, className),
4094
- children: /* @__PURE__ */ jsx14(
4348
+ children: /* @__PURE__ */ jsx15(
4095
4349
  "div",
4096
4350
  {
4097
4351
  ref: parentRef,
4098
4352
  style: { maxHeight: visible ? maxHeight : void 0, overflow: "auto" },
4099
- children: /* @__PURE__ */ jsx14(
4353
+ children: /* @__PURE__ */ jsx15(
4100
4354
  "div",
4101
4355
  {
4102
4356
  style: {
@@ -4108,7 +4362,7 @@ function GraphA11yList({
4108
4362
  const row = rows[vi.index];
4109
4363
  if (!row) return null;
4110
4364
  const selected = isSelected(row);
4111
- return /* @__PURE__ */ jsx14(
4365
+ return /* @__PURE__ */ jsx15(
4112
4366
  "button",
4113
4367
  {
4114
4368
  type: "button",
@@ -4126,7 +4380,7 @@ function GraphA11yList({
4126
4380
  height: `${vi.size}px`,
4127
4381
  transform: `translateY(${vi.start}px)`
4128
4382
  },
4129
- children: /* @__PURE__ */ jsx14("span", { className: "truncate", children: row.label })
4383
+ children: /* @__PURE__ */ jsx15("span", { className: "truncate", children: row.label })
4130
4384
  },
4131
4385
  vi.key
4132
4386
  );