@petrarca/sonnet-graph 0.8.0 → 0.9.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
@@ -1681,7 +1681,8 @@ import {
1681
1681
  useEffect as useEffect4,
1682
1682
  useRef as useRef4,
1683
1683
  useState as useState6,
1684
- useCallback as useCallback5
1684
+ useCallback as useCallback5,
1685
+ useMemo as useMemo4
1685
1686
  } from "react";
1686
1687
  import { devLog as devLog8 } from "@petrarca/sonnet-core";
1687
1688
  import {
@@ -1690,38 +1691,87 @@ import {
1690
1691
  } from "reagraph";
1691
1692
 
1692
1693
  // src/components/rea/theme.ts
1693
- import { lightTheme } from "reagraph";
1694
+ import { darkTheme, lightTheme } from "reagraph";
1694
1695
  var highlightColor = SELECTION_COLOR;
1695
- var highlightColorText = "#1d4ed8";
1696
- var theme = {
1697
- ...lightTheme,
1698
- node: {
1699
- ...lightTheme.node,
1700
- activeFill: highlightColor,
1701
- label: {
1702
- ...lightTheme.node.label,
1703
- activeColor: highlightColorText
1704
- }
1705
- },
1706
- ring: {
1707
- ...lightTheme.ring,
1708
- activeFill: highlightColor
1709
- },
1710
- edge: {
1711
- ...lightTheme.edge,
1712
- activeFill: highlightColor,
1713
- label: {
1714
- ...lightTheme.edge.label,
1715
- activeColor: highlightColorText
1696
+ var HIGHLIGHT_TEXT = { light: "#1d4ed8", dark: "#93c5fd" };
1697
+ function reaTheme(dark, colors) {
1698
+ const base = dark ? darkTheme : lightTheme;
1699
+ const highlightColorText = dark ? HIGHLIGHT_TEXT.dark : HIGHLIGHT_TEXT.light;
1700
+ return {
1701
+ ...base,
1702
+ canvas: { ...base.canvas, background: colors.background },
1703
+ node: {
1704
+ ...base.node,
1705
+ activeFill: highlightColor,
1706
+ label: {
1707
+ ...base.node.label,
1708
+ color: colors.text,
1709
+ stroke: colors.background,
1710
+ activeColor: highlightColorText
1711
+ }
1712
+ },
1713
+ ring: {
1714
+ ...base.ring,
1715
+ activeFill: highlightColor
1716
+ },
1717
+ edge: {
1718
+ ...base.edge,
1719
+ activeFill: highlightColor,
1720
+ label: {
1721
+ ...base.edge.label,
1722
+ color: colors.text,
1723
+ stroke: colors.background,
1724
+ activeColor: highlightColorText
1725
+ }
1726
+ },
1727
+ arrow: {
1728
+ ...base.arrow,
1729
+ activeFill: highlightColor
1716
1730
  }
1717
- },
1718
- arrow: {
1719
- ...lightTheme.arrow,
1720
- activeFill: highlightColor
1721
- }
1731
+ };
1732
+ }
1733
+
1734
+ // src/graph/canvasColors.ts
1735
+ var FALLBACK = {
1736
+ background: "#ffffff",
1737
+ text: "#020817",
1738
+ labelBackground: "#f1f5f9",
1739
+ raised: "#39445a"
1722
1740
  };
1741
+ function hslToHex(triple) {
1742
+ const match = triple.trim().match(/^(-?[\d.]+)(?:deg)?\s+([\d.]+)%\s+([\d.]+)%$/);
1743
+ if (!match) return void 0;
1744
+ const [h, s, l] = [
1745
+ Number(match[1]),
1746
+ Number(match[2]) / 100,
1747
+ Number(match[3]) / 100
1748
+ ];
1749
+ const a = s * Math.min(l, 1 - l);
1750
+ const channel = (n) => {
1751
+ const k = (n + h / 30) % 12;
1752
+ const value = l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
1753
+ return Math.round(value * 255).toString(16).padStart(2, "0");
1754
+ };
1755
+ return `#${channel(0)}${channel(8)}${channel(4)}`;
1756
+ }
1757
+ function withAlpha(hex, alpha) {
1758
+ const [r, g, b] = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
1759
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
1760
+ }
1761
+ function canvasColors(theme) {
1762
+ const style = getComputedStyle(document.documentElement);
1763
+ const token = (name, fallback) => hslToHex(style.getPropertyValue(name)) ?? fallback;
1764
+ const text = token("--foreground", FALLBACK.text);
1765
+ return {
1766
+ background: token("--background", FALLBACK.background),
1767
+ text,
1768
+ labelBackground: token("--muted", FALLBACK.labelBackground),
1769
+ menu: theme === "dark" ? withAlpha(token("--border-strong", FALLBACK.raised), 0.95) : withAlpha(text, 0.75)
1770
+ };
1771
+ }
1723
1772
 
1724
1773
  // src/components/rea/ReaGraph.tsx
1774
+ import { useTheme } from "@petrarca/sonnet-core/hooks";
1725
1775
  import { cn } from "@petrarca/sonnet-core";
1726
1776
 
1727
1777
  // src/components/rea/LayoutAndCameraControls.tsx
@@ -1876,7 +1926,7 @@ import { Card as Card2 } from "@petrarca/sonnet-ui";
1876
1926
  import { Card, Separator } from "@petrarca/sonnet-ui";
1877
1927
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1878
1928
  function NodeHoverInfo({ node }) {
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: [
1929
+ return /* @__PURE__ */ jsx6(Card, { className: "absolute top-2 left-1/2 -translate-x-1/2 bg-card shadow-md p-2", children: /* @__PURE__ */ jsxs5("div", { className: "flex flex-row items-center gap-2", children: [
1880
1930
  /* @__PURE__ */ jsx6(
1881
1931
  Separator,
1882
1932
  {
@@ -2312,6 +2362,11 @@ function ReaGraph({
2312
2362
  if (edges.length > 0) devLog8("[ReGraph] sample edge", edges[0]);
2313
2363
  }, [nodes, edges]);
2314
2364
  const canvas = useRef4(null);
2365
+ const { theme: pageTheme } = useTheme();
2366
+ const theme = useMemo4(
2367
+ () => reaTheme(pageTheme === "dark", canvasColors(pageTheme)),
2368
+ [pageTheme]
2369
+ );
2315
2370
  const [hoveredNode, setHoveredNode] = useState6(
2316
2371
  void 0
2317
2372
  );
@@ -2581,7 +2636,7 @@ function ReaGraph({
2581
2636
  "div",
2582
2637
  {
2583
2638
  className: cn(
2584
- "bg-white",
2639
+ "bg-background",
2585
2640
  fullscreen ? "fixed inset-0 z-50 w-screen h-screen p-0 m-0" : "relative w-full h-full"
2586
2641
  ),
2587
2642
  children: [
@@ -2658,6 +2713,7 @@ import {
2658
2713
  useCallback as useCallback6
2659
2714
  } from "react";
2660
2715
  import { devLog as devLog10, errorLog as errorLog3 } from "@petrarca/sonnet-core";
2716
+ import { useTheme as useTheme2 } from "@petrarca/sonnet-core/hooks";
2661
2717
  import cytoscape from "cytoscape";
2662
2718
  import fcose from "cytoscape-fcose";
2663
2719
  import cola from "cytoscape-cola";
@@ -2765,7 +2821,7 @@ function CytoscapeControls({
2765
2821
  onResetView,
2766
2822
  extraToolbarContent
2767
2823
  }) {
2768
- return /* @__PURE__ */ jsxs9("div", { className: "absolute right-2 top-2 flex flex-col border bg-white rounded-md shadow-sm z-10", children: [
2824
+ return /* @__PURE__ */ jsxs9("div", { className: "absolute right-2 top-2 flex flex-col border bg-background rounded-md shadow-sm z-10", children: [
2769
2825
  /* @__PURE__ */ jsx10(RendererDropdown, {}),
2770
2826
  /* @__PURE__ */ jsx10(
2771
2827
  ViewControls2,
@@ -3067,7 +3123,8 @@ var createNodeContextMenu = ({
3067
3123
  cy,
3068
3124
  useGraphExploration,
3069
3125
  onCopyNode,
3070
- onExpandRequest
3126
+ onExpandRequest,
3127
+ fillColor
3071
3128
  }) => {
3072
3129
  devLog9("[CxtMenu] Initializing node context menu");
3073
3130
  return cy.cxtmenu({
@@ -3136,7 +3193,7 @@ var createNodeContextMenu = ({
3136
3193
  }
3137
3194
  ],
3138
3195
  menuRadius: 60,
3139
- fillColor: "rgba(0, 0, 0, 0.75)",
3196
+ fillColor,
3140
3197
  activeFillColor: "rgba(59, 130, 246, 0.75)",
3141
3198
  // blue-500
3142
3199
  activePadding: 10,
@@ -3155,7 +3212,8 @@ var createNodeContextMenu = ({
3155
3212
  };
3156
3213
  var createEdgeContextMenu = ({
3157
3214
  cy,
3158
- useGraphExploration
3215
+ useGraphExploration,
3216
+ fillColor
3159
3217
  }) => {
3160
3218
  return cy.cxtmenu({
3161
3219
  selector: "edge",
@@ -3204,7 +3262,7 @@ var createEdgeContextMenu = ({
3204
3262
  ],
3205
3263
  menuRadius: 90,
3206
3264
  // Increased for more items
3207
- fillColor: "rgba(0, 0, 0, 0.75)",
3265
+ fillColor,
3208
3266
  activeFillColor: "rgba(239, 68, 68, 0.75)",
3209
3267
  // red-500
3210
3268
  activePadding: 10,
@@ -3221,6 +3279,139 @@ var createEdgeContextMenu = ({
3221
3279
 
3222
3280
  // src/components/cyto/CytoscapeGraph.tsx
3223
3281
  import { toast as toast4 } from "sonner";
3282
+
3283
+ // src/components/cyto/stylesheet.ts
3284
+ function graphStylesheet(colors) {
3285
+ return [
3286
+ // Base node style with data mappers for dynamic properties
3287
+ {
3288
+ selector: "node",
3289
+ style: {
3290
+ "background-color": "data(color)",
3291
+ // Read color from element data
3292
+ "background-opacity": 0.9,
3293
+ width: "data(size)",
3294
+ // Read size from element data
3295
+ height: "data(size)",
3296
+ shape: "ellipse",
3297
+ label: "data(label)",
3298
+ // Label text on a chip, in the theme's colours
3299
+ color: colors.text,
3300
+ "font-size": "11px",
3301
+ // Increased from 10px
3302
+ "font-weight": "500",
3303
+ // medium: legible on the chip without shouting
3304
+ "text-valign": "bottom",
3305
+ "text-halign": "center",
3306
+ "text-wrap": "wrap",
3307
+ "text-max-width": "120px",
3308
+ // Increased from 100px for longer labels
3309
+ "text-margin-y": 5,
3310
+ "text-background-color": colors.labelBackground,
3311
+ "text-background-opacity": 1,
3312
+ "text-background-padding": "3px",
3313
+ "text-background-shape": "roundrectangle",
3314
+ "border-width": 1,
3315
+ "border-color": colors.background,
3316
+ "border-opacity": 0.8
3317
+ }
3318
+ },
3319
+ // Override background color only when fill data is present
3320
+ {
3321
+ selector: "node[fill]",
3322
+ style: {
3323
+ "background-color": "data(fill)"
3324
+ }
3325
+ },
3326
+ {
3327
+ selector: "edge",
3328
+ style: {
3329
+ width: "data(size)",
3330
+ // Read width from element data
3331
+ "line-color": "data(color)",
3332
+ // Read color from element data
3333
+ "target-arrow-color": "data(color)",
3334
+ "target-arrow-shape": "triangle",
3335
+ "curve-style": "bezier",
3336
+ "control-point-step-size": 40,
3337
+ "control-point-weight": 0.5,
3338
+ opacity: 0.8,
3339
+ "arrow-scale": 0.8,
3340
+ "mid-target-arrow-shape": "none",
3341
+ "source-endpoint": "outside-to-node-or-label",
3342
+ "target-endpoint": "outside-to-node-or-label"
3343
+ }
3344
+ },
3345
+ // Dashed edges (when dashed property is true)
3346
+ {
3347
+ selector: "edge[dashed]",
3348
+ style: {
3349
+ "line-style": "dashed"
3350
+ }
3351
+ },
3352
+ // Show edge labels by default with good styling
3353
+ {
3354
+ selector: "edge[label]",
3355
+ style: {
3356
+ label: "data(label)",
3357
+ "font-size": "10px",
3358
+ // Increased from 8px for better readability
3359
+ "font-weight": "500",
3360
+ // Semi-bold for better visibility
3361
+ "text-background-color": colors.background,
3362
+ "text-background-opacity": 0.9,
3363
+ // Increased from 0.8 for better contrast
3364
+ "text-background-padding": "3px",
3365
+ // Increased from 2px
3366
+ "text-background-shape": "roundrectangle",
3367
+ "text-rotation": "autorotate",
3368
+ "text-margin-y": -8,
3369
+ // Adjusted for larger font
3370
+ color: colors.text
3371
+ }
3372
+ },
3373
+ {
3374
+ selector: "edge:selected",
3375
+ style: {
3376
+ "line-color": SELECTION_COLOR,
3377
+ "target-arrow-color": SELECTION_COLOR,
3378
+ // Modest bump over the base width (~3); selection is conveyed mainly
3379
+ // by colour + overlay, not a slab. (Was 8, which looked oversized.)
3380
+ width: 4,
3381
+ "z-index": 999,
3382
+ "font-size": "11px",
3383
+ "font-weight": "bold",
3384
+ "text-background-opacity": 1,
3385
+ "text-background-color": colors.background,
3386
+ "text-background-padding": "3px",
3387
+ "arrow-scale": 0.9,
3388
+ opacity: 1,
3389
+ "overlay-color": SELECTION_COLOR,
3390
+ "overlay-opacity": 0.08
3391
+ }
3392
+ },
3393
+ {
3394
+ selector: "node:selected",
3395
+ style: {
3396
+ "border-width": 10,
3397
+ // thicker circle outline
3398
+ "border-color": SELECTION_COLOR,
3399
+ "border-opacity": 1,
3400
+ "background-opacity": 1,
3401
+ "text-background-opacity": 1,
3402
+ "text-background-color": colors.background,
3403
+ "text-background-padding": "4px",
3404
+ "overlay-color": SELECTION_COLOR,
3405
+ "overlay-opacity": 0.18,
3406
+ width: 48,
3407
+ // slight size bump from 40
3408
+ height: 48
3409
+ }
3410
+ }
3411
+ ];
3412
+ }
3413
+
3414
+ // src/components/cyto/CytoscapeGraph.tsx
3224
3415
  import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3225
3416
  function CytoscapeExpandOverlay({
3226
3417
  expandRequest,
@@ -3551,8 +3742,8 @@ function computeGraphViewState({
3551
3742
  showEmptyMessage: !hasNodes && !hasExplorationState,
3552
3743
  showFilteredMessage: !hasNodes && hasExplorationState,
3553
3744
  showControls: hasNodes || hasExplorationState,
3554
- outerClass: fullscreen ? "fixed inset-0 z-50 w-screen h-screen bg-white flex flex-col" : "w-full h-full flex flex-col relative",
3555
- innerClass: fullscreen ? "flex-1 relative overflow-hidden bg-white" : "flex-1 relative border border-gray-300 rounded-md overflow-hidden bg-white"
3745
+ outerClass: fullscreen ? "fixed inset-0 z-50 w-screen h-screen bg-background flex flex-col" : "w-full h-full flex flex-col relative",
3746
+ innerClass: fullscreen ? "flex-1 relative overflow-hidden bg-background" : "flex-1 relative border border-border rounded-md overflow-hidden bg-background"
3556
3747
  };
3557
3748
  }
3558
3749
  function useFullscreen(fitToScreen, cyRef) {
@@ -3595,10 +3786,7 @@ function setupCytoscapeEventHandlers({
3595
3786
  selectedEdgeRef,
3596
3787
  setSelectedNodeIdsStore,
3597
3788
  setSelectedEdgeStore,
3598
- clearSelectionStore,
3599
- useGraphExploration,
3600
- onCopyNode,
3601
- onExpandRequest
3789
+ clearSelectionStore
3602
3790
  }) {
3603
3791
  const updateSelectedNodeIds = (nodeIds) => {
3604
3792
  setSelectedNodeIdsStore(nodeIds);
@@ -3684,13 +3872,6 @@ function setupCytoscapeEventHandlers({
3684
3872
  cy.on("tap", (evt) => {
3685
3873
  if (evt.target === cy) clearAll();
3686
3874
  });
3687
- createNodeContextMenu({
3688
- cy,
3689
- useGraphExploration,
3690
- onCopyNode,
3691
- onExpandRequest
3692
- });
3693
- createEdgeContextMenu({ cy, useGraphExploration });
3694
3875
  }
3695
3876
  function CytoscapeGraph({
3696
3877
  layout: initialLayout = "auto",
@@ -3716,6 +3897,7 @@ function CytoscapeGraph({
3716
3897
  const baseNormalized = live.baseNormalized;
3717
3898
  const useGraphFilter2 = useWorkspaceGraphFilter();
3718
3899
  const useGraphExploration = useWorkspaceGraphExploration();
3900
+ const { theme } = useTheme2();
3719
3901
  const useSelection3 = useWorkspaceSelection();
3720
3902
  const useGraphFocus2 = useWorkspaceGraphFocus();
3721
3903
  const lastDataChangeTime = useGraphFilter2((s) => s.lastDataChangeTime);
@@ -3791,135 +3973,7 @@ function CytoscapeGraph({
3791
3973
  // Shift/Cmd to add a second node (e.g. for the two-node edge action).
3792
3974
  // This matches the conventional canvas-selection expectation.
3793
3975
  selectionType: "single",
3794
- style: [
3795
- // Base node style with data mappers for dynamic properties
3796
- {
3797
- selector: "node",
3798
- style: {
3799
- "background-color": "data(color)",
3800
- // Read color from element data
3801
- "background-opacity": 0.9,
3802
- width: "data(size)",
3803
- // Read size from element data
3804
- height: "data(size)",
3805
- shape: "ellipse",
3806
- label: "data(label)",
3807
- // Dark gray text for readability on subtle gray background
3808
- color: "#374151",
3809
- "font-size": "11px",
3810
- // Increased from 10px
3811
- "font-weight": "bold",
3812
- "text-valign": "bottom",
3813
- "text-halign": "center",
3814
- "text-wrap": "wrap",
3815
- "text-max-width": "120px",
3816
- // Increased from 100px for longer labels
3817
- "text-margin-y": 5,
3818
- // Subtle light gray label background
3819
- "text-background-color": "#e5e7eb",
3820
- // gray-200
3821
- "text-background-opacity": 1,
3822
- "text-background-padding": "3px",
3823
- "text-background-shape": "roundrectangle",
3824
- "border-width": 1,
3825
- "border-color": "#ffffff",
3826
- "border-opacity": 0.8
3827
- }
3828
- },
3829
- // Override background color only when fill data is present
3830
- {
3831
- selector: "node[fill]",
3832
- style: {
3833
- "background-color": "data(fill)"
3834
- }
3835
- },
3836
- {
3837
- selector: "edge",
3838
- style: {
3839
- width: "data(size)",
3840
- // Read width from element data
3841
- "line-color": "data(color)",
3842
- // Read color from element data
3843
- "target-arrow-color": "data(color)",
3844
- "target-arrow-shape": "triangle",
3845
- "curve-style": "bezier",
3846
- "control-point-step-size": 40,
3847
- "control-point-weight": 0.5,
3848
- opacity: 0.8,
3849
- "arrow-scale": 0.8,
3850
- "mid-target-arrow-shape": "none",
3851
- "source-endpoint": "outside-to-node-or-label",
3852
- "target-endpoint": "outside-to-node-or-label"
3853
- }
3854
- },
3855
- // Dashed edges (when dashed property is true)
3856
- {
3857
- selector: "edge[dashed]",
3858
- style: {
3859
- "line-style": "dashed"
3860
- }
3861
- },
3862
- // Show edge labels by default with good styling
3863
- {
3864
- selector: "edge[label]",
3865
- style: {
3866
- label: "data(label)",
3867
- "font-size": "10px",
3868
- // Increased from 8px for better readability
3869
- "font-weight": "500",
3870
- // Semi-bold for better visibility
3871
- "text-background-color": "#ffffff",
3872
- "text-background-opacity": 0.9,
3873
- // Increased from 0.8 for better contrast
3874
- "text-background-padding": "3px",
3875
- // Increased from 2px
3876
- "text-background-shape": "roundrectangle",
3877
- "text-rotation": "autorotate",
3878
- "text-margin-y": -8,
3879
- // Adjusted for larger font
3880
- color: "#1a1a1a"
3881
- // Darker for better contrast
3882
- }
3883
- },
3884
- {
3885
- selector: "edge:selected",
3886
- style: {
3887
- "line-color": SELECTION_COLOR,
3888
- "target-arrow-color": SELECTION_COLOR,
3889
- // Modest bump over the base width (~3); selection is conveyed mainly
3890
- // by colour + overlay, not a slab. (Was 8, which looked oversized.)
3891
- width: 4,
3892
- "z-index": 999,
3893
- "font-size": "11px",
3894
- "font-weight": "bold",
3895
- "text-background-opacity": 1,
3896
- "text-background-color": "#ffffff",
3897
- "text-background-padding": "3px",
3898
- "arrow-scale": 0.9,
3899
- opacity: 1,
3900
- "overlay-color": SELECTION_COLOR,
3901
- "overlay-opacity": 0.08
3902
- }
3903
- },
3904
- {
3905
- selector: "node:selected",
3906
- style: {
3907
- "border-width": 10,
3908
- // thicker circle outline
3909
- "border-color": SELECTION_COLOR,
3910
- "border-opacity": 1,
3911
- "background-opacity": 1,
3912
- "text-background-opacity": 1,
3913
- "text-background-color": "#ffffff",
3914
- "text-background-padding": "4px",
3915
- "overlay-color": SELECTION_COLOR,
3916
- "overlay-opacity": 0.18,
3917
- width: 48,
3918
- // slight size bump from 40
3919
- height: 48
3920
- }
3921
- }
3922
- ],
3976
+ style: graphStylesheet(canvasColors(theme)),
3923
3977
  layout: { name: "grid", fit: true, avoidOverlap: true, condense: true },
3924
3978
  // initial quick layout
3925
3979
  wheelSensitivity: 0.2
@@ -3933,10 +3987,7 @@ function CytoscapeGraph({
3933
3987
  selectedEdgeRef,
3934
3988
  setSelectedNodeIdsStore,
3935
3989
  setSelectedEdgeStore,
3936
- clearSelectionStore,
3937
- useGraphExploration,
3938
- onCopyNode,
3939
- onExpandRequest: setExpandRequest
3990
+ clearSelectionStore
3940
3991
  });
3941
3992
  }
3942
3993
  return () => {
@@ -3944,6 +3995,27 @@ function CytoscapeGraph({
3944
3995
  cyRef.current = null;
3945
3996
  };
3946
3997
  }, []);
3998
+ useEffect5(() => {
3999
+ const cy = cyRef.current;
4000
+ if (!cy) return;
4001
+ const colors = canvasColors(theme);
4002
+ cy.style(graphStylesheet(colors));
4003
+ const menus = [
4004
+ createNodeContextMenu({
4005
+ cy,
4006
+ useGraphExploration,
4007
+ onCopyNode,
4008
+ onExpandRequest: setExpandRequest,
4009
+ fillColor: colors.menu
4010
+ }),
4011
+ createEdgeContextMenu({
4012
+ cy,
4013
+ useGraphExploration,
4014
+ fillColor: colors.menu
4015
+ })
4016
+ ];
4017
+ return () => menus.forEach((menu) => menu.destroy());
4018
+ }, [theme, useGraphExploration, onCopyNode]);
3947
4019
  const elementIdsRef = useRef5("");
3948
4020
  const lastLayoutRef = useRef5("");
3949
4021
  const lastFitPaddingRef = useRef5(fitPadding);
@@ -4090,8 +4162,8 @@ function CytoscapeGraph({
4090
4162
  onClose: () => setExpandRequest(null)
4091
4163
  }
4092
4164
  ),
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)' }),
4165
+ showEmptyMessage && /* @__PURE__ */ jsx11("div", { className: "absolute inset-0 flex items-center justify-center text-xs text-muted-foreground pointer-events-none select-none", children: "No graph data to visualize (run a query or check the filters)" }),
4166
+ showFilteredMessage && /* @__PURE__ */ jsx11("div", { className: "absolute inset-0 flex items-center justify-center text-xs text-muted-foreground pointer-events-none select-none", children: 'All elements are hidden or filtered (click "Reset Focus" to restore)' }),
4095
4167
  showControls && /* @__PURE__ */ jsx11(
4096
4168
  CytoscapeControls_default,
4097
4169
  {
@@ -4263,7 +4335,7 @@ var EdgeInfoCard_default = EdgeInfoCard;
4263
4335
  // src/components/ActionPanel/Overview.tsx
4264
4336
  import { Badge } from "@petrarca/sonnet-ui";
4265
4337
  import { formatNumber, devLog as devLog11 } from "@petrarca/sonnet-core";
4266
- import { useMemo as useMemo4 } from "react";
4338
+ import { useMemo as useMemo5 } from "react";
4267
4339
  import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
4268
4340
  function computeEdgeToNodeLabels(nodesById, edgesById) {
4269
4341
  const mapping = {};
@@ -4542,7 +4614,7 @@ function GraphOverview({
4542
4614
  }
4543
4615
  function Overview({ nodeBuckets, edgeSectionTitle } = {}) {
4544
4616
  const { stats, colorPalette, labelCounts, edgeLabelCounts, baseNormalized } = useLiveGraphData();
4545
- const edgeToNodeLabels = useMemo4(() => {
4617
+ const edgeToNodeLabels = useMemo5(() => {
4546
4618
  if (!baseNormalized?.nodesById || !baseNormalized?.edgesById) return {};
4547
4619
  return computeEdgeToNodeLabels(
4548
4620
  baseNormalized.nodesById,
@@ -4577,7 +4649,7 @@ function Overview({ nodeBuckets, edgeSectionTitle } = {}) {
4577
4649
  var Overview_default = Overview;
4578
4650
 
4579
4651
  // src/components/ActionPanel/ActionPanel.tsx
4580
- import { useMemo as useMemo5 } from "react";
4652
+ import { useMemo as useMemo6 } from "react";
4581
4653
  import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
4582
4654
  function ActionPanel({ actions }) {
4583
4655
  const useSelection3 = useWorkspaceSelection();
@@ -4586,7 +4658,7 @@ function ActionPanel({ actions }) {
4586
4658
  );
4587
4659
  const selectedEdge = useSelection3((s) => s.selectedEdge);
4588
4660
  const live = useLiveGraphData();
4589
- const selectedNodes = useMemo5(
4661
+ const selectedNodes = useMemo6(
4590
4662
  () => selectedNodeIds.map((id) => live.normalized.nodesById[id]).filter(Boolean),
4591
4663
  [selectedNodeIds, live.normalized]
4592
4664
  );
@@ -4613,11 +4685,30 @@ function ActionPanel({ actions }) {
4613
4685
  }
4614
4686
  var ActionPanel_default = ActionPanel;
4615
4687
 
4688
+ // src/components/CenteredMessage.tsx
4689
+ import { jsx as jsx17 } from "react/jsx-runtime";
4690
+ var TONE = {
4691
+ muted: "text-muted-foreground",
4692
+ destructive: "text-destructive"
4693
+ };
4694
+ function CenteredMessage({
4695
+ children,
4696
+ tone = "muted"
4697
+ }) {
4698
+ return /* @__PURE__ */ jsx17(
4699
+ "div",
4700
+ {
4701
+ className: `flex-1 flex items-center justify-center text-sm ${TONE[tone]}`,
4702
+ children
4703
+ }
4704
+ );
4705
+ }
4706
+
4616
4707
  // src/components/GraphA11yList/GraphA11yList.tsx
4617
- import { useCallback as useCallback7, useMemo as useMemo6, useRef as useRef6 } from "react";
4708
+ import { useCallback as useCallback7, useMemo as useMemo7, useRef as useRef6 } from "react";
4618
4709
  import { useVirtualizer } from "@tanstack/react-virtual";
4619
4710
  import { cn as cn2 } from "@petrarca/sonnet-core";
4620
- import { jsx as jsx17 } from "react/jsx-runtime";
4711
+ import { jsx as jsx18 } from "react/jsx-runtime";
4621
4712
  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";
4622
4713
  function GraphA11yList({
4623
4714
  visible = false,
@@ -4637,7 +4728,7 @@ function GraphA11yList({
4637
4728
  const setSelectedEdge = useSelection3(
4638
4729
  (s) => s.setSelectedEdge
4639
4730
  );
4640
- const rows = useMemo6(() => {
4731
+ const rows = useMemo7(() => {
4641
4732
  const nodeRows = live.nodes.map((n) => ({
4642
4733
  kind: "node",
4643
4734
  id: String(n.id_),
@@ -4686,19 +4777,19 @@ function GraphA11yList({
4686
4777
  [activateNode, setSelectedNodeIds, setSelectedEdge]
4687
4778
  );
4688
4779
  const isSelected = (row) => row.kind === "node" ? selectedNodeIds.includes(row.id) : selectedEdge?.id_ === row.edge.id_;
4689
- return /* @__PURE__ */ jsx17(
4780
+ return /* @__PURE__ */ jsx18(
4690
4781
  "div",
4691
4782
  {
4692
4783
  role: "listbox",
4693
4784
  "aria-label": "Graph elements",
4694
4785
  "aria-multiselectable": true,
4695
4786
  className: cn2(visible ? "rounded-md border" : SR_ONLY, className),
4696
- children: /* @__PURE__ */ jsx17(
4787
+ children: /* @__PURE__ */ jsx18(
4697
4788
  "div",
4698
4789
  {
4699
4790
  ref: parentRef,
4700
4791
  style: { maxHeight: visible ? maxHeight : void 0, overflow: "auto" },
4701
- children: /* @__PURE__ */ jsx17(
4792
+ children: /* @__PURE__ */ jsx18(
4702
4793
  "div",
4703
4794
  {
4704
4795
  style: {
@@ -4710,7 +4801,7 @@ function GraphA11yList({
4710
4801
  const row = rows[vi.index];
4711
4802
  if (!row) return null;
4712
4803
  const selected = isSelected(row);
4713
- return /* @__PURE__ */ jsx17(
4804
+ return /* @__PURE__ */ jsx18(
4714
4805
  "button",
4715
4806
  {
4716
4807
  type: "button",
@@ -4728,7 +4819,7 @@ function GraphA11yList({
4728
4819
  height: `${vi.size}px`,
4729
4820
  transform: `translateY(${vi.start}px)`
4730
4821
  },
4731
- children: /* @__PURE__ */ jsx17("span", { className: "truncate", children: row.label })
4822
+ children: /* @__PURE__ */ jsx18("span", { className: "truncate", children: row.label })
4732
4823
  },
4733
4824
  vi.key
4734
4825
  );
@@ -4870,7 +4961,7 @@ import { useCallback as useCallback8, useEffect as useEffect6, useRef as useRef7
4870
4961
  import { ResizeHandle } from "@petrarca/sonnet-ui";
4871
4962
 
4872
4963
  // src/graph-model/ModelActionPanel.tsx
4873
- import { useMemo as useMemo8, useState as useState13 } from "react";
4964
+ import { useMemo as useMemo9, useState as useState13 } from "react";
4874
4965
  import { Button as Button8, IconButton as IconButton2 } from "@petrarca/sonnet-ui";
4875
4966
  import { GitBranchPlus, CirclePlus, Waypoints } from "lucide-react";
4876
4967
 
@@ -5185,7 +5276,7 @@ function deleteEdgeType(model, label) {
5185
5276
  }
5186
5277
 
5187
5278
  // src/graph-model/PropertyFormSheet.tsx
5188
- import { useMemo as useMemo7 } from "react";
5279
+ import { useMemo as useMemo8 } from "react";
5189
5280
  import {
5190
5281
  Sheet,
5191
5282
  SheetContent,
@@ -5195,7 +5286,7 @@ import {
5195
5286
  SheetDescription
5196
5287
  } from "@petrarca/sonnet-ui";
5197
5288
  import { JsonSchemaFormRenderer } from "@petrarca/sonnet-forms";
5198
- import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
5289
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
5199
5290
  var TYPE_OPTIONS = [
5200
5291
  "string",
5201
5292
  "integer",
@@ -5278,14 +5369,14 @@ function PropertyFormSheet({
5278
5369
  widgetOptions,
5279
5370
  onSave
5280
5371
  }) {
5281
- const widgets = useMemo7(
5372
+ const widgets = useMemo8(
5282
5373
  () => [...DEFAULT_WIDGETS, ...widgetOptions ?? []],
5283
5374
  [widgetOptions]
5284
5375
  );
5285
- const schema = useMemo7(() => buildSchema(widgets), [widgets]);
5286
- const data = useMemo7(() => toFormData(property), [property]);
5376
+ const schema = useMemo8(() => buildSchema(widgets), [widgets]);
5377
+ const data = useMemo8(() => toFormData(property), [property]);
5287
5378
  const isEdit = !!property;
5288
- return /* @__PURE__ */ jsx18(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs16(SheetContent, { children: [
5379
+ return /* @__PURE__ */ jsx19(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs16(SheetContent, { children: [
5289
5380
  /* @__PURE__ */ jsxs16(SheetHeader, { children: [
5290
5381
  /* @__PURE__ */ jsxs16(SheetTitle, { children: [
5291
5382
  isEdit ? "Edit property" : "Add property",
@@ -5294,9 +5385,9 @@ function PropertyFormSheet({
5294
5385
  " ",
5295
5386
  ownerLabel
5296
5387
  ] }),
5297
- /* @__PURE__ */ jsx18(SheetDescription, { children: "Define the property and how it is rendered." })
5388
+ /* @__PURE__ */ jsx19(SheetDescription, { children: "Define the property and how it is rendered." })
5298
5389
  ] }),
5299
- /* @__PURE__ */ jsx18(SheetBody, { children: /* @__PURE__ */ jsx18(
5390
+ /* @__PURE__ */ jsx19(SheetBody, { children: /* @__PURE__ */ jsx19(
5300
5391
  JsonSchemaFormRenderer,
5301
5392
  {
5302
5393
  schema,
@@ -5327,7 +5418,7 @@ import {
5327
5418
  } from "@petrarca/sonnet-ui";
5328
5419
  import { FormSelect } from "@petrarca/sonnet-forms";
5329
5420
  import { ArrowRight } from "lucide-react";
5330
- import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
5421
+ import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
5331
5422
  var CARDINALITIES = ["ONE_TO_MANY", "ONE_TO_ONE"];
5332
5423
  function RelationTypeField({
5333
5424
  relType,
@@ -5340,7 +5431,7 @@ function RelationTypeField({
5340
5431
  );
5341
5432
  const options = labels.map((l) => ({ value: l, label: l }));
5342
5433
  const canCreate = edgeTypeMode === "edit";
5343
- return /* @__PURE__ */ jsx19(
5434
+ return /* @__PURE__ */ jsx20(
5344
5435
  FormSelect,
5345
5436
  {
5346
5437
  label: "Relation type",
@@ -5363,12 +5454,12 @@ function DirectionField({
5363
5454
  onSwap
5364
5455
  }) {
5365
5456
  return /* @__PURE__ */ jsxs17("div", { children: [
5366
- /* @__PURE__ */ jsx19("label", { className: "mb-1 block text-xs font-medium", children: "Direction" }),
5457
+ /* @__PURE__ */ jsx20("label", { className: "mb-1 block text-xs font-medium", children: "Direction" }),
5367
5458
  /* @__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(
5459
+ /* @__PURE__ */ jsx20("span", { className: "font-medium", children: source }),
5460
+ /* @__PURE__ */ jsx20(ArrowRight, { className: "h-4 w-4 text-muted-foreground" }),
5461
+ /* @__PURE__ */ jsx20("span", { className: "font-medium", children: target }),
5462
+ canSwap && /* @__PURE__ */ jsx20(
5372
5463
  Button6,
5373
5464
  {
5374
5465
  variant: "ghost",
@@ -5386,8 +5477,8 @@ function CardinalityField({
5386
5477
  onChange
5387
5478
  }) {
5388
5479
  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(
5480
+ /* @__PURE__ */ jsx20("label", { className: "mb-1 block text-xs font-medium", children: "Cardinality" }),
5481
+ /* @__PURE__ */ jsx20("div", { className: "flex gap-2", children: CARDINALITIES.map((c) => /* @__PURE__ */ jsx20(
5391
5482
  Button6,
5392
5483
  {
5393
5484
  variant: c === cardinality ? "default" : "outline",
@@ -5427,13 +5518,13 @@ function RelationFormSheet({
5427
5518
  });
5428
5519
  onOpenChange(false);
5429
5520
  };
5430
- return /* @__PURE__ */ jsx19(Sheet2, { open, onOpenChange, children: /* @__PURE__ */ jsxs17(SheetContent2, { children: [
5521
+ return /* @__PURE__ */ jsx20(Sheet2, { open, onOpenChange, children: /* @__PURE__ */ jsxs17(SheetContent2, { children: [
5431
5522
  /* @__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." })
5523
+ /* @__PURE__ */ jsx20(SheetTitle2, { children: isEdit ? "Edit relation" : "Create relation" }),
5524
+ /* @__PURE__ */ jsx20(SheetDescription2, { children: "A relation is stored on the source node type." })
5434
5525
  ] }),
5435
5526
  /* @__PURE__ */ jsxs17(SheetBody2, { className: "flex flex-col gap-4", children: [
5436
- /* @__PURE__ */ jsx19(
5527
+ /* @__PURE__ */ jsx20(
5437
5528
  RelationTypeField,
5438
5529
  {
5439
5530
  relType,
@@ -5442,7 +5533,7 @@ function RelationFormSheet({
5442
5533
  edgeTypeMode
5443
5534
  }
5444
5535
  ),
5445
- /* @__PURE__ */ jsx19(
5536
+ /* @__PURE__ */ jsx20(
5446
5537
  DirectionField,
5447
5538
  {
5448
5539
  source,
@@ -5451,7 +5542,7 @@ function RelationFormSheet({
5451
5542
  onSwap: () => setSwapped((s) => !s)
5452
5543
  }
5453
5544
  ),
5454
- /* @__PURE__ */ jsx19(
5545
+ /* @__PURE__ */ jsx20(
5455
5546
  CardinalityField,
5456
5547
  {
5457
5548
  cardinality,
@@ -5459,8 +5550,8 @@ function RelationFormSheet({
5459
5550
  }
5460
5551
  ),
5461
5552
  /* @__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" })
5553
+ /* @__PURE__ */ jsx20(Button6, { variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
5554
+ /* @__PURE__ */ jsx20(Button6, { disabled: !canSave, onClick: handleSave, children: isEdit ? "Save" : "Create relation" })
5464
5555
  ] })
5465
5556
  ] })
5466
5557
  ] }) });
@@ -5478,7 +5569,7 @@ import {
5478
5569
  Button as Button7,
5479
5570
  Input as Input2
5480
5571
  } from "@petrarca/sonnet-ui";
5481
- import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
5572
+ import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
5482
5573
  function NodeTypeNameSheet({
5483
5574
  open,
5484
5575
  onOpenChange,
@@ -5493,15 +5584,15 @@ function NodeTypeNameSheet({
5493
5584
  const duplicate = trimmed !== initial && existing.includes(trimmed);
5494
5585
  const canSave = trimmed.length > 0 && !duplicate;
5495
5586
  const typeNoun = noun === "edge" ? "edge type" : "node type";
5496
- return /* @__PURE__ */ jsx20(Sheet3, { open, onOpenChange, children: /* @__PURE__ */ jsxs18(SheetContent3, { children: [
5587
+ return /* @__PURE__ */ jsx21(Sheet3, { open, onOpenChange, children: /* @__PURE__ */ jsxs18(SheetContent3, { children: [
5497
5588
  /* @__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." })
5589
+ /* @__PURE__ */ jsx21(SheetTitle3, { children: isEdit ? `Rename ${typeNoun}` : `Add ${typeNoun}` }),
5590
+ /* @__PURE__ */ jsx21(SheetDescription3, { children: "The label identifies this type and its instances." })
5500
5591
  ] }),
5501
5592
  /* @__PURE__ */ jsxs18(SheetBody3, { className: "flex flex-col gap-4", children: [
5502
5593
  /* @__PURE__ */ jsxs18("div", { children: [
5503
- /* @__PURE__ */ jsx20("label", { className: "mb-1 block text-xs font-medium", children: "Label" }),
5504
- /* @__PURE__ */ jsx20(
5594
+ /* @__PURE__ */ jsx21("label", { className: "mb-1 block text-xs font-medium", children: "Label" }),
5595
+ /* @__PURE__ */ jsx21(
5505
5596
  Input2,
5506
5597
  {
5507
5598
  value: name,
@@ -5510,11 +5601,11 @@ function NodeTypeNameSheet({
5510
5601
  autoFocus: true
5511
5602
  }
5512
5603
  ),
5513
- duplicate && /* @__PURE__ */ jsx20("p", { className: "mt-1 text-xs text-destructive", children: "A type with this label already exists." })
5604
+ duplicate && /* @__PURE__ */ jsx21("p", { className: "mt-1 text-xs text-destructive", children: "A type with this label already exists." })
5514
5605
  ] }),
5515
5606
  /* @__PURE__ */ jsxs18("div", { className: "flex justify-end gap-2", children: [
5516
- /* @__PURE__ */ jsx20(Button7, { variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
5517
- /* @__PURE__ */ jsx20(
5607
+ /* @__PURE__ */ jsx21(Button7, { variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
5608
+ /* @__PURE__ */ jsx21(
5518
5609
  Button7,
5519
5610
  {
5520
5611
  disabled: !canSave,
@@ -5531,7 +5622,7 @@ function NodeTypeNameSheet({
5531
5622
  }
5532
5623
 
5533
5624
  // src/graph-model/TypeDetailPanel.tsx
5534
- import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
5625
+ import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
5535
5626
  function relationViewOf(view) {
5536
5627
  return {
5537
5628
  relType: view.relType,
@@ -5545,8 +5636,8 @@ function SectionTitle({
5545
5636
  action
5546
5637
  }) {
5547
5638
  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 })
5639
+ /* @__PURE__ */ jsx22("span", { className: "heading-meta", children }),
5640
+ action && /* @__PURE__ */ jsx22("span", { className: "ml-auto", children: action })
5550
5641
  ] });
5551
5642
  }
5552
5643
  function PropertyRows({
@@ -5557,37 +5648,37 @@ function PropertyRows({
5557
5648
  onDelete
5558
5649
  }) {
5559
5650
  if (properties.length === 0) {
5560
- return /* @__PURE__ */ jsx21("p", { className: "text-xs text-muted-foreground", children: emptyText });
5651
+ return /* @__PURE__ */ jsx22("p", { className: "text-xs text-muted-foreground", children: emptyText });
5561
5652
  }
5562
- return /* @__PURE__ */ jsx21("div", { className: "rounded-lg border divide-y", children: properties.map((p) => /* @__PURE__ */ jsxs19(
5653
+ return /* @__PURE__ */ jsx22("div", { className: "rounded-lg border divide-y", children: properties.map((p) => /* @__PURE__ */ jsxs19(
5563
5654
  "div",
5564
5655
  {
5565
5656
  className: "group/row flex items-center gap-2 px-3 py-2",
5566
5657
  children: [
5567
- /* @__PURE__ */ jsx21("span", { className: "text-sm font-medium min-w-24", children: p.name }),
5658
+ /* @__PURE__ */ jsx22("span", { className: "text-sm font-medium min-w-24", children: p.name }),
5568
5659
  /* @__PURE__ */ jsxs19("span", { className: "flex-1 font-mono text-xs text-muted-foreground", children: [
5569
5660
  p.type,
5570
5661
  p.widget ? ` \xB7 ${p.widget}` : ""
5571
5662
  ] }),
5572
- p.required && /* @__PURE__ */ jsx21(Badge2, { badgeColor: "orange", children: "required" }),
5663
+ p.required && /* @__PURE__ */ jsx22(Badge2, { badgeColor: "orange", children: "required" }),
5573
5664
  editable && /* @__PURE__ */ jsxs19("span", { className: "flex gap-1", children: [
5574
- /* @__PURE__ */ jsx21(
5665
+ /* @__PURE__ */ jsx22(
5575
5666
  IconButton,
5576
5667
  {
5577
5668
  label: "Edit property",
5578
5669
  revealOnHover: true,
5579
5670
  onClick: () => onEdit(p),
5580
- children: /* @__PURE__ */ jsx21(Pencil, {})
5671
+ children: /* @__PURE__ */ jsx22(Pencil, {})
5581
5672
  }
5582
5673
  ),
5583
- /* @__PURE__ */ jsx21(
5674
+ /* @__PURE__ */ jsx22(
5584
5675
  IconButton,
5585
5676
  {
5586
5677
  label: "Delete property",
5587
5678
  tone: "destructive",
5588
5679
  revealOnHover: true,
5589
5680
  onClick: () => onDelete(p),
5590
- children: /* @__PURE__ */ jsx21(Trash2, {})
5681
+ children: /* @__PURE__ */ jsx22(Trash2, {})
5591
5682
  }
5592
5683
  )
5593
5684
  ] })
@@ -5670,7 +5761,7 @@ function TypeDetailPanel({
5670
5761
  if (!onChange) return;
5671
5762
  onChange(updateRelation(model, source, prev.relType, prev.target, input));
5672
5763
  };
5673
- const propertiesSection = propsHidden ? null : /* @__PURE__ */ jsx21(
5764
+ const propertiesSection = propsHidden ? null : /* @__PURE__ */ jsx22(
5674
5765
  PropertiesSection,
5675
5766
  {
5676
5767
  view,
@@ -5681,7 +5772,7 @@ function TypeDetailPanel({
5681
5772
  }
5682
5773
  );
5683
5774
  return /* @__PURE__ */ jsxs19("div", { children: [
5684
- view.kind === "node" ? /* @__PURE__ */ jsx21(
5775
+ view.kind === "node" ? /* @__PURE__ */ jsx22(
5685
5776
  NodeDetail,
5686
5777
  {
5687
5778
  view,
@@ -5693,7 +5784,7 @@ function TypeDetailPanel({
5693
5784
  onDeleteRelation: removeRelation,
5694
5785
  children: propertiesSection
5695
5786
  }
5696
- ) : view.kind === "edge" ? /* @__PURE__ */ jsx21(
5787
+ ) : view.kind === "edge" ? /* @__PURE__ */ jsx22(
5697
5788
  EdgeDetail,
5698
5789
  {
5699
5790
  view,
@@ -5702,7 +5793,7 @@ function TypeDetailPanel({
5702
5793
  onDelete: () => onChange?.(deleteEdgeType(model, view.label)),
5703
5794
  children: propertiesSection
5704
5795
  }
5705
- ) : /* @__PURE__ */ jsx21(
5796
+ ) : /* @__PURE__ */ jsx22(
5706
5797
  RelationDetail,
5707
5798
  {
5708
5799
  view,
@@ -5712,7 +5803,7 @@ function TypeDetailPanel({
5712
5803
  children: propertiesSection
5713
5804
  }
5714
5805
  ),
5715
- editable && /* @__PURE__ */ jsx21(
5806
+ editable && /* @__PURE__ */ jsx22(
5716
5807
  DetailSheets,
5717
5808
  {
5718
5809
  model,
@@ -5757,7 +5848,7 @@ function DetailSheets({
5757
5848
  }) {
5758
5849
  const edgeTypeOptions = model.filter(isEdgeType).map(typeLabel);
5759
5850
  return /* @__PURE__ */ jsxs19(Fragment4, { children: [
5760
- /* @__PURE__ */ jsx21(
5851
+ /* @__PURE__ */ jsx22(
5761
5852
  PropertyFormSheet,
5762
5853
  {
5763
5854
  open: sheet.open,
@@ -5768,7 +5859,7 @@ function DetailSheets({
5768
5859
  onSave: onSaveProperty
5769
5860
  }
5770
5861
  ),
5771
- relSheet && (view.kind === "node" || view.kind === "relation") && /* @__PURE__ */ jsx21(
5862
+ relSheet && (view.kind === "node" || view.kind === "relation") && /* @__PURE__ */ jsx22(
5772
5863
  RelationFormSheet,
5773
5864
  {
5774
5865
  open: !!relSheet,
@@ -5785,7 +5876,7 @@ function DetailSheets({
5785
5876
  },
5786
5877
  `${relSheet.relType}:${relSheet.target}`
5787
5878
  ),
5788
- (view.kind === "node" || view.kind === "edge") && renameOpen && /* @__PURE__ */ jsx21(
5879
+ (view.kind === "node" || view.kind === "edge") && renameOpen && /* @__PURE__ */ jsx22(
5789
5880
  NodeTypeNameSheet,
5790
5881
  {
5791
5882
  open: renameOpen,
@@ -5807,14 +5898,14 @@ function PropertiesSection({
5807
5898
  onDelete
5808
5899
  }) {
5809
5900
  return /* @__PURE__ */ jsxs19(Fragment4, { children: [
5810
- /* @__PURE__ */ jsx21(
5901
+ /* @__PURE__ */ jsx22(
5811
5902
  SectionTitle,
5812
5903
  {
5813
- action: editable ? /* @__PURE__ */ jsx21(IconButton, { label: "Add property", onClick: onAdd, children: /* @__PURE__ */ jsx21(Plus, {}) }) : void 0,
5904
+ action: editable ? /* @__PURE__ */ jsx22(IconButton, { label: "Add property", onClick: onAdd, children: /* @__PURE__ */ jsx22(Plus, {}) }) : void 0,
5814
5905
  children: "Properties"
5815
5906
  }
5816
5907
  ),
5817
- /* @__PURE__ */ jsx21(
5908
+ /* @__PURE__ */ jsx22(
5818
5909
  PropertyRows,
5819
5910
  {
5820
5911
  properties: view.properties,
@@ -5838,9 +5929,9 @@ function NodeDetail({
5838
5929
  }) {
5839
5930
  return /* @__PURE__ */ jsxs19(Fragment4, { children: [
5840
5931
  /* @__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(
5932
+ /* @__PURE__ */ jsx22("span", { className: "text-base font-semibold", children: view.label }),
5933
+ /* @__PURE__ */ jsx22(Badge2, { children: "NODE TYPE" }),
5934
+ nodeTypeEditable && /* @__PURE__ */ jsx22(
5844
5935
  RenameDeleteActions,
5845
5936
  {
5846
5937
  noun: "node type",
@@ -5849,15 +5940,15 @@ function NodeDetail({
5849
5940
  }
5850
5941
  )
5851
5942
  ] }),
5852
- view.description && /* @__PURE__ */ jsx21("p", { className: "mt-1 text-sm text-muted-foreground", children: view.description }),
5943
+ view.description && /* @__PURE__ */ jsx22("p", { className: "mt-1 text-sm text-muted-foreground", children: view.description }),
5853
5944
  children,
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(
5945
+ /* @__PURE__ */ jsx22(SectionTitle, { children: "Relations" }),
5946
+ view.relations.length === 0 ? /* @__PURE__ */ jsx22("p", { className: "text-xs text-muted-foreground", children: "No relations." }) : /* @__PURE__ */ jsx22("div", { className: "rounded-lg border divide-y", children: view.relations.map((r) => /* @__PURE__ */ jsxs19(
5856
5947
  "div",
5857
5948
  {
5858
5949
  className: "group/row flex items-center gap-2 px-3 py-2",
5859
5950
  children: [
5860
- /* @__PURE__ */ jsx21("span", { className: "text-sm font-medium min-w-24", children: r.relType }),
5951
+ /* @__PURE__ */ jsx22("span", { className: "text-sm font-medium min-w-24", children: r.relType }),
5861
5952
  /* @__PURE__ */ jsxs19("span", { className: "flex-1 font-mono text-xs text-muted-foreground", children: [
5862
5953
  "\u2192",
5863
5954
  " ",
@@ -5868,23 +5959,23 @@ function NodeDetail({
5868
5959
  cardinalityLabel(r.cardinality)
5869
5960
  ] }),
5870
5961
  relationsEditable && /* @__PURE__ */ jsxs19("span", { className: "flex gap-1", children: [
5871
- /* @__PURE__ */ jsx21(
5962
+ /* @__PURE__ */ jsx22(
5872
5963
  IconButton,
5873
5964
  {
5874
5965
  label: "Edit relation",
5875
5966
  revealOnHover: true,
5876
5967
  onClick: () => onEditRelation(r),
5877
- children: /* @__PURE__ */ jsx21(Pencil, {})
5968
+ children: /* @__PURE__ */ jsx22(Pencil, {})
5878
5969
  }
5879
5970
  ),
5880
- /* @__PURE__ */ jsx21(
5971
+ /* @__PURE__ */ jsx22(
5881
5972
  IconButton,
5882
5973
  {
5883
5974
  label: "Delete relation",
5884
5975
  tone: "destructive",
5885
5976
  revealOnHover: true,
5886
5977
  onClick: () => onDeleteRelation(r),
5887
- children: /* @__PURE__ */ jsx21(Trash2, {})
5978
+ children: /* @__PURE__ */ jsx22(Trash2, {})
5888
5979
  }
5889
5980
  )
5890
5981
  ] })
@@ -5897,7 +5988,7 @@ function NodeDetail({
5897
5988
  function EdgeTypeNote({
5898
5989
  children
5899
5990
  }) {
5900
- return /* @__PURE__ */ jsx21("p", { className: "mt-3 rounded-md bg-muted/50 px-3 py-2 text-xs text-muted-foreground", children });
5991
+ return /* @__PURE__ */ jsx22("p", { className: "mt-3 rounded-md bg-muted/50 px-3 py-2 text-xs text-muted-foreground", children });
5901
5992
  }
5902
5993
  function RenameDeleteActions({
5903
5994
  noun,
@@ -5906,14 +5997,14 @@ function RenameDeleteActions({
5906
5997
  onDelete
5907
5998
  }) {
5908
5999
  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(
6000
+ /* @__PURE__ */ jsx22(IconButton, { label: `${editVerb} ${noun}`, onClick: onRename, children: /* @__PURE__ */ jsx22(Pencil, {}) }),
6001
+ /* @__PURE__ */ jsx22(
5911
6002
  IconButton,
5912
6003
  {
5913
6004
  label: `Delete ${noun}`,
5914
6005
  tone: "destructive",
5915
6006
  onClick: onDelete,
5916
- children: /* @__PURE__ */ jsx21(Trash2, {})
6007
+ children: /* @__PURE__ */ jsx22(Trash2, {})
5917
6008
  }
5918
6009
  )
5919
6010
  ] });
@@ -5927,9 +6018,9 @@ function EdgeDetail({
5927
6018
  }) {
5928
6019
  return /* @__PURE__ */ jsxs19(Fragment4, { children: [
5929
6020
  /* @__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(
6021
+ /* @__PURE__ */ jsx22("span", { className: "text-base font-semibold", children: view.label }),
6022
+ /* @__PURE__ */ jsx22(Badge2, { children: "EDGE TYPE" }),
6023
+ editable && /* @__PURE__ */ jsx22(
5933
6024
  RenameDeleteActions,
5934
6025
  {
5935
6026
  noun: "edge type",
@@ -5938,11 +6029,11 @@ function EdgeDetail({
5938
6029
  }
5939
6030
  )
5940
6031
  ] }),
5941
- view.description && /* @__PURE__ */ jsx21("p", { className: "mt-1 text-sm text-muted-foreground", children: view.description }),
6032
+ view.description && /* @__PURE__ */ jsx22("p", { className: "mt-1 text-sm text-muted-foreground", children: view.description }),
5942
6033
  /* @__PURE__ */ jsxs19(EdgeTypeNote, { children: [
5943
6034
  "These properties belong to the",
5944
6035
  " ",
5945
- /* @__PURE__ */ jsx21("span", { className: "font-medium", children: view.label }),
6036
+ /* @__PURE__ */ jsx22("span", { className: "font-medium", children: view.label }),
5946
6037
  " edge type and are shared by every relation of this type."
5947
6038
  ] }),
5948
6039
  children
@@ -5957,9 +6048,9 @@ function RelationDetail({
5957
6048
  }) {
5958
6049
  return /* @__PURE__ */ jsxs19(Fragment4, { children: [
5959
6050
  /* @__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(
6051
+ /* @__PURE__ */ jsx22("span", { className: "text-base font-semibold", children: view.relType }),
6052
+ /* @__PURE__ */ jsx22(Badge2, { children: "RELATION" }),
6053
+ editable && /* @__PURE__ */ jsx22(
5963
6054
  RenameDeleteActions,
5964
6055
  {
5965
6056
  noun: "relation",
@@ -5970,16 +6061,16 @@ function RelationDetail({
5970
6061
  )
5971
6062
  ] }),
5972
6063
  /* @__PURE__ */ jsxs19("p", { className: "mt-1 text-sm text-muted-foreground", children: [
5973
- /* @__PURE__ */ jsx21("span", { className: "font-medium text-foreground", children: view.source }),
6064
+ /* @__PURE__ */ jsx22("span", { className: "font-medium text-foreground", children: view.source }),
5974
6065
  " \u2192 ",
5975
- /* @__PURE__ */ jsx21("span", { className: "font-medium text-foreground", children: view.target }),
6066
+ /* @__PURE__ */ jsx22("span", { className: "font-medium text-foreground", children: view.target }),
5976
6067
  " \xB7 ",
5977
6068
  cardinalityLabel(view.cardinality)
5978
6069
  ] }),
5979
6070
  /* @__PURE__ */ jsxs19(EdgeTypeNote, { children: [
5980
6071
  "Properties are defined on the",
5981
6072
  " ",
5982
- /* @__PURE__ */ jsx21("span", { className: "font-medium", children: view.relType }),
6073
+ /* @__PURE__ */ jsx22("span", { className: "font-medium", children: view.relType }),
5983
6074
  " edge type, shared by every relation of this type -- not on this single relation."
5984
6075
  ] }),
5985
6076
  children
@@ -6044,7 +6135,7 @@ function validateCapabilities(input) {
6044
6135
  }
6045
6136
 
6046
6137
  // src/graph-model/ModelActionPanel.tsx
6047
- import { Fragment as Fragment5, jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
6138
+ import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
6048
6139
  function nodeTypeLabels(model) {
6049
6140
  return model.filter(isNodeType).map(typeLabel);
6050
6141
  }
@@ -6061,16 +6152,16 @@ function CreateRelationPrompt({
6061
6152
  const [open, setOpen] = useState13(false);
6062
6153
  return /* @__PURE__ */ jsxs20("div", { children: [
6063
6154
  /* @__PURE__ */ jsxs20("div", { className: "text-sm", children: [
6064
- /* @__PURE__ */ jsx22("span", { className: "font-medium", children: nodeA }),
6155
+ /* @__PURE__ */ jsx23("span", { className: "font-medium", children: nodeA }),
6065
6156
  " and ",
6066
- /* @__PURE__ */ jsx22("span", { className: "font-medium", children: nodeB }),
6157
+ /* @__PURE__ */ jsx23("span", { className: "font-medium", children: nodeB }),
6067
6158
  " selected."
6068
6159
  ] }),
6069
6160
  /* @__PURE__ */ jsxs20(Button8, { size: "sm", className: "mt-3 gap-1.5", onClick: () => setOpen(true), children: [
6070
- /* @__PURE__ */ jsx22(GitBranchPlus, { className: "h-4 w-4" }),
6161
+ /* @__PURE__ */ jsx23(GitBranchPlus, { className: "h-4 w-4" }),
6071
6162
  "Create relation"
6072
6163
  ] }),
6073
- /* @__PURE__ */ jsx22(
6164
+ /* @__PURE__ */ jsx23(
6074
6165
  RelationFormSheet,
6075
6166
  {
6076
6167
  open,
@@ -6099,7 +6190,7 @@ function ModelActionPanel({
6099
6190
  const selectedEdge = useSelection3((s) => s.selectedEdge);
6100
6191
  const live = useLiveGraphData();
6101
6192
  const labelsById = (ids) => ids.map((id) => live.normalized.nodesById[id]?.label_).filter((l) => !!l);
6102
- const selection = useMemo8(() => {
6193
+ const selection = useMemo9(() => {
6103
6194
  if (selectedEdge) {
6104
6195
  const r = resolveRelationEdge(selectedEdge);
6105
6196
  if (r) {
@@ -6126,7 +6217,7 @@ function ModelActionPanel({
6126
6217
  const schema = findType(model, l);
6127
6218
  return schema ? isNodeType(schema) : false;
6128
6219
  }) : [];
6129
- return /* @__PURE__ */ jsx22("div", { className: "h-full overflow-auto p-4", children: /* @__PURE__ */ jsx22(
6220
+ return /* @__PURE__ */ jsx23("div", { className: "h-full overflow-auto p-4", children: /* @__PURE__ */ jsx23(
6130
6221
  PanelBody,
6131
6222
  {
6132
6223
  model,
@@ -6149,7 +6240,7 @@ function PanelBody({
6149
6240
  twoLabels
6150
6241
  }) {
6151
6242
  if (selection) {
6152
- return /* @__PURE__ */ jsx22(
6243
+ return /* @__PURE__ */ jsx23(
6153
6244
  TypeDetailPanel,
6154
6245
  {
6155
6246
  model,
@@ -6163,7 +6254,7 @@ function PanelBody({
6163
6254
  }
6164
6255
  const canEdit = !readOnly && !!onChange;
6165
6256
  if (twoLabels.length === 2 && canEdit && caps.relations === "edit") {
6166
- return /* @__PURE__ */ jsx22(
6257
+ return /* @__PURE__ */ jsx23(
6167
6258
  CreateRelationPrompt,
6168
6259
  {
6169
6260
  nodeA: twoLabels[0],
@@ -6175,7 +6266,7 @@ function PanelBody({
6175
6266
  );
6176
6267
  }
6177
6268
  return /* @__PURE__ */ jsxs20(Fragment5, { children: [
6178
- canEdit && /* @__PURE__ */ jsx22(
6269
+ canEdit && /* @__PURE__ */ jsx23(
6179
6270
  AddTypeToolbar,
6180
6271
  {
6181
6272
  model,
@@ -6184,7 +6275,7 @@ function PanelBody({
6184
6275
  canAddEdge: caps.edgeTypes === "edit"
6185
6276
  }
6186
6277
  ),
6187
- /* @__PURE__ */ jsx22(
6278
+ /* @__PURE__ */ jsx23(
6188
6279
  Overview_default,
6189
6280
  {
6190
6281
  nodeBuckets: [
@@ -6205,7 +6296,7 @@ function AddTypeIconButton({
6205
6296
  }) {
6206
6297
  const [open, setOpen] = useState13(false);
6207
6298
  return /* @__PURE__ */ jsxs20(Fragment5, { children: [
6208
- /* @__PURE__ */ jsx22(
6299
+ /* @__PURE__ */ jsx23(
6209
6300
  IconButton2,
6210
6301
  {
6211
6302
  label: tooltip,
@@ -6214,7 +6305,7 @@ function AddTypeIconButton({
6214
6305
  children: icon
6215
6306
  }
6216
6307
  ),
6217
- /* @__PURE__ */ jsx22(
6308
+ /* @__PURE__ */ jsx23(
6218
6309
  NodeTypeNameSheet,
6219
6310
  {
6220
6311
  open,
@@ -6235,21 +6326,21 @@ function AddTypeToolbar({
6235
6326
  if (!canAddNode && !canAddEdge) return null;
6236
6327
  const existing = model.map(typeLabel);
6237
6328
  return /* @__PURE__ */ jsxs20("div", { className: "mb-3 flex gap-1.5", children: [
6238
- canAddNode && /* @__PURE__ */ jsx22(
6329
+ canAddNode && /* @__PURE__ */ jsx23(
6239
6330
  AddTypeIconButton,
6240
6331
  {
6241
6332
  tooltip: "Add node type",
6242
- icon: /* @__PURE__ */ jsx22(CirclePlus, {}),
6333
+ icon: /* @__PURE__ */ jsx23(CirclePlus, {}),
6243
6334
  noun: "node",
6244
6335
  existing,
6245
6336
  onSave: (name) => onChange(addNodeType(model, name))
6246
6337
  }
6247
6338
  ),
6248
- canAddEdge && /* @__PURE__ */ jsx22(
6339
+ canAddEdge && /* @__PURE__ */ jsx23(
6249
6340
  AddTypeIconButton,
6250
6341
  {
6251
6342
  tooltip: "Add edge type",
6252
- icon: /* @__PURE__ */ jsx22(Waypoints, {}),
6343
+ icon: /* @__PURE__ */ jsx23(Waypoints, {}),
6253
6344
  noun: "edge",
6254
6345
  existing,
6255
6346
  onSave: (name) => onChange(addEdgeType(model, name))
@@ -6259,7 +6350,7 @@ function AddTypeToolbar({
6259
6350
  }
6260
6351
 
6261
6352
  // src/graph-model/GraphModelEditor.tsx
6262
- import { Fragment as Fragment6, jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
6353
+ import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs21 } from "react/jsx-runtime";
6263
6354
  function createEditorStores() {
6264
6355
  const graphFilter = createGraphFilterStore();
6265
6356
  const graphData = createGraphDataStore();
@@ -6334,10 +6425,10 @@ function GraphModelEditor({
6334
6425
  window.addEventListener("pointermove", move);
6335
6426
  window.addEventListener("pointerup", up);
6336
6427
  }, []);
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, {}) }),
6428
+ return /* @__PURE__ */ jsx24(GraphStoresContext.Provider, { value: stores, children: /* @__PURE__ */ jsx24(GraphRendererProvider, { defaultRenderer, children: /* @__PURE__ */ jsxs21("div", { ref: rowRef, className: "flex w-full h-full overflow-hidden", children: [
6429
+ /* @__PURE__ */ jsx24("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx24(GraphVisualizer_default, {}) }),
6339
6430
  !hideActionPanel && /* @__PURE__ */ jsxs21(Fragment6, { children: [
6340
- /* @__PURE__ */ jsx23(
6431
+ /* @__PURE__ */ jsx24(
6341
6432
  ResizeHandle,
6342
6433
  {
6343
6434
  orientation: "vertical",
@@ -6348,12 +6439,12 @@ function GraphModelEditor({
6348
6439
  onDoubleClick: () => setPanelWidth(380)
6349
6440
  }
6350
6441
  ),
6351
- /* @__PURE__ */ jsx23(
6442
+ /* @__PURE__ */ jsx24(
6352
6443
  "div",
6353
6444
  {
6354
6445
  style: { width: panelWidth },
6355
6446
  className: "shrink-0 border-l overflow-hidden bg-card",
6356
- children: /* @__PURE__ */ jsx23(
6447
+ children: /* @__PURE__ */ jsx24(
6357
6448
  ModelActionPanel,
6358
6449
  {
6359
6450
  model: value,
@@ -6517,7 +6608,7 @@ function enrichSchemaWithRelations(baseSchema, relations) {
6517
6608
  }
6518
6609
 
6519
6610
  // src/graph-edit/GraphEditToolbar.tsx
6520
- import { useCallback as useCallback12, useMemo as useMemo11, useState as useState18 } from "react";
6611
+ import { useCallback as useCallback12, useMemo as useMemo12, useState as useState18 } from "react";
6521
6612
  import { IconButton as IconButton4 } from "@petrarca/sonnet-ui";
6522
6613
  import { Plus as Plus3, Edit, Link, Trash2 as Trash23 } from "lucide-react";
6523
6614
 
@@ -6525,7 +6616,7 @@ import { Plus as Plus3, Edit, Link, Trash2 as Trash23 } from "lucide-react";
6525
6616
  import {
6526
6617
  useCallback as useCallback9,
6527
6618
  useEffect as useEffect7,
6528
- useMemo as useMemo9,
6619
+ useMemo as useMemo10,
6529
6620
  useRef as useRef8,
6530
6621
  useState as useState15
6531
6622
  } from "react";
@@ -6544,7 +6635,7 @@ import {
6544
6635
  JsonSchemaFormRenderer as JsonSchemaFormRenderer2
6545
6636
  } from "@petrarca/sonnet-forms";
6546
6637
  import { errorLog as errorLog4 } from "@petrarca/sonnet-core";
6547
- import { Fragment as Fragment7, jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
6638
+ import { Fragment as Fragment7, jsx as jsx25, jsxs as jsxs22 } from "react/jsx-runtime";
6548
6639
  var EMPTY_RESOLVED = {
6549
6640
  schema: null,
6550
6641
  uiSchema: {},
@@ -6656,11 +6747,11 @@ function LabelField({
6656
6747
  }) {
6657
6748
  if (isEdit) {
6658
6749
  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 })
6750
+ /* @__PURE__ */ jsx25("span", { className: "heading-meta block", children: "Label" }),
6751
+ /* @__PURE__ */ jsx25("span", { className: "text-sm text-muted-foreground", children: selectedLabel })
6661
6752
  ] });
6662
6753
  }
6663
- return /* @__PURE__ */ jsx24(
6754
+ return /* @__PURE__ */ jsx25(
6664
6755
  FormSelect2,
6665
6756
  {
6666
6757
  label: "Label",
@@ -6695,7 +6786,7 @@ function NodeForm({
6695
6786
  onSave,
6696
6787
  onCancel
6697
6788
  }) {
6698
- return /* @__PURE__ */ jsx24(
6789
+ return /* @__PURE__ */ jsx25(
6699
6790
  JsonSchemaFormRenderer2,
6700
6791
  {
6701
6792
  schema,
@@ -6731,11 +6822,11 @@ function NodeFormSheetBody({
6731
6822
  }) {
6732
6823
  return /* @__PURE__ */ jsxs22(Fragment7, { children: [
6733
6824
  /* @__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." })
6825
+ /* @__PURE__ */ jsx25(SheetTitle4, { children: isEdit ? "Edit node" : "Add node" }),
6826
+ /* @__PURE__ */ jsx25(SheetDescription4, { children: isEdit ? "Edit this node's properties and relations." : "Create a node and (optionally) its relations." })
6736
6827
  ] }),
6737
6828
  /* @__PURE__ */ jsxs22(SheetBody4, { className: "flex flex-col gap-4", children: [
6738
- /* @__PURE__ */ jsx24(
6829
+ /* @__PURE__ */ jsx25(
6739
6830
  LabelField,
6740
6831
  {
6741
6832
  isEdit,
@@ -6744,7 +6835,7 @@ function NodeFormSheetBody({
6744
6835
  onSelect: onSelectLabel
6745
6836
  }
6746
6837
  ),
6747
- schema && /* @__PURE__ */ jsx24(
6838
+ schema && /* @__PURE__ */ jsx25(
6748
6839
  NodeForm,
6749
6840
  {
6750
6841
  formKey,
@@ -6757,7 +6848,7 @@ function NodeFormSheetBody({
6757
6848
  onCancel
6758
6849
  }
6759
6850
  ),
6760
- !isEdit && /* @__PURE__ */ jsx24(
6851
+ !isEdit && /* @__PURE__ */ jsx25(
6761
6852
  FormCheckbox,
6762
6853
  {
6763
6854
  label: "Create another node",
@@ -6765,7 +6856,7 @@ function NodeFormSheetBody({
6765
6856
  onChange: (c) => onKeepOpenChange(c === true)
6766
6857
  }
6767
6858
  ),
6768
- error && /* @__PURE__ */ jsx24(Alert2, { variant: "destructive", children: error })
6859
+ error && /* @__PURE__ */ jsx25(Alert2, { variant: "destructive", children: error })
6769
6860
  ] })
6770
6861
  ] });
6771
6862
  }
@@ -6800,7 +6891,7 @@ function NodeFormSheet({
6800
6891
  node
6801
6892
  );
6802
6893
  const existingData = useExistingFormData(port, node, relationTypes);
6803
- const formData = useMemo9(() => existingData ?? {}, [existingData]);
6894
+ const formData = useMemo10(() => existingData ?? {}, [existingData]);
6804
6895
  const stableOnSaved = useStableRef(onSaved);
6805
6896
  const stableOnOpenChange = useStableRef(onOpenChange);
6806
6897
  const handleSave = useCallback9(
@@ -6836,7 +6927,7 @@ function NodeFormSheet({
6836
6927
  stableOnOpenChange
6837
6928
  ]
6838
6929
  );
6839
- return /* @__PURE__ */ jsx24(Sheet4, { open, onOpenChange, children: /* @__PURE__ */ jsx24(SheetContent4, { children: /* @__PURE__ */ jsx24(
6930
+ return /* @__PURE__ */ jsx25(Sheet4, { open, onOpenChange, children: /* @__PURE__ */ jsx25(SheetContent4, { children: /* @__PURE__ */ jsx25(
6840
6931
  NodeFormSheetBody,
6841
6932
  {
6842
6933
  isEdit,
@@ -6893,7 +6984,7 @@ async function saveNode({
6893
6984
  }
6894
6985
 
6895
6986
  // src/graph-edit/EdgeFormSheet.tsx
6896
- import { useCallback as useCallback10, useEffect as useEffect8, useMemo as useMemo10, useState as useState16 } from "react";
6987
+ import { useCallback as useCallback10, useEffect as useEffect8, useMemo as useMemo11, useState as useState16 } from "react";
6897
6988
  import {
6898
6989
  Sheet as Sheet5,
6899
6990
  SheetContent as SheetContent5,
@@ -6916,7 +7007,7 @@ import {
6916
7007
  JsonSchemaFormRenderer as JsonSchemaFormRenderer3
6917
7008
  } from "@petrarca/sonnet-forms";
6918
7009
  import { errorLog as errorLog5 } from "@petrarca/sonnet-core";
6919
- import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
7010
+ import { jsx as jsx26, jsxs as jsxs23 } from "react/jsx-runtime";
6920
7011
  function usePairState(port, schemaProvider, open, nodeAId, nodeALabel, nodeBId, nodeBLabel, reloadKey) {
6921
7012
  const [active, setActive] = useState16([]);
6922
7013
  const [available, setAvailable] = useState16([]);
@@ -6986,7 +7077,7 @@ var EMPTY_EDGE_SCHEMA = {
6986
7077
  };
6987
7078
  function useEdgeSchema(schemaProvider, draft) {
6988
7079
  const [schema, setSchema] = useState16(null);
6989
- const label = useMemo10(() => draftLabel(draft), [draft]);
7080
+ const label = useMemo11(() => draftLabel(draft), [draft]);
6990
7081
  useEffect8(() => {
6991
7082
  let cancelled = false;
6992
7083
  if (!label) {
@@ -7008,7 +7099,7 @@ function useEdgeSchema(schemaProvider, draft) {
7008
7099
  }
7009
7100
  function useEditableEdgeTypes(schemaProvider, active) {
7010
7101
  const [editable, setEditable] = useState16({});
7011
- const labelsKey = useMemo10(
7102
+ const labelsKey = useMemo11(
7012
7103
  () => Array.from(new Set(active.map((e) => e.label_))).sort().join("\0"),
7013
7104
  [active]
7014
7105
  );
@@ -7041,7 +7132,7 @@ function useEditableEdgeTypes(schemaProvider, active) {
7041
7132
  function SectionTitle2({
7042
7133
  children
7043
7134
  }) {
7044
- return /* @__PURE__ */ jsx25("div", { className: "mb-2", children: /* @__PURE__ */ jsx25("span", { className: "heading-meta", children }) });
7135
+ return /* @__PURE__ */ jsx26("div", { className: "mb-2", children: /* @__PURE__ */ jsx26("span", { className: "heading-meta", children }) });
7045
7136
  }
7046
7137
  function EdgeRow({
7047
7138
  label,
@@ -7050,18 +7141,18 @@ function EdgeRow({
7050
7141
  onAdd
7051
7142
  }) {
7052
7143
  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 }),
7144
+ /* @__PURE__ */ jsx26("span", { className: "flex-1 text-sm font-medium", children: label }),
7054
7145
  /* @__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(
7146
+ onAdd && /* @__PURE__ */ jsx26(IconButton3, { label: "Create edge", revealOnHover: true, onClick: onAdd, children: /* @__PURE__ */ jsx26(Plus2, {}) }),
7147
+ onEdit && /* @__PURE__ */ jsx26(IconButton3, { label: "Edit edge", revealOnHover: true, onClick: onEdit, children: /* @__PURE__ */ jsx26(Pencil2, {}) }),
7148
+ onDelete && /* @__PURE__ */ jsx26(
7058
7149
  IconButton3,
7059
7150
  {
7060
7151
  label: "Delete edge",
7061
7152
  tone: "destructive",
7062
7153
  revealOnHover: true,
7063
7154
  onClick: onDelete,
7064
- children: /* @__PURE__ */ jsx25(Trash22, {})
7155
+ children: /* @__PURE__ */ jsx26(Trash22, {})
7065
7156
  }
7066
7157
  )
7067
7158
  ] })
@@ -7098,8 +7189,8 @@ function PairLists({
7098
7189
  "."
7099
7190
  ] }),
7100
7191
  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(
7192
+ /* @__PURE__ */ jsx26(SectionTitle2, { children: "Active edges" }),
7193
+ /* @__PURE__ */ jsx26("div", { className: "rounded-lg border divide-y", children: active.map((edge) => /* @__PURE__ */ jsx26(
7103
7194
  EdgeRow,
7104
7195
  {
7105
7196
  label: edge.label_,
@@ -7110,8 +7201,8 @@ function PairLists({
7110
7201
  )) })
7111
7202
  ] }),
7112
7203
  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(
7204
+ /* @__PURE__ */ jsx26(SectionTitle2, { children: "Available edges" }),
7205
+ /* @__PURE__ */ jsx26("div", { className: "rounded-lg border divide-y", children: available.map((entry) => /* @__PURE__ */ jsx26(
7115
7206
  EdgeRow,
7116
7207
  {
7117
7208
  label: rowLabel(entry),
@@ -7137,7 +7228,7 @@ function DraftForm({
7137
7228
  " ",
7138
7229
  label
7139
7230
  ] }),
7140
- /* @__PURE__ */ jsx25(
7231
+ /* @__PURE__ */ jsx26(
7141
7232
  JsonSchemaFormRenderer3,
7142
7233
  {
7143
7234
  schema,
@@ -7242,16 +7333,16 @@ function EdgeFormSheet({
7242
7333
  return /* @__PURE__ */ jsxs23(Sheet5, { open, onOpenChange, children: [
7243
7334
  /* @__PURE__ */ jsxs23(SheetContent5, { children: [
7244
7335
  /* @__PURE__ */ jsxs23(SheetHeader5, { children: [
7245
- /* @__PURE__ */ jsx25(SheetTitle5, { children: "Edges" }),
7246
- /* @__PURE__ */ jsx25(SheetDescription5, { children: "Manage the edges between the two selected nodes." })
7336
+ /* @__PURE__ */ jsx26(SheetTitle5, { children: "Edges" }),
7337
+ /* @__PURE__ */ jsx26(SheetDescription5, { children: "Manage the edges between the two selected nodes." })
7247
7338
  ] }),
7248
7339
  /* @__PURE__ */ jsxs23(SheetBody5, { className: "flex flex-col gap-4", children: [
7249
7340
  /* @__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) })
7341
+ /* @__PURE__ */ jsx26("span", { className: "font-medium", children: extractDisplayLabel(nodeA) }),
7342
+ /* @__PURE__ */ jsx26(ArrowRight2, { className: "h-4 w-4 text-muted-foreground" }),
7343
+ /* @__PURE__ */ jsx26("span", { className: "font-medium", children: extractDisplayLabel(nodeB) })
7253
7344
  ] }),
7254
- !draft && /* @__PURE__ */ jsx25(
7345
+ !draft && /* @__PURE__ */ jsx26(
7255
7346
  PairLists,
7256
7347
  {
7257
7348
  nodeALabel: nodeA.label_,
@@ -7265,7 +7356,7 @@ function EdgeFormSheet({
7265
7356
  onCreate: (entry) => setDraft({ mode: "create", entry })
7266
7357
  }
7267
7358
  ),
7268
- draft && schema && /* @__PURE__ */ jsx25(
7359
+ draft && schema && /* @__PURE__ */ jsx26(
7269
7360
  DraftForm,
7270
7361
  {
7271
7362
  draft,
@@ -7275,10 +7366,10 @@ function EdgeFormSheet({
7275
7366
  onCancel: () => setDraft(null)
7276
7367
  }
7277
7368
  ),
7278
- error && /* @__PURE__ */ jsx25(Alert3, { variant: "destructive", children: error })
7369
+ error && /* @__PURE__ */ jsx26(Alert3, { variant: "destructive", children: error })
7279
7370
  ] })
7280
7371
  ] }),
7281
- /* @__PURE__ */ jsx25(
7372
+ /* @__PURE__ */ jsx26(
7282
7373
  DeleteEdgeConfirm,
7283
7374
  {
7284
7375
  edge: pendingDelete,
@@ -7293,18 +7384,18 @@ function DeleteEdgeConfirm({
7293
7384
  onCancel,
7294
7385
  onConfirm
7295
7386
  }) {
7296
- return /* @__PURE__ */ jsx25(Dialog, { open: !!edge, onOpenChange: (o) => !o && onCancel(), children: /* @__PURE__ */ jsxs23(DialogContent, { children: [
7387
+ return /* @__PURE__ */ jsx26(Dialog, { open: !!edge, onOpenChange: (o) => !o && onCancel(), children: /* @__PURE__ */ jsxs23(DialogContent, { children: [
7297
7388
  /* @__PURE__ */ jsxs23(DialogHeader, { children: [
7298
- /* @__PURE__ */ jsx25(DialogTitle, { children: "Delete edge" }),
7389
+ /* @__PURE__ */ jsx26(DialogTitle, { children: "Delete edge" }),
7299
7390
  /* @__PURE__ */ jsxs23(DialogDescription, { children: [
7300
7391
  "Delete the edge ",
7301
- /* @__PURE__ */ jsx25("strong", { children: edge?.label_ }),
7392
+ /* @__PURE__ */ jsx26("strong", { children: edge?.label_ }),
7302
7393
  "? This removes the relationship between the two nodes."
7303
7394
  ] })
7304
7395
  ] }),
7305
7396
  /* @__PURE__ */ jsxs23(DialogFooter, { children: [
7306
- /* @__PURE__ */ jsx25(Button9, { variant: "outline", onClick: onCancel, children: "Cancel" }),
7307
- /* @__PURE__ */ jsx25(Button9, { variant: "destructive", onClick: onConfirm, children: "Delete" })
7397
+ /* @__PURE__ */ jsx26(Button9, { variant: "outline", onClick: onCancel, children: "Cancel" }),
7398
+ /* @__PURE__ */ jsx26(Button9, { variant: "destructive", onClick: onConfirm, children: "Delete" })
7308
7399
  ] })
7309
7400
  ] }) });
7310
7401
  }
@@ -7322,7 +7413,7 @@ import {
7322
7413
  Alert as Alert4
7323
7414
  } from "@petrarca/sonnet-ui";
7324
7415
  import { errorLog as errorLog6 } from "@petrarca/sonnet-core";
7325
- import { jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
7416
+ import { jsx as jsx27, jsxs as jsxs24 } from "react/jsx-runtime";
7326
7417
  var EMPTY_DEPENDENT = {
7327
7418
  inbound: [],
7328
7419
  outbound: [],
@@ -7381,11 +7472,11 @@ function EdgeList({
7381
7472
  }) {
7382
7473
  if (edges.length === 0) return null;
7383
7474
  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) => {
7475
+ /* @__PURE__ */ jsx27("span", { className: "heading-meta block", children: title }),
7476
+ /* @__PURE__ */ jsx27("ul", { className: "list-disc pl-6 text-sm", children: edges.map((edge) => {
7386
7477
  const neighbour = direction === "inbound" ? names[edge.start_id_] : names[edge.end_id_];
7387
7478
  const text = direction === "inbound" ? `${neighbour ?? "?"} \u2192 ${edge.label_}` : `${edge.label_} \u2192 ${neighbour ?? "?"}`;
7388
- return /* @__PURE__ */ jsx26("li", { children: neighbour ? text : edge.label_ }, edge.id_);
7479
+ return /* @__PURE__ */ jsx27("li", { children: neighbour ? text : edge.label_ }, edge.id_);
7389
7480
  }) })
7390
7481
  ] });
7391
7482
  }
@@ -7412,17 +7503,17 @@ function DeleteNodeDialog({
7412
7503
  }
7413
7504
  })();
7414
7505
  }, [port, node.id_, onDeleted, onOpenChange]);
7415
- return /* @__PURE__ */ jsx26(Dialog2, { open, onOpenChange, children: /* @__PURE__ */ jsxs24(DialogContent2, { children: [
7506
+ return /* @__PURE__ */ jsx27(Dialog2, { open, onOpenChange, children: /* @__PURE__ */ jsxs24(DialogContent2, { children: [
7416
7507
  /* @__PURE__ */ jsxs24(DialogHeader2, { children: [
7417
- /* @__PURE__ */ jsx26(DialogTitle2, { children: "Delete node" }),
7508
+ /* @__PURE__ */ jsx27(DialogTitle2, { children: "Delete node" }),
7418
7509
  /* @__PURE__ */ jsxs24(DialogDescription2, { children: [
7419
7510
  "Delete the node ",
7420
- /* @__PURE__ */ jsx26("strong", { children: extractDisplayLabel(node) }),
7511
+ /* @__PURE__ */ jsx27("strong", { children: extractDisplayLabel(node) }),
7421
7512
  "?"
7422
7513
  ] })
7423
7514
  ] }),
7424
- !hasEdges && /* @__PURE__ */ jsx26("p", { className: "text-sm text-muted-foreground", children: "No connected edges." }),
7425
- /* @__PURE__ */ jsx26(
7515
+ !hasEdges && /* @__PURE__ */ jsx27("p", { className: "text-sm text-muted-foreground", children: "No connected edges." }),
7516
+ /* @__PURE__ */ jsx27(
7426
7517
  EdgeList,
7427
7518
  {
7428
7519
  title: "Inbound edges",
@@ -7431,7 +7522,7 @@ function DeleteNodeDialog({
7431
7522
  direction: "inbound"
7432
7523
  }
7433
7524
  ),
7434
- /* @__PURE__ */ jsx26(
7525
+ /* @__PURE__ */ jsx27(
7435
7526
  EdgeList,
7436
7527
  {
7437
7528
  title: "Outbound edges",
@@ -7440,16 +7531,16 @@ function DeleteNodeDialog({
7440
7531
  direction: "outbound"
7441
7532
  }
7442
7533
  ),
7443
- error && /* @__PURE__ */ jsx26(Alert4, { variant: "destructive", children: error }),
7534
+ error && /* @__PURE__ */ jsx27(Alert4, { variant: "destructive", children: error }),
7444
7535
  /* @__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" })
7536
+ /* @__PURE__ */ jsx27(Button10, { variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
7537
+ /* @__PURE__ */ jsx27(Button10, { variant: "destructive", onClick: handleDelete, children: "Delete" })
7447
7538
  ] })
7448
7539
  ] }) });
7449
7540
  }
7450
7541
 
7451
7542
  // src/graph-edit/GraphEditToolbar.tsx
7452
- import { Fragment as Fragment8, jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
7543
+ import { Fragment as Fragment8, jsx as jsx28, jsxs as jsxs25 } from "react/jsx-runtime";
7453
7544
  function toPerms(caps) {
7454
7545
  const canCreateNode = caps.createNode === "edit";
7455
7546
  const canUpdateNode = caps.updateNode === "edit";
@@ -7469,37 +7560,37 @@ function ToolbarButtons({
7469
7560
  onOpen
7470
7561
  }) {
7471
7562
  return /* @__PURE__ */ jsxs25("div", { className: "flex gap-0.5", children: [
7472
- perms.canCreateNode && /* @__PURE__ */ jsx27(
7563
+ perms.canCreateNode && /* @__PURE__ */ jsx28(
7473
7564
  IconButton4,
7474
7565
  {
7475
7566
  label: "Add node",
7476
7567
  variant: "outline",
7477
7568
  disabled: count > 0,
7478
7569
  onClick: () => onOpen("node-create"),
7479
- children: /* @__PURE__ */ jsx27(Plus3, {})
7570
+ children: /* @__PURE__ */ jsx28(Plus3, {})
7480
7571
  }
7481
7572
  ),
7482
- perms.canUpdateNode && /* @__PURE__ */ jsx27(
7573
+ perms.canUpdateNode && /* @__PURE__ */ jsx28(
7483
7574
  IconButton4,
7484
7575
  {
7485
7576
  label: "Edit node",
7486
7577
  variant: "outline",
7487
7578
  disabled: count !== 1,
7488
7579
  onClick: () => onOpen("node-edit"),
7489
- children: /* @__PURE__ */ jsx27(Edit, {})
7580
+ children: /* @__PURE__ */ jsx28(Edit, {})
7490
7581
  }
7491
7582
  ),
7492
- perms.canEditEdges && /* @__PURE__ */ jsx27(
7583
+ perms.canEditEdges && /* @__PURE__ */ jsx28(
7493
7584
  IconButton4,
7494
7585
  {
7495
7586
  label: "Edges",
7496
7587
  variant: "outline",
7497
7588
  disabled: count !== 2,
7498
7589
  onClick: () => onOpen("edge"),
7499
- children: /* @__PURE__ */ jsx27(Link, {})
7590
+ children: /* @__PURE__ */ jsx28(Link, {})
7500
7591
  }
7501
7592
  ),
7502
- perms.canDeleteNode && /* @__PURE__ */ jsx27(
7593
+ perms.canDeleteNode && /* @__PURE__ */ jsx28(
7503
7594
  IconButton4,
7504
7595
  {
7505
7596
  label: "Delete node",
@@ -7507,7 +7598,7 @@ function ToolbarButtons({
7507
7598
  tone: "destructive",
7508
7599
  disabled: count !== 1,
7509
7600
  onClick: () => onOpen("delete"),
7510
- children: /* @__PURE__ */ jsx27(Trash23, {})
7601
+ children: /* @__PURE__ */ jsx28(Trash23, {})
7511
7602
  }
7512
7603
  )
7513
7604
  ] });
@@ -7530,7 +7621,7 @@ function ToolbarDialogs({
7530
7621
  [onClose]
7531
7622
  );
7532
7623
  return /* @__PURE__ */ jsxs25(Fragment8, { children: [
7533
- (perms.canCreateNode || perms.canUpdateNode) && /* @__PURE__ */ jsx27(
7624
+ (perms.canCreateNode || perms.canUpdateNode) && /* @__PURE__ */ jsx28(
7534
7625
  NodeFormSheet,
7535
7626
  {
7536
7627
  open: dialog === "node-create" || dialog === "node-edit",
@@ -7542,7 +7633,7 @@ function ToolbarDialogs({
7542
7633
  onSaved: onMutated
7543
7634
  }
7544
7635
  ),
7545
- perms.canEditEdges && firstNode && secondNode && /* @__PURE__ */ jsx27(
7636
+ perms.canEditEdges && firstNode && secondNode && /* @__PURE__ */ jsx28(
7546
7637
  EdgeFormSheet,
7547
7638
  {
7548
7639
  open: dialog === "edge",
@@ -7555,7 +7646,7 @@ function ToolbarDialogs({
7555
7646
  onChanged: onMutated
7556
7647
  }
7557
7648
  ),
7558
- perms.canDeleteNode && firstNode && /* @__PURE__ */ jsx27(
7649
+ perms.canDeleteNode && firstNode && /* @__PURE__ */ jsx28(
7559
7650
  DeleteNodeDialog,
7560
7651
  {
7561
7652
  open: dialog === "delete",
@@ -7581,18 +7672,18 @@ function GraphEditToolbar({
7581
7672
  );
7582
7673
  const live = useLiveGraphData();
7583
7674
  const [dialog, setDialog] = useState18(null);
7584
- const perms = useMemo11(
7675
+ const perms = useMemo12(
7585
7676
  () => toPerms(resolveGraphCapabilities(capabilities, readOnly)),
7586
7677
  [capabilities, readOnly]
7587
7678
  );
7588
- const selectedNodes = useMemo11(
7679
+ const selectedNodes = useMemo12(
7589
7680
  () => selectedNodeIds.map((id) => live.normalized.nodesById[id]).filter(Boolean),
7590
7681
  [selectedNodeIds, live.normalized]
7591
7682
  );
7592
7683
  const stableOnMutated = useStableRef(onMutated);
7593
7684
  if (!perms.anyEditable) return null;
7594
7685
  return /* @__PURE__ */ jsxs25(Fragment8, { children: [
7595
- /* @__PURE__ */ jsx27(
7686
+ /* @__PURE__ */ jsx28(
7596
7687
  ToolbarButtons,
7597
7688
  {
7598
7689
  perms,
@@ -7600,7 +7691,7 @@ function GraphEditToolbar({
7600
7691
  onOpen: setDialog
7601
7692
  }
7602
7693
  ),
7603
- /* @__PURE__ */ jsx27(
7694
+ /* @__PURE__ */ jsx28(
7604
7695
  ToolbarDialogs,
7605
7696
  {
7606
7697
  perms,
@@ -8086,7 +8177,7 @@ function useFinderState() {
8086
8177
  }
8087
8178
 
8088
8179
  // src/finder/GraphFinder.tsx
8089
- import { Fragment as Fragment9, jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
8180
+ import { Fragment as Fragment9, jsx as jsx29, jsxs as jsxs26 } from "react/jsx-runtime";
8090
8181
  function GraphFinder() {
8091
8182
  const {
8092
8183
  searchMode,
@@ -8108,7 +8199,7 @@ function GraphFinder() {
8108
8199
  };
8109
8200
  const both = searchMode === "both";
8110
8201
  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(
8202
+ /* @__PURE__ */ jsx29("div", { className: "mb-3 flex-shrink-0", children: /* @__PURE__ */ jsx29(
8112
8203
  FormSelect3,
8113
8204
  {
8114
8205
  label: "Search Mode",
@@ -8123,7 +8214,7 @@ function GraphFinder() {
8123
8214
  }
8124
8215
  ) }),
8125
8216
  /* @__PURE__ */ jsxs26("div", { className: "mb-4 flex-shrink-0 space-y-3", children: [
8126
- (searchMode === "expression" || both) && /* @__PURE__ */ jsx28(
8217
+ (searchMode === "expression" || both) && /* @__PURE__ */ jsx29(
8127
8218
  ExpressionInput,
8128
8219
  {
8129
8220
  value: expressionQuery,
@@ -8132,7 +8223,7 @@ function GraphFinder() {
8132
8223
  both
8133
8224
  }
8134
8225
  ),
8135
- (searchMode === "fulltext" || both) && /* @__PURE__ */ jsx28(
8226
+ (searchMode === "fulltext" || both) && /* @__PURE__ */ jsx29(
8136
8227
  FullTextInput,
8137
8228
  {
8138
8229
  value: fulltextQuery,
@@ -8146,10 +8237,10 @@ function GraphFinder() {
8146
8237
  )
8147
8238
  ] }),
8148
8239
  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 })
8240
+ /* @__PURE__ */ jsx29(AlertTitle, { children: "Query Error" }),
8241
+ /* @__PURE__ */ jsx29(AlertDescription, { children: error })
8151
8242
  ] }),
8152
- hasSearched && !error && /* @__PURE__ */ jsx28(ResultsPanel, { results, searchInfo })
8243
+ hasSearched && !error && /* @__PURE__ */ jsx29(ResultsPanel, { results, searchInfo })
8153
8244
  ] });
8154
8245
  }
8155
8246
  function ExpressionInput({
@@ -8159,8 +8250,8 @@ function ExpressionInput({
8159
8250
  both
8160
8251
  }) {
8161
8252
  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(
8253
+ both && /* @__PURE__ */ jsx29("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Expression filter (applied first):" }),
8254
+ /* @__PURE__ */ jsx29(
8164
8255
  FormTextarea,
8165
8256
  {
8166
8257
  placeholder: 'e.g., Person | filter name == "John"',
@@ -8171,7 +8262,7 @@ function ExpressionInput({
8171
8262
  minRows: both ? 2 : 3
8172
8263
  }
8173
8264
  ),
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." })
8265
+ !both && /* @__PURE__ */ jsx29("div", { className: "text-xs text-muted-foreground mt-1", children: "Use pipes (|) to filter and transform. Available: nodes, edges, graph, and each label." })
8175
8266
  ] });
8176
8267
  }
8177
8268
  function FullTextInput({
@@ -8184,8 +8275,8 @@ function FullTextInput({
8184
8275
  statsLabel
8185
8276
  }) {
8186
8277
  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(
8278
+ both && /* @__PURE__ */ jsx29("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Full-text search (on filtered results):" }),
8279
+ /* @__PURE__ */ jsx29(
8189
8280
  FormTextarea,
8190
8281
  {
8191
8282
  placeholder: "e.g., software engineer machine learning",
@@ -8194,7 +8285,7 @@ function FullTextInput({
8194
8285
  onKeyDown,
8195
8286
  autosize: true,
8196
8287
  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 }) }) }),
8288
+ rightSection: /* @__PURE__ */ jsx29(SimpleTooltip5, { label: "Search", children: /* @__PURE__ */ jsx29(Button11, { variant: "ghost", size: "icon", onClick: onSearch, children: /* @__PURE__ */ jsx29(Search, { size: 16 }) }) }),
8198
8289
  rightSectionWidth: 48
8199
8290
  }
8200
8291
  ),
@@ -8217,9 +8308,9 @@ function ResultsPanel({
8217
8308
  " ",
8218
8309
  results.length === 1 ? "item" : "items"
8219
8310
  ] }),
8220
- searchInfo && /* @__PURE__ */ jsx28("div", { className: "text-xs text-muted-foreground mt-1", children: searchInfo })
8311
+ searchInfo && /* @__PURE__ */ jsx29("div", { className: "text-xs text-muted-foreground mt-1", children: searchInfo })
8221
8312
  ] }),
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))) })
8313
+ results.length === 0 ? /* @__PURE__ */ jsx29("div", { className: "text-sm text-muted-foreground italic", children: "No results found" }) : /* @__PURE__ */ jsx29("div", { className: "space-y-2 overflow-y-auto flex-1", children: results.map((item, index) => /* @__PURE__ */ jsx29(ResultItem, { item }, resultKey(item, index))) })
8223
8314
  ] });
8224
8315
  }
8225
8316
  function resultKey(item, index) {
@@ -8232,7 +8323,7 @@ function ResultItem({ item }) {
8232
8323
  const useGraphFocus2 = useWorkspaceGraphFocus();
8233
8324
  const requestFocus = useGraphFocus2((s) => s.requestFocus);
8234
8325
  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) }) });
8326
+ return /* @__PURE__ */ jsx29("div", { className: "p-3 bg-muted/40 rounded border border-border", children: /* @__PURE__ */ jsx29("div", { className: "text-sm font-medium", children: String(item) }) });
8236
8327
  }
8237
8328
  const isNodeItem = isNodeRow(item);
8238
8329
  const isEdgeItem = isEdgeRow(item);
@@ -8241,7 +8332,7 @@ function ResultItem({ item }) {
8241
8332
  if (!isGraphElement) return;
8242
8333
  requestFocus(String(item.id_), isNodeItem ? "node" : "edge", shouldSelect);
8243
8334
  };
8244
- return /* @__PURE__ */ jsx28(
8335
+ return /* @__PURE__ */ jsx29(
8245
8336
  "div",
8246
8337
  {
8247
8338
  className: `p-3 bg-background rounded border border-border transition-colors ${isGraphElement ? "hover:border-primary hover:bg-accent cursor-pointer" : ""}`,
@@ -8251,18 +8342,18 @@ function ResultItem({ item }) {
8251
8342
  children: /* @__PURE__ */ jsxs26("div", { className: "flex items-start justify-between", children: [
8252
8343
  /* @__PURE__ */ jsxs26("div", { className: "flex items-center gap-2", children: [
8253
8344
  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 })
8345
+ /* @__PURE__ */ jsx29("span", { className: "text-xs font-semibold px-2 py-0.5 bg-accent text-accent-foreground rounded", children: item.label_ }),
8346
+ /* @__PURE__ */ jsx29("span", { className: "text-sm font-medium", children: item.displayName })
8256
8347
  ] }),
8257
8348
  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_ }),
8349
+ /* @__PURE__ */ jsx29("span", { className: "text-xs font-semibold px-2 py-0.5 bg-accent text-accent-foreground rounded", children: item.label_ }),
8259
8350
  /* @__PURE__ */ jsxs26("span", { className: "text-xs text-muted-foreground", children: [
8260
8351
  String(item.start_id_),
8261
8352
  " -> ",
8262
8353
  String(item.end_id_)
8263
8354
  ] })
8264
8355
  ] }),
8265
- !isGraphElement && /* @__PURE__ */ jsx28("span", { className: "text-sm font-medium", children: "Object" })
8356
+ !isGraphElement && /* @__PURE__ */ jsx29("span", { className: "text-sm font-medium", children: "Object" })
8266
8357
  ] }),
8267
8358
  isGraphElement && /* @__PURE__ */ jsxs26("span", { className: "text-xs text-muted-foreground", children: [
8268
8359
  "ID: ",
@@ -8272,19 +8363,1091 @@ function ResultItem({ item }) {
8272
8363
  }
8273
8364
  );
8274
8365
  }
8366
+
8367
+ // src/tabular/TabularVisualizer.tsx
8368
+ import { useCallback as useCallback13, useMemo as useMemo16, useState as useState22 } from "react";
8369
+
8370
+ // src/tabular/cellContext.ts
8371
+ import { createContext as createContext3, useContext as useContext3 } from "react";
8372
+ var TabularCellContext = createContext3({});
8373
+ function useTabularCellContext() {
8374
+ return useContext3(TabularCellContext);
8375
+ }
8376
+
8377
+ // src/tabular/columns.ts
8378
+ function isRecordRow(row) {
8379
+ return typeof row === "object" && row !== null && !Array.isArray(row);
8380
+ }
8381
+ function resolveColumnNames(colNames, columnCount) {
8382
+ const headers = namesFromColNames(colNames);
8383
+ while (headers.length < columnCount) {
8384
+ headers.push(`column_${headers.length + 1}`);
8385
+ }
8386
+ return headers;
8387
+ }
8388
+ function namesFromColNames(colNames) {
8389
+ if (!colNames) return [];
8390
+ const entries = Object.entries(colNames);
8391
+ const allNumeric = entries.every(([key]) => !isNaN(parseInt(key, 10)));
8392
+ if (allNumeric) {
8393
+ entries.sort((a, b) => parseInt(a[0], 10) - parseInt(b[0], 10));
8394
+ return entries.map(([, value]) => value);
8395
+ }
8396
+ return entries.map(([key]) => key);
8397
+ }
8398
+ function deriveHeaders(rows, colNames) {
8399
+ if (rows.length === 0) return { headers: [], mode: "value" };
8400
+ const recordKeys = /* @__PURE__ */ new Set();
8401
+ let maxArrayLength = 0;
8402
+ for (const row of rows) {
8403
+ if (isRecordRow(row)) {
8404
+ Object.keys(row).forEach((key) => recordKeys.add(key));
8405
+ } else if (Array.isArray(row)) {
8406
+ maxArrayLength = Math.max(maxArrayLength, row.length);
8407
+ }
8408
+ }
8409
+ if (recordKeys.size > 0) {
8410
+ return { headers: Array.from(recordKeys).sort(), mode: "record" };
8411
+ }
8412
+ if (maxArrayLength > 0) {
8413
+ return {
8414
+ headers: resolveColumnNames(colNames, maxArrayLength),
8415
+ mode: "array"
8416
+ };
8417
+ }
8418
+ return { headers: ["value"], mode: "value" };
8419
+ }
8420
+ function getCellValue(row, header, index, mode) {
8421
+ if (mode === "record" && isRecordRow(row)) return row[header];
8422
+ if (mode === "array" && Array.isArray(row)) return row[index];
8423
+ return row;
8424
+ }
8425
+ function rowCells(row) {
8426
+ if (Array.isArray(row)) return row;
8427
+ if (isRecordRow(row)) return Object.values(row);
8428
+ return [row];
8429
+ }
8430
+
8431
+ // src/tabular/ExportMenu.tsx
8432
+ import { useMemo as useMemo13 } from "react";
8433
+ import { ClipboardCopy, Download } from "lucide-react";
8434
+ import { toast as toast5 } from "sonner";
8435
+ import {
8436
+ Button as Button12,
8437
+ DropdownMenu as DropdownMenu5,
8438
+ DropdownMenuContent as DropdownMenuContent5,
8439
+ DropdownMenuItem as DropdownMenuItem5,
8440
+ DropdownMenuSeparator as DropdownMenuSeparator4,
8441
+ DropdownMenuTrigger as DropdownMenuTrigger5
8442
+ } from "@petrarca/sonnet-ui";
8443
+
8444
+ // src/tabular/exportTabular.ts
8445
+ function escapeDsvCell(value, delimiter) {
8446
+ if (value === null || value === void 0) return "";
8447
+ const str = typeof value === "object" ? JSON.stringify(value) : String(value);
8448
+ const needsQuoting = str.includes(delimiter) || str.includes('"') || str.includes("\n") || str.includes("\r");
8449
+ return needsQuoting ? `"${str.replace(/"/g, '""')}"` : str;
8450
+ }
8451
+ function toDsv(rows, headers, mode, delimiter) {
8452
+ const lines = [
8453
+ headers.map((h) => escapeDsvCell(h, delimiter)).join(delimiter)
8454
+ ];
8455
+ for (const row of rows) {
8456
+ const cells = headers.map(
8457
+ (header, index) => escapeDsvCell(getCellValue(row, header, index, mode), delimiter)
8458
+ );
8459
+ lines.push(cells.join(delimiter));
8460
+ }
8461
+ return lines.join("\n");
8462
+ }
8463
+ function toCsv(rows, headers, mode) {
8464
+ return toDsv(rows, headers, mode, ",");
8465
+ }
8466
+ function toTsv(rows, headers, mode) {
8467
+ return toDsv(rows, headers, mode, " ");
8468
+ }
8469
+ function toJson(rows, headers, mode) {
8470
+ const objects = rows.map((row) => {
8471
+ const obj = {};
8472
+ headers.forEach((header, index) => {
8473
+ obj[header] = getCellValue(row, header, index, mode);
8474
+ });
8475
+ return obj;
8476
+ });
8477
+ return JSON.stringify(objects, null, 2);
8478
+ }
8479
+ function isFlatExportSuitable(rows, headers, mode) {
8480
+ if (rows.length === 0 || headers.length === 0) return false;
8481
+ const sample = rows.slice(0, 3);
8482
+ return sample.some(
8483
+ (row) => headers.some((header, index) => {
8484
+ const value = getCellValue(row, header, index, mode);
8485
+ return value === null || value === void 0 || typeof value !== "object";
8486
+ })
8487
+ );
8488
+ }
8489
+ function downloadFile(content, filename, mimeType) {
8490
+ const blob = new Blob([content], { type: `${mimeType};charset=utf-8` });
8491
+ const url = URL.createObjectURL(blob);
8492
+ const anchor = document.createElement("a");
8493
+ anchor.href = url;
8494
+ anchor.download = filename;
8495
+ anchor.style.display = "none";
8496
+ document.body.appendChild(anchor);
8497
+ anchor.click();
8498
+ setTimeout(() => {
8499
+ document.body.removeChild(anchor);
8500
+ URL.revokeObjectURL(url);
8501
+ }, 100);
8502
+ }
8503
+
8504
+ // src/tabular/ExportMenu.tsx
8505
+ import { Fragment as Fragment10, jsx as jsx30, jsxs as jsxs27 } from "react/jsx-runtime";
8506
+ var CLIPBOARD_ROW_LIMIT = 1e3;
8507
+ function copyToClipboard(content, what) {
8508
+ navigator.clipboard.writeText(content).then(() => toast5.success(`Copied as ${what} to clipboard`)).catch(() => toast5.error("Failed to copy to clipboard"));
8509
+ }
8510
+ function ExportMenu({
8511
+ rows,
8512
+ headerInfo,
8513
+ fileName
8514
+ }) {
8515
+ const { headers, mode } = headerInfo;
8516
+ const flat = useMemo13(
8517
+ () => isFlatExportSuitable(rows, headers, mode),
8518
+ [rows, headers, mode]
8519
+ );
8520
+ const clipboard = rows.length <= CLIPBOARD_ROW_LIMIT;
8521
+ return /* @__PURE__ */ jsxs27(DropdownMenu5, { children: [
8522
+ /* @__PURE__ */ jsx30(DropdownMenuTrigger5, { asChild: true, children: /* @__PURE__ */ jsxs27(Button12, { variant: "outline", size: "sm", className: "gap-1.5 text-xs", children: [
8523
+ /* @__PURE__ */ jsx30(Download, { className: "h-3.5 w-3.5" }),
8524
+ "Export"
8525
+ ] }) }),
8526
+ /* @__PURE__ */ jsxs27(DropdownMenuContent5, { align: "end", children: [
8527
+ flat && /* @__PURE__ */ jsxs27(Fragment10, { children: [
8528
+ /* @__PURE__ */ jsxs27(
8529
+ DropdownMenuItem5,
8530
+ {
8531
+ onClick: () => downloadFile(
8532
+ toCsv(rows, headers, mode),
8533
+ `${fileName}.csv`,
8534
+ "text/csv"
8535
+ ),
8536
+ children: [
8537
+ /* @__PURE__ */ jsx30(Download, {}),
8538
+ "Download CSV"
8539
+ ]
8540
+ }
8541
+ ),
8542
+ /* @__PURE__ */ jsxs27(
8543
+ DropdownMenuItem5,
8544
+ {
8545
+ onClick: () => downloadFile(
8546
+ toTsv(rows, headers, mode),
8547
+ `${fileName}.tsv`,
8548
+ "text/tab-separated-values"
8549
+ ),
8550
+ children: [
8551
+ /* @__PURE__ */ jsx30(Download, {}),
8552
+ "Download TSV"
8553
+ ]
8554
+ }
8555
+ )
8556
+ ] }),
8557
+ /* @__PURE__ */ jsxs27(
8558
+ DropdownMenuItem5,
8559
+ {
8560
+ onClick: () => downloadFile(
8561
+ toJson(rows, headers, mode),
8562
+ `${fileName}.json`,
8563
+ "application/json"
8564
+ ),
8565
+ children: [
8566
+ /* @__PURE__ */ jsx30(Download, {}),
8567
+ "Download JSON"
8568
+ ]
8569
+ }
8570
+ ),
8571
+ clipboard && /* @__PURE__ */ jsxs27(Fragment10, { children: [
8572
+ /* @__PURE__ */ jsx30(DropdownMenuSeparator4, {}),
8573
+ flat && /* @__PURE__ */ jsxs27(
8574
+ DropdownMenuItem5,
8575
+ {
8576
+ onClick: () => copyToClipboard(toTsv(rows, headers, mode), "TSV"),
8577
+ children: [
8578
+ /* @__PURE__ */ jsx30(ClipboardCopy, {}),
8579
+ "Copy as TSV"
8580
+ ]
8581
+ }
8582
+ ),
8583
+ /* @__PURE__ */ jsxs27(
8584
+ DropdownMenuItem5,
8585
+ {
8586
+ onClick: () => copyToClipboard(toJson(rows, headers, mode), "JSON"),
8587
+ children: [
8588
+ /* @__PURE__ */ jsx30(ClipboardCopy, {}),
8589
+ "Copy as JSON"
8590
+ ]
8591
+ }
8592
+ )
8593
+ ] })
8594
+ ] })
8595
+ ] });
8596
+ }
8597
+
8598
+ // src/tabular/GraphInspector.tsx
8599
+ import { useMemo as useMemo14 } from "react";
8600
+ import { Copy as Copy2 } from "lucide-react";
8601
+ import { toast as toast6 } from "sonner";
8602
+ import {
8603
+ Button as Button13,
8604
+ JsonTree,
8605
+ PropertyList as PropertyList3,
8606
+ ScrollArea,
8607
+ Sheet as Sheet6,
8608
+ SheetBody as SheetBody6,
8609
+ SheetContent as SheetContent6,
8610
+ SheetDescription as SheetDescription6,
8611
+ SheetHeader as SheetHeader6,
8612
+ SheetTitle as SheetTitle6,
8613
+ Tabs,
8614
+ TabsContent,
8615
+ TabsList,
8616
+ TabsTrigger
8617
+ } from "@petrarca/sonnet-ui";
8618
+
8619
+ // src/tabular/graphCell.ts
8620
+ var CAPTION_PRIORITY = [
8621
+ "name",
8622
+ "title",
8623
+ "label",
8624
+ "displayName",
8625
+ "symbol",
8626
+ "id",
8627
+ "code"
8628
+ ];
8629
+ function asGraphCell(value) {
8630
+ if (!value || typeof value !== "object") return null;
8631
+ const obj = value;
8632
+ if (obj.node && typeof obj.node === "object") {
8633
+ return { kind: "node", data: obj.node };
8634
+ }
8635
+ if (obj.edge && typeof obj.edge === "object") {
8636
+ return { kind: "edge", data: obj.edge };
8637
+ }
8638
+ return null;
8639
+ }
8640
+ function captionFor(cell, labelDisplayMap) {
8641
+ const { data } = cell;
8642
+ const config = labelDisplayMap?.[data.label_];
8643
+ if (config) {
8644
+ const resolved = extractDisplayLabel(data, config);
8645
+ if (resolved && resolved !== data.label_) return resolved;
8646
+ }
8647
+ const props = data.properties_ ?? {};
8648
+ const key = CAPTION_PRIORITY.find((k) => props[k] != null && props[k] !== "");
8649
+ if (key) return String(props[key]);
8650
+ return data.id_ != null ? `${data.label_} #${data.id_}` : data.label_;
8651
+ }
8652
+ function propertyCount(cell) {
8653
+ return Object.keys(cell.data.properties_ ?? {}).length;
8654
+ }
8655
+ function colorForLabel(label, palette) {
8656
+ if (!palette) return "hsl(var(--muted-foreground))";
8657
+ return palette[label] ?? palette.neutral ?? "hsl(var(--muted-foreground))";
8658
+ }
8659
+
8660
+ // src/tabular/GraphInspector.tsx
8661
+ import { Fragment as Fragment11, jsx as jsx31, jsxs as jsxs28 } from "react/jsx-runtime";
8662
+ function copy(text, what) {
8663
+ navigator.clipboard.writeText(text).then(() => toast6.success(`Copied ${what}`)).catch(() => toast6.error("Failed to copy"));
8664
+ }
8665
+ function InspectorTitle({
8666
+ cell,
8667
+ color
8668
+ }) {
8669
+ return /* @__PURE__ */ jsxs28(SheetTitle6, { className: "flex items-center gap-2 text-base", children: [
8670
+ cell.kind === "node" ? /* @__PURE__ */ jsx31(
8671
+ "span",
8672
+ {
8673
+ "aria-hidden": "true",
8674
+ className: "h-3 w-3 shrink-0 rounded-full",
8675
+ style: { backgroundColor: color }
8676
+ }
8677
+ ) : /* @__PURE__ */ jsx31("span", { "aria-hidden": "true", className: "text-sm", style: { color }, children: "\u2192" }),
8678
+ /* @__PURE__ */ jsx31("span", { className: "font-semibold", children: cell.data.label_ }),
8679
+ cell.data.id_ != null && /* @__PURE__ */ jsxs28("span", { className: "text-xs font-normal text-muted-foreground", children: [
8680
+ "#",
8681
+ cell.data.id_
8682
+ ] })
8683
+ ] });
8684
+ }
8685
+ function InspectorBody({ cell }) {
8686
+ return /* @__PURE__ */ jsx31(SheetBody6, { className: "flex-1 min-h-0 p-0", children: /* @__PURE__ */ jsxs28(Tabs, { defaultValue: "properties", className: "flex h-full flex-col", children: [
8687
+ /* @__PURE__ */ jsxs28(TabsList, { className: "mx-6", children: [
8688
+ /* @__PURE__ */ jsx31(TabsTrigger, { value: "properties", children: "Properties" }),
8689
+ /* @__PURE__ */ jsx31(TabsTrigger, { value: "json", children: "JSON" })
8690
+ ] }),
8691
+ /* @__PURE__ */ jsx31(TabsContent, { value: "properties", className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx31(ScrollArea, { className: "h-full px-6 py-3", children: /* @__PURE__ */ jsx31(PropertyList3, { properties: cell.data.properties_ ?? {} }) }) }),
8692
+ /* @__PURE__ */ jsx31(TabsContent, { value: "json", className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx31(ScrollArea, { className: "h-full px-6 py-3", children: /* @__PURE__ */ jsx31("div", { className: "text-sm font-mono", children: /* @__PURE__ */ jsx31(JsonTree, { data: cell.data }) }) }) })
8693
+ ] }) });
8694
+ }
8695
+ function GraphInspector({
8696
+ cell,
8697
+ open,
8698
+ onOpenChange,
8699
+ palette,
8700
+ labelDisplayMap
8701
+ }) {
8702
+ const caption = useMemo14(
8703
+ () => cell ? captionFor(cell, labelDisplayMap) : "",
8704
+ [cell, labelDisplayMap]
8705
+ );
8706
+ return /* @__PURE__ */ jsx31(Sheet6, { open, onOpenChange, children: /* @__PURE__ */ jsx31(
8707
+ SheetContent6,
8708
+ {
8709
+ side: "right",
8710
+ className: "w-[420px] sm:max-w-[420px] flex flex-col gap-0",
8711
+ children: cell && /* @__PURE__ */ jsxs28(Fragment11, { children: [
8712
+ /* @__PURE__ */ jsxs28(SheetHeader6, { children: [
8713
+ /* @__PURE__ */ jsx31(
8714
+ InspectorTitle,
8715
+ {
8716
+ cell,
8717
+ color: colorForLabel(cell.data.label_, palette)
8718
+ }
8719
+ ),
8720
+ /* @__PURE__ */ jsx31(SheetDescription6, { className: "truncate", children: caption })
8721
+ ] }),
8722
+ /* @__PURE__ */ jsxs28("div", { className: "flex items-center gap-2 px-6 pb-2", children: [
8723
+ /* @__PURE__ */ jsxs28(
8724
+ Button13,
8725
+ {
8726
+ variant: "outline",
8727
+ size: "sm",
8728
+ className: "h-7 gap-1.5 text-xs",
8729
+ onClick: () => copy(JSON.stringify(cell.data, null, 2), "as JSON"),
8730
+ children: [
8731
+ /* @__PURE__ */ jsx31(Copy2, { className: "h-3 w-3" }),
8732
+ "Copy JSON"
8733
+ ]
8734
+ }
8735
+ ),
8736
+ cell.data.id_ != null && /* @__PURE__ */ jsxs28(
8737
+ Button13,
8738
+ {
8739
+ variant: "outline",
8740
+ size: "sm",
8741
+ className: "h-7 gap-1.5 text-xs",
8742
+ onClick: () => copy(String(cell.data.id_), "id"),
8743
+ children: [
8744
+ /* @__PURE__ */ jsx31(Copy2, { className: "h-3 w-3" }),
8745
+ "Copy id"
8746
+ ]
8747
+ }
8748
+ )
8749
+ ] }),
8750
+ cell.kind === "edge" && /* @__PURE__ */ jsxs28("div", { className: "px-6 pb-2 text-xs text-muted-foreground", children: [
8751
+ cell.data.start_id_,
8752
+ " ",
8753
+ /* @__PURE__ */ jsx31("span", { className: "text-foreground", children: "\u2192" }),
8754
+ " ",
8755
+ cell.data.end_id_
8756
+ ] }),
8757
+ /* @__PURE__ */ jsx31(InspectorBody, { cell })
8758
+ ] })
8759
+ }
8760
+ ) });
8761
+ }
8762
+
8763
+ // src/tabular/PaginationFooter.tsx
8764
+ import { ChevronLeft, ChevronRight } from "lucide-react";
8765
+ import { Button as Button14 } from "@petrarca/sonnet-ui";
8766
+
8767
+ // src/tabular/pagination.ts
8768
+ function pageLabels(page, rowsOnPage) {
8769
+ const end = page.offset + rowsOnPage;
8770
+ const range = rowsOnPage > 0 ? `${(page.offset + 1).toLocaleString()}\u2013${end.toLocaleString()}` : "0";
8771
+ const total = page.total != null ? page.total.toLocaleString() : `${end.toLocaleString()}+`;
8772
+ return { range, total };
8773
+ }
8774
+
8775
+ // src/tabular/PaginationFooter.tsx
8776
+ import { jsx as jsx32, jsxs as jsxs29 } from "react/jsx-runtime";
8777
+ function PageSizeSelect({
8778
+ size,
8779
+ options,
8780
+ onChange
8781
+ }) {
8782
+ return /* @__PURE__ */ jsxs29("label", { className: "flex items-center gap-1.5", children: [
8783
+ /* @__PURE__ */ jsx32("span", { children: "Rows" }),
8784
+ /* @__PURE__ */ jsx32(
8785
+ "select",
8786
+ {
8787
+ className: "rounded border border-border bg-background px-1.5 py-0.5 text-foreground",
8788
+ value: size,
8789
+ onChange: (e) => onChange(Number(e.target.value)),
8790
+ children: options.map((opt) => /* @__PURE__ */ jsx32("option", { value: opt, children: opt }, opt))
8791
+ }
8792
+ )
8793
+ ] });
8794
+ }
8795
+ function PageNav({
8796
+ pageIndex,
8797
+ hasMore,
8798
+ onPageChange
8799
+ }) {
8800
+ return /* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-1", children: [
8801
+ /* @__PURE__ */ jsx32(
8802
+ Button14,
8803
+ {
8804
+ variant: "outline",
8805
+ size: "sm",
8806
+ className: "h-7 gap-1 px-2",
8807
+ disabled: pageIndex <= 0,
8808
+ onClick: () => onPageChange(pageIndex - 1),
8809
+ "aria-label": "Previous page",
8810
+ children: /* @__PURE__ */ jsx32(ChevronLeft, { className: "h-3.5 w-3.5" })
8811
+ }
8812
+ ),
8813
+ /* @__PURE__ */ jsxs29("span", { className: "px-1", children: [
8814
+ "Page ",
8815
+ pageIndex + 1
8816
+ ] }),
8817
+ /* @__PURE__ */ jsx32(
8818
+ Button14,
8819
+ {
8820
+ variant: "outline",
8821
+ size: "sm",
8822
+ className: "h-7 gap-1 px-2",
8823
+ disabled: !hasMore,
8824
+ onClick: () => onPageChange(pageIndex + 1),
8825
+ "aria-label": "Next page",
8826
+ children: /* @__PURE__ */ jsx32(ChevronRight, { className: "h-3.5 w-3.5" })
8827
+ }
8828
+ )
8829
+ ] });
8830
+ }
8831
+ function PaginationFooter({
8832
+ page,
8833
+ rowsOnPage,
8834
+ controls
8835
+ }) {
8836
+ if (!page) return null;
8837
+ const { range, total } = pageLabels(page, rowsOnPage);
8838
+ const {
8839
+ pageIndex,
8840
+ pageSize,
8841
+ pageSizeOptions,
8842
+ onPageChange,
8843
+ onPageSizeChange
8844
+ } = controls;
8845
+ return /* @__PURE__ */ jsxs29("div", { className: "flex items-center justify-between gap-4 border-t border-border px-3 py-2 text-xs text-muted-foreground shrink-0", children: [
8846
+ /* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-2", children: [
8847
+ /* @__PURE__ */ jsxs29("span", { children: [
8848
+ "Showing ",
8849
+ /* @__PURE__ */ jsx32("span", { className: "text-foreground", children: range }),
8850
+ " of",
8851
+ " ",
8852
+ /* @__PURE__ */ jsx32("span", { className: "text-foreground", children: total })
8853
+ ] }),
8854
+ page.truncated && /* @__PURE__ */ jsx32("span", { className: "text-amber-600 dark:text-amber-400", children: "(capped)" })
8855
+ ] }),
8856
+ onPageChange && /* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-3", children: [
8857
+ pageSizeOptions && onPageSizeChange && /* @__PURE__ */ jsx32(
8858
+ PageSizeSelect,
8859
+ {
8860
+ size: pageSize ?? page.limit,
8861
+ options: pageSizeOptions,
8862
+ onChange: onPageSizeChange
8863
+ }
8864
+ ),
8865
+ /* @__PURE__ */ jsx32(
8866
+ PageNav,
8867
+ {
8868
+ pageIndex,
8869
+ hasMore: page.has_more,
8870
+ onPageChange
8871
+ }
8872
+ )
8873
+ ] })
8874
+ ] });
8875
+ }
8876
+
8877
+ // src/tabular/JsonCell.tsx
8878
+ import React18 from "react";
8879
+ import { escapeHtml, formatJsonValue } from "@petrarca/sonnet-core";
8880
+
8881
+ // src/tabular/GraphChip.tsx
8882
+ import React17, { useMemo as useMemo15 } from "react";
8883
+ import { ArrowRight as ArrowRight3 } from "lucide-react";
8884
+ import { SimpleTooltip as SimpleTooltip6 } from "@petrarca/sonnet-ui";
8885
+ import { jsx as jsx33, jsxs as jsxs30 } from "react/jsx-runtime";
8886
+ function GraphChipImpl({ cell }) {
8887
+ const { palette, labelDisplayMap, onInspect } = useTabularCellContext();
8888
+ const caption = useMemo15(
8889
+ () => captionFor(cell, labelDisplayMap),
8890
+ [cell, labelDisplayMap]
8891
+ );
8892
+ const extraProps = useMemo15(() => propertyCount(cell), [cell]);
8893
+ const color = colorForLabel(cell.data.label_, palette);
8894
+ const isNode = cell.kind === "node";
8895
+ const idSuffix = cell.data.id_ != null ? ` #${cell.data.id_}` : "";
8896
+ const propsLabel = `${extraProps} ${extraProps === 1 ? "property" : "properties"}`;
8897
+ return /* @__PURE__ */ jsx33(SimpleTooltip6, { label: `${cell.data.label_}${idSuffix} \xB7 ${caption}`, children: /* @__PURE__ */ jsxs30(
8898
+ "button",
8899
+ {
8900
+ type: "button",
8901
+ onClick: () => onInspect?.(cell),
8902
+ "aria-label": `${isNode ? "Node" : "Relationship"} ${cell.data.label_}, ${caption}, ${propsLabel}. Activate to inspect.`,
8903
+ className: "inline-flex max-w-full items-center gap-1.5 rounded-full border border-border bg-card px-2 py-0.5 text-xs hover:bg-state-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
8904
+ children: [
8905
+ isNode ? /* @__PURE__ */ jsx33(
8906
+ "span",
8907
+ {
8908
+ "aria-hidden": "true",
8909
+ className: "h-2.5 w-2.5 shrink-0 rounded-full",
8910
+ style: { backgroundColor: color }
8911
+ }
8912
+ ) : /* @__PURE__ */ jsx33(
8913
+ ArrowRight3,
8914
+ {
8915
+ "aria-hidden": "true",
8916
+ className: "h-3 w-3 shrink-0",
8917
+ style: { color }
8918
+ }
8919
+ ),
8920
+ /* @__PURE__ */ jsx33("span", { className: "shrink-0 font-semibold text-foreground", children: cell.data.label_ }),
8921
+ /* @__PURE__ */ jsx33("span", { className: "truncate text-muted-foreground", children: caption }),
8922
+ extraProps > 0 && /* @__PURE__ */ jsxs30("span", { className: "shrink-0 text-tertiary-foreground", children: [
8923
+ "+",
8924
+ extraProps
8925
+ ] })
8926
+ ]
8927
+ }
8928
+ ) });
8929
+ }
8930
+ var GraphChip = React17.memo(GraphChipImpl);
8931
+
8932
+ // src/tabular/JsonCell.tsx
8933
+ import { jsx as jsx34, jsxs as jsxs31 } from "react/jsx-runtime";
8934
+ function ScalarValue({
8935
+ value
8936
+ }) {
8937
+ if (value === null || value === void 0) {
8938
+ return /* @__PURE__ */ jsx34("span", { style: { color: "var(--json-null)" }, children: "null" });
8939
+ }
8940
+ if (typeof value === "string") {
8941
+ return /* @__PURE__ */ jsxs31("span", { style: { color: "var(--json-string)" }, children: [
8942
+ '"',
8943
+ escapeHtml(value),
8944
+ '"'
8945
+ ] });
8946
+ }
8947
+ if (typeof value === "number") {
8948
+ return /* @__PURE__ */ jsx34("span", { style: { color: "var(--json-number)" }, children: value });
8949
+ }
8950
+ return /* @__PURE__ */ jsx34("span", { style: { color: "var(--json-boolean)" }, children: value ? "true" : "false" });
8951
+ }
8952
+ function JsonRenderer({
8953
+ value,
8954
+ className = "max-w-sm"
8955
+ }) {
8956
+ if (value === null || typeof value !== "object") {
8957
+ return /* @__PURE__ */ jsx34(
8958
+ ScalarValue,
8959
+ {
8960
+ value
8961
+ }
8962
+ );
8963
+ }
8964
+ return /* @__PURE__ */ jsx34(
8965
+ "div",
8966
+ {
8967
+ className: `text-sm font-mono whitespace-pre-wrap break-words ${className}`,
8968
+ dangerouslySetInnerHTML: { __html: formatJsonValue(value) }
8969
+ }
8970
+ );
8971
+ }
8972
+ function JsonCellImpl({ value }) {
8973
+ const graphCell = asGraphCell(value);
8974
+ if (graphCell) return /* @__PURE__ */ jsx34(GraphChip, { cell: graphCell });
8975
+ return /* @__PURE__ */ jsx34(JsonRenderer, { value });
8976
+ }
8977
+ var JsonCell = React18.memo(JsonCellImpl);
8978
+
8979
+ // src/tabular/tableColumns.tsx
8980
+ import { jsx as jsx35 } from "react/jsx-runtime";
8981
+ var COLUMN_SIZING = {
8982
+ record: { size: 220, minSize: 140, maxSize: 280 },
8983
+ array: { size: 200, minSize: 140, maxSize: 260 },
8984
+ value: { size: 200, minSize: 140, maxSize: 260 }
8985
+ };
8986
+ function buildColumns(headerInfo, rowOffset, sortingEnabled) {
8987
+ const { headers, mode } = headerInfo;
8988
+ const sizing = COLUMN_SIZING[mode];
8989
+ const dataColumns = headers.map((header, index) => ({
8990
+ id: header,
8991
+ header,
8992
+ accessorFn: (row) => getCellValue(row, header, index, mode),
8993
+ cell: ({ getValue }) => /* @__PURE__ */ jsx35(JsonCell, { value: getValue() }),
8994
+ enableSorting: sortingEnabled,
8995
+ ...sizing
8996
+ }));
8997
+ return [
8998
+ {
8999
+ id: "__index",
9000
+ header: "#",
9001
+ enableSorting: false,
9002
+ size: 48,
9003
+ minSize: 40,
9004
+ maxSize: 64,
9005
+ cell: ({ row }) => /* @__PURE__ */ jsx35("span", { className: "text-muted-foreground", children: rowOffset + row.index + 1 })
9006
+ },
9007
+ ...dataColumns
9008
+ ];
9009
+ }
9010
+
9011
+ // src/tabular/VirtualTable.tsx
9012
+ import { useRef as useRef10, useState as useState21 } from "react";
9013
+ import {
9014
+ flexRender,
9015
+ getCoreRowModel,
9016
+ getSortedRowModel,
9017
+ useReactTable
9018
+ } from "@tanstack/react-table";
9019
+ import { useVirtualizer as useVirtualizer2 } from "@tanstack/react-virtual";
9020
+ import { jsx as jsx36, jsxs as jsxs32 } from "react/jsx-runtime";
9021
+ var ROW_HEIGHT = 41;
9022
+ var HEADER_PADDING = { index: "px-2 py-2", data: "px-4 py-2" };
9023
+ var CELL_PADDING = { index: "px-2 py-3", data: "px-4 py-3" };
9024
+ function padding(table, columnId) {
9025
+ return columnId === "__index" ? table.index : table.data;
9026
+ }
9027
+ function SortIndicator({
9028
+ sorted
9029
+ }) {
9030
+ if (sorted === "asc") return /* @__PURE__ */ jsx36("span", { className: "text-muted-foreground", children: "\u25B2" });
9031
+ if (sorted === "desc")
9032
+ return /* @__PURE__ */ jsx36("span", { className: "text-muted-foreground", children: "\u25BC" });
9033
+ return null;
9034
+ }
9035
+ function HeaderCell({
9036
+ header
9037
+ }) {
9038
+ const width = { width: `${header.getSize()}px` };
9039
+ if (header.isPlaceholder)
9040
+ return /* @__PURE__ */ jsx36("th", { className: "border-r border-border", style: width });
9041
+ const sortable = header.column.getCanSort();
9042
+ const sorted = header.column.getIsSorted();
9043
+ const ariaSort = sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : "none";
9044
+ return /* @__PURE__ */ jsx36(
9045
+ "th",
9046
+ {
9047
+ scope: "col",
9048
+ "aria-sort": sortable ? ariaSort : void 0,
9049
+ className: `text-left text-xs font-semibold tracking-wide border-r border-border ${padding(HEADER_PADDING, header.id)} ${sortable ? "cursor-pointer select-none" : ""}`,
9050
+ onClick: sortable ? header.column.getToggleSortingHandler() : void 0,
9051
+ style: width,
9052
+ children: /* @__PURE__ */ jsxs32("span", { className: "inline-flex items-center gap-1", children: [
9053
+ flexRender(header.column.columnDef.header, header.getContext()),
9054
+ /* @__PURE__ */ jsx36(SortIndicator, { sorted })
9055
+ ] })
9056
+ }
9057
+ );
9058
+ }
9059
+ function SpacerRow({
9060
+ height,
9061
+ colSpan
9062
+ }) {
9063
+ if (height <= 0) return null;
9064
+ return /* @__PURE__ */ jsx36("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx36("td", { style: { height: `${height}px` }, colSpan }) });
9065
+ }
9066
+ function VirtualTable({
9067
+ rows,
9068
+ columns
9069
+ }) {
9070
+ const [sorting, setSorting] = useState21([]);
9071
+ const [columnSizing, setColumnSizing] = useState21({});
9072
+ const table = useReactTable({
9073
+ data: rows,
9074
+ columns,
9075
+ state: { sorting, columnSizing },
9076
+ onSortingChange: setSorting,
9077
+ onColumnSizingChange: setColumnSizing,
9078
+ enableSortingRemoval: true,
9079
+ // 'onEnd' commits the width once, instead of re-rendering cells per pixel.
9080
+ columnResizeMode: "onEnd",
9081
+ getCoreRowModel: getCoreRowModel(),
9082
+ getSortedRowModel: getSortedRowModel()
9083
+ });
9084
+ const tableRows = table.getRowModel().rows;
9085
+ const scrollRef = useRef10(null);
9086
+ const virtualizer = useVirtualizer2({
9087
+ count: tableRows.length,
9088
+ getScrollElement: () => scrollRef.current,
9089
+ estimateSize: () => ROW_HEIGHT,
9090
+ overscan: 12
9091
+ });
9092
+ const virtualRows = virtualizer.getVirtualItems();
9093
+ const paddingTop = virtualRows.length > 0 ? virtualRows[0].start : 0;
9094
+ const paddingBottom = virtualRows.length > 0 ? virtualizer.getTotalSize() - virtualRows[virtualRows.length - 1].end : 0;
9095
+ return /* @__PURE__ */ jsx36("div", { ref: scrollRef, className: "flex-1 overflow-auto px-3 min-h-0", children: /* @__PURE__ */ jsxs32("table", { className: "min-w-full table-fixed border border-border border-collapse text-sm font-mono text-foreground shadow-sm", children: [
9096
+ /* @__PURE__ */ jsx36("thead", { className: "sticky top-0 z-10 bg-muted text-muted-foreground", children: table.getHeaderGroups().map((group) => /* @__PURE__ */ jsx36("tr", { className: "border-b border-border", children: group.headers.map((header) => /* @__PURE__ */ jsx36(HeaderCell, { header }, header.id)) }, group.id)) }),
9097
+ /* @__PURE__ */ jsxs32("tbody", { children: [
9098
+ /* @__PURE__ */ jsx36(SpacerRow, { height: paddingTop, colSpan: columns.length }),
9099
+ virtualRows.map((virtualRow) => {
9100
+ const row = tableRows[virtualRow.index];
9101
+ return /* @__PURE__ */ jsx36(
9102
+ "tr",
9103
+ {
9104
+ "data-index": virtualRow.index,
9105
+ ref: virtualizer.measureElement,
9106
+ className: "border-b border-border",
9107
+ children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx36(
9108
+ "td",
9109
+ {
9110
+ className: `align-top border-r border-border bg-background ${padding(CELL_PADDING, cell.column.id)}`,
9111
+ style: { width: `${cell.column.getSize()}px` },
9112
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
9113
+ },
9114
+ cell.id
9115
+ ))
9116
+ },
9117
+ row.id
9118
+ );
9119
+ }),
9120
+ /* @__PURE__ */ jsx36(SpacerRow, { height: paddingBottom, colSpan: columns.length })
9121
+ ] })
9122
+ ] }) });
9123
+ }
9124
+
9125
+ // src/tabular/TabularVisualizer.tsx
9126
+ import { jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
9127
+ function paletteFromRows(rows) {
9128
+ const nodes = [];
9129
+ for (const row of rows) {
9130
+ for (const cell of rowCells(row)) {
9131
+ const gc = asGraphCell(cell);
9132
+ if (gc?.kind === "node") nodes.push({ label_: gc.data.label_ });
9133
+ }
9134
+ }
9135
+ return buildColorPaletteFromNodes(nodes);
9136
+ }
9137
+ function useInspector() {
9138
+ const [cell, setCell] = useState22(null);
9139
+ const [open, setOpen] = useState22(false);
9140
+ const onInspect = useCallback13((next) => {
9141
+ setCell(next);
9142
+ setOpen(true);
9143
+ }, []);
9144
+ return { cell, open, setOpen, onInspect };
9145
+ }
9146
+ function TabularVisualizer({
9147
+ result,
9148
+ labelDisplayMap,
9149
+ exportFileName = "query-result",
9150
+ pageIndex = 0,
9151
+ ...controls
9152
+ }) {
9153
+ const rows = useMemo16(() => result?.rows ?? [], [result]);
9154
+ const page = result?.page ?? null;
9155
+ const headerInfo = useMemo16(
9156
+ () => deriveHeaders(rows, result?.colNames),
9157
+ [rows, result?.colNames]
9158
+ );
9159
+ const palette = useMemo16(() => paletteFromRows(rows), [rows]);
9160
+ const inspector = useInspector();
9161
+ const { onInspect } = inspector;
9162
+ const cellContext = useMemo16(
9163
+ () => ({ palette, labelDisplayMap, onInspect }),
9164
+ [palette, labelDisplayMap, onInspect]
9165
+ );
9166
+ const rowOffset = page?.offset ?? 0;
9167
+ const columns = useMemo16(
9168
+ () => buildColumns(headerInfo, rowOffset, !page),
9169
+ [headerInfo, rowOffset, page]
9170
+ );
9171
+ if (!result)
9172
+ return /* @__PURE__ */ jsx37(CenteredMessage, { children: "No tabular data available." });
9173
+ if (rows.length === 0)
9174
+ return /* @__PURE__ */ jsx37(CenteredMessage, { children: "Result set is empty." });
9175
+ return /* @__PURE__ */ jsx37(TabularCellContext.Provider, { value: cellContext, children: /* @__PURE__ */ jsxs33("div", { className: "w-full h-full flex flex-col bg-background", children: [
9176
+ /* @__PURE__ */ jsx37("div", { className: "flex justify-end px-3 pt-3 pb-2 shrink-0", children: /* @__PURE__ */ jsx37(
9177
+ ExportMenu,
9178
+ {
9179
+ rows,
9180
+ headerInfo,
9181
+ fileName: exportFileName
9182
+ }
9183
+ ) }),
9184
+ /* @__PURE__ */ jsx37(VirtualTable, { rows, columns }),
9185
+ /* @__PURE__ */ jsx37(
9186
+ PaginationFooter,
9187
+ {
9188
+ page,
9189
+ rowsOnPage: rows.length,
9190
+ controls: { pageIndex, ...controls }
9191
+ }
9192
+ ),
9193
+ /* @__PURE__ */ jsx37(
9194
+ GraphInspector,
9195
+ {
9196
+ cell: inspector.cell,
9197
+ open: inspector.open,
9198
+ onOpenChange: inspector.setOpen,
9199
+ palette,
9200
+ labelDisplayMap
9201
+ }
9202
+ )
9203
+ ] }) });
9204
+ }
9205
+
9206
+ // src/tabular/ResultViewTabs.tsx
9207
+ import { Tabs as Tabs2, TabsList as TabsList2, TabsTrigger as TabsTrigger2 } from "@petrarca/sonnet-ui";
9208
+ import { jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
9209
+ function ResultViewTabs({
9210
+ value,
9211
+ onChange
9212
+ }) {
9213
+ return /* @__PURE__ */ jsx38(Tabs2, { value, onValueChange: (v) => onChange(v), children: /* @__PURE__ */ jsxs34(TabsList2, { children: [
9214
+ /* @__PURE__ */ jsx38(TabsTrigger2, { value: "graph", children: "Graph View" }),
9215
+ /* @__PURE__ */ jsx38(TabsTrigger2, { value: "table", children: "Table View" })
9216
+ ] }) });
9217
+ }
9218
+
9219
+ // src/tabular/resultView.ts
9220
+ import { useState as useState23 } from "react";
9221
+ function classifyResult(isGraph, rowCount) {
9222
+ if (isGraph) return "graph";
9223
+ return rowCount > 0 ? "tabular" : "empty";
9224
+ }
9225
+ function isViewValid(view, kind) {
9226
+ return view === "table" || kind !== "tabular";
9227
+ }
9228
+ function naturalView(kind) {
9229
+ return kind === "graph" ? "graph" : "table";
9230
+ }
9231
+ function useResultView(kind, resultKey2, { initialView } = {}) {
9232
+ const [state, setState] = useState23(() => ({
9233
+ key: resultKey2,
9234
+ view: initialView ?? naturalView(kind)
9235
+ }));
9236
+ let view = state.view;
9237
+ if (state.key !== resultKey2) {
9238
+ const firstResult = state.key == null && resultKey2 != null;
9239
+ view = firstResult ? naturalView(kind) : state.view;
9240
+ if (!isViewValid(view, kind)) view = naturalView(kind);
9241
+ setState({ key: resultKey2, view });
9242
+ }
9243
+ const setView = (next) => setState((s) => ({ ...s, view: next }));
9244
+ return [view, setView];
9245
+ }
9246
+
9247
+ // src/workspace/panes.ts
9248
+ function choosePane(view, kind) {
9249
+ if (view === "graph") {
9250
+ return isViewValid("graph", kind) ? "graph" : "graph-unavailable";
9251
+ }
9252
+ return kind === "empty" ? "table-empty" : "table";
9253
+ }
9254
+
9255
+ // src/workspace/ResultWorkspace.tsx
9256
+ import { jsx as jsx39, jsxs as jsxs35 } from "react/jsx-runtime";
9257
+ function ResultWorkspace({
9258
+ kind,
9259
+ resultKey: resultKey2,
9260
+ graphPane,
9261
+ tablePane,
9262
+ view: controlledView,
9263
+ onViewChange,
9264
+ initialView
9265
+ }) {
9266
+ const [ownView, setOwnView] = useResultView(kind, resultKey2, { initialView });
9267
+ const view = controlledView ?? ownView;
9268
+ const setView = (next) => {
9269
+ if (next === view) return;
9270
+ setOwnView(next);
9271
+ onViewChange?.(next);
9272
+ };
9273
+ const pane = choosePane(view, kind);
9274
+ return (
9275
+ // contain-inline-size: the workspace fills the width it is given and never
9276
+ // asks its parent for more. Without it a wide table (long names, virtualized
9277
+ // so it changes as you scroll) widens any shrink-to-fit ancestor -- e.g. a
9278
+ // scroll area's display:table wrapper -- and pushes the whole page sideways.
9279
+ /* @__PURE__ */ jsxs35("div", { className: "flex flex-col w-full h-full min-h-0 min-w-0 [contain:inline-size]", children: [
9280
+ /* @__PURE__ */ jsx39("div", { className: "shrink-0", children: /* @__PURE__ */ jsx39(ResultViewTabs, { value: view, onChange: setView }) }),
9281
+ /* @__PURE__ */ jsxs35("div", { className: "flex-1 overflow-hidden flex min-h-0", children: [
9282
+ pane === "graph" && graphPane,
9283
+ pane === "graph-unavailable" && /* @__PURE__ */ jsx39(CenteredMessage, { children: "This result cannot be displayed in the graph view." }),
9284
+ pane === "table" && tablePane,
9285
+ pane === "table-empty" && /* @__PURE__ */ jsx39(CenteredMessage, { children: "No results to display." })
9286
+ ] })
9287
+ ] })
9288
+ );
9289
+ }
9290
+
9291
+ // src/workspace/GraphPane.tsx
9292
+ import { ResizeHandle as ResizeHandle2 } from "@petrarca/sonnet-ui";
9293
+
9294
+ // src/workspace/usePanelResize.ts
9295
+ import { useCallback as useCallback14, useEffect as useEffect12, useRef as useRef11, useState as useState24 } from "react";
9296
+ function usePanelResize({
9297
+ defaultWidth,
9298
+ minWidth,
9299
+ maxWidth
9300
+ }) {
9301
+ const [width, setWidth] = useState24(defaultWidth);
9302
+ const dragging = useRef11(false);
9303
+ const startX = useRef11(0);
9304
+ const startWidth = useRef11(0);
9305
+ const finish = useCallback14(() => {
9306
+ if (!dragging.current) return;
9307
+ dragging.current = false;
9308
+ document.body.classList.remove("select-none");
9309
+ }, []);
9310
+ useEffect12(() => {
9311
+ const clamp = (v) => Math.min(Math.max(v, minWidth), maxWidth);
9312
+ const onMove = (e) => {
9313
+ if (!dragging.current) return;
9314
+ setWidth(clamp(startWidth.current - (e.clientX - startX.current)));
9315
+ };
9316
+ const onKey = (e) => {
9317
+ if (e.key !== "Escape" || !dragging.current) return;
9318
+ setWidth(startWidth.current);
9319
+ finish();
9320
+ };
9321
+ window.addEventListener("pointermove", onMove);
9322
+ window.addEventListener("pointerup", finish);
9323
+ window.addEventListener("pointercancel", finish);
9324
+ window.addEventListener("keydown", onKey);
9325
+ return () => {
9326
+ window.removeEventListener("pointermove", onMove);
9327
+ window.removeEventListener("pointerup", finish);
9328
+ window.removeEventListener("pointercancel", finish);
9329
+ window.removeEventListener("keydown", onKey);
9330
+ };
9331
+ }, [finish, minWidth, maxWidth]);
9332
+ const onPointerDown = useCallback14(
9333
+ (e) => {
9334
+ dragging.current = true;
9335
+ startX.current = e.clientX;
9336
+ startWidth.current = width;
9337
+ document.body.classList.add("select-none");
9338
+ },
9339
+ [width]
9340
+ );
9341
+ const onDoubleClick = useCallback14(
9342
+ () => setWidth(defaultWidth),
9343
+ [defaultWidth]
9344
+ );
9345
+ return { width, onPointerDown, onDoubleClick };
9346
+ }
9347
+
9348
+ // src/workspace/GraphPane.tsx
9349
+ import { Fragment as Fragment12, jsx as jsx40, jsxs as jsxs36 } from "react/jsx-runtime";
9350
+ var PANEL_DEFAULT_WIDTH = 320;
9351
+ var PANEL_MIN_WIDTH = 260;
9352
+ var PANEL_MAX_WIDTH = 640;
9353
+ function ActionPanelSlot({
9354
+ actions
9355
+ }) {
9356
+ const visible = useActionPanelStore((s) => s.state) === "visible";
9357
+ const { width, onPointerDown, onDoubleClick } = usePanelResize({
9358
+ defaultWidth: PANEL_DEFAULT_WIDTH,
9359
+ minWidth: PANEL_MIN_WIDTH,
9360
+ maxWidth: PANEL_MAX_WIDTH
9361
+ });
9362
+ if (!visible) return null;
9363
+ return /* @__PURE__ */ jsxs36(Fragment12, { children: [
9364
+ /* @__PURE__ */ jsx40(
9365
+ ResizeHandle2,
9366
+ {
9367
+ orientation: "vertical",
9368
+ "aria-valuenow": width,
9369
+ "aria-valuemin": PANEL_MIN_WIDTH,
9370
+ "aria-valuemax": PANEL_MAX_WIDTH,
9371
+ tabIndex: 0,
9372
+ onPointerDown,
9373
+ onDoubleClick
9374
+ }
9375
+ ),
9376
+ /* @__PURE__ */ jsx40("div", { className: "flex-shrink-0 min-h-0 overflow-hidden", style: { width }, children: /* @__PURE__ */ jsx40(ActionPanel_default, { actions }) })
9377
+ ] });
9378
+ }
9379
+ function GraphPane({
9380
+ ready = true,
9381
+ loadingLabel = "Preparing graph\u2026",
9382
+ banner,
9383
+ actions,
9384
+ defaultRenderer = "cytoscape",
9385
+ onRendererChange,
9386
+ ...visualizerProps
9387
+ }) {
9388
+ return /* @__PURE__ */ jsxs36(
9389
+ GraphRendererProvider,
9390
+ {
9391
+ defaultRenderer,
9392
+ onRendererChange,
9393
+ children: [
9394
+ ready && /* @__PURE__ */ jsx40(GraphA11yList, {}),
9395
+ /* @__PURE__ */ jsxs36("div", { className: "flex-1 flex overflow-hidden min-h-0", children: [
9396
+ !ready && /* @__PURE__ */ jsx40(CenteredMessage, { children: loadingLabel }),
9397
+ ready && /* @__PURE__ */ jsxs36("div", { className: "relative flex-1 min-w-0 overflow-hidden", children: [
9398
+ banner,
9399
+ /* @__PURE__ */ jsx40(GraphVisualizer_default, { ...visualizerProps })
9400
+ ] }),
9401
+ ready && /* @__PURE__ */ jsx40(ActionPanelSlot, { actions })
9402
+ ] })
9403
+ ]
9404
+ }
9405
+ );
9406
+ }
9407
+
9408
+ // src/workspace/LargeResultBanner.tsx
9409
+ import { Info } from "lucide-react";
9410
+ import { formatNumber as formatNumber2 } from "@petrarca/sonnet-core";
9411
+ import { jsx as jsx41, jsxs as jsxs37 } from "react/jsx-runtime";
9412
+ function LargeResultBanner({
9413
+ page
9414
+ }) {
9415
+ if (!page || !page.has_more) return null;
9416
+ const returned = page.returned ?? page.limit;
9417
+ const total = page.total != null ? formatNumber2(page.total) : `${formatNumber2(returned)}+`;
9418
+ return /* @__PURE__ */ jsx41("div", { className: "pointer-events-none absolute left-1/2 top-3 z-10 -translate-x-1/2", children: /* @__PURE__ */ jsxs37("div", { className: "pointer-events-auto flex items-center gap-2 rounded-md border border-border bg-card/95 px-3 py-1.5 text-xs text-muted-foreground shadow-sm backdrop-blur", children: [
9419
+ /* @__PURE__ */ jsx41(Info, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
9420
+ /* @__PURE__ */ jsxs37("span", { children: [
9421
+ "Showing first",
9422
+ " ",
9423
+ /* @__PURE__ */ jsx41("span", { className: "text-foreground", children: formatNumber2(returned) }),
9424
+ " of",
9425
+ " ",
9426
+ /* @__PURE__ */ jsx41("span", { className: "text-foreground", children: total }),
9427
+ " results \u2014 refine your query or add filters to focus."
9428
+ ] })
9429
+ ] }) });
9430
+ }
8275
9431
  export {
8276
9432
  ActionPanel_default as ActionPanel,
9433
+ CenteredMessage,
8277
9434
  DeleteNodeDialog,
8278
9435
  EMPTY_NORMALIZED,
8279
9436
  EdgeFormSheet,
8280
9437
  EdgeInfoCard_default as EdgeInfoCard,
8281
9438
  GraphA11yList,
9439
+ GraphChip,
8282
9440
  GraphEditToolbar,
8283
9441
  GraphFinder,
9442
+ GraphInspector,
8284
9443
  GraphModelEditor,
9444
+ GraphPane,
8285
9445
  GraphRendererProvider,
8286
9446
  GraphStoresContext,
8287
9447
  GraphVisualizer_default as GraphVisualizer,
9448
+ JsonCell,
9449
+ JsonRenderer,
9450
+ LargeResultBanner,
8288
9451
  ModelActionPanel,
8289
9452
  NodeFormSheet,
8290
9453
  NodeInfoCard_default as NodeInfoCard,
@@ -8295,14 +9458,22 @@ export {
8295
9458
  QueryEditor,
8296
9459
  QueryHistory,
8297
9460
  RelationFormSheet,
9461
+ ResultViewTabs,
9462
+ ResultWorkspace,
9463
+ TabularVisualizer,
8298
9464
  TypeDetailPanel,
8299
9465
  addEdgeType,
8300
9466
  addNodeType,
8301
9467
  addRelation,
9468
+ asGraphCell,
8302
9469
  buildColorPalette,
8303
9470
  buildColorPaletteFromNodes,
8304
9471
  capabilitiesSchema,
9472
+ captionFor,
8305
9473
  cardinalityLabel,
9474
+ choosePane,
9475
+ classifyResult,
9476
+ colorForLabel,
8306
9477
  createActionPanelStore,
8307
9478
  createGraphDataStore,
8308
9479
  createGraphExplorationStore,
@@ -8316,6 +9487,8 @@ export {
8316
9487
  deleteNodeType,
8317
9488
  deleteRelation,
8318
9489
  deleteRelationProperty,
9490
+ deriveHeaders,
9491
+ downloadFile,
8319
9492
  edgeTypeView,
8320
9493
  editableRelations,
8321
9494
  enrichEdge,
@@ -8329,20 +9502,25 @@ export {
8329
9502
  findEdgeType,
8330
9503
  findType,
8331
9504
  flattenGraphData,
9505
+ getCellValue,
8332
9506
  graphCapabilitiesSchema,
8333
9507
  groupEdgesByRelation,
8334
9508
  hasProperties,
8335
9509
  hasRelationFields,
8336
9510
  initializeFormData,
8337
9511
  isEdgeType,
9512
+ isFlatExportSuitable,
8338
9513
  isNodeType,
8339
9514
  isRawGraphNode,
8340
9515
  isRelationField,
9516
+ isViewValid,
8341
9517
  isVisualEdge,
8342
9518
  isVisualNode,
8343
9519
  modelToGraph,
9520
+ naturalView,
8344
9521
  nodeTypeView,
8345
9522
  normalizeGraph,
9523
+ propertyCount,
8346
9524
  reconcileFilter,
8347
9525
  relationFieldName,
8348
9526
  relationTitle,
@@ -8351,15 +9529,19 @@ export {
8351
9529
  renameEdgeType,
8352
9530
  renameNodeType,
8353
9531
  resolveCapabilities,
9532
+ resolveColumnNames,
8354
9533
  resolveGraphCapabilities,
8355
9534
  resolveRelationEdge,
8356
9535
  setEdgeTypeProperty,
8357
9536
  setNodeProperty,
8358
9537
  setRelationProperty,
9538
+ toCsv,
8359
9539
  toCytoscapeEdge,
8360
9540
  toCytoscapeNode,
9541
+ toJson,
8361
9542
  toReagraphEdge,
8362
9543
  toReagraphNode,
9544
+ toTsv,
8363
9545
  typeLabel,
8364
9546
  updateRelation,
8365
9547
  useActionPanelStore,
@@ -8369,6 +9551,7 @@ export {
8369
9551
  useGraphSearch,
8370
9552
  useLiveGraphData,
8371
9553
  useReagraphData,
9554
+ useResultView,
8372
9555
  useStableRef,
8373
9556
  useWorkspaceGraphData,
8374
9557
  useWorkspaceGraphExploration,