modviz 0.1.2

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.
Files changed (109) hide show
  1. package/README.md +261 -0
  2. package/dist/client/_shell.html +0 -0
  3. package/dist/client/android-chrome-192x192.png +0 -0
  4. package/dist/client/android-chrome-512x512.png +0 -0
  5. package/dist/client/apple-touch-icon.png +0 -0
  6. package/dist/client/assets/app-Sjrldkrg.css +2 -0
  7. package/dist/client/assets/button-aOWckyNs.js +1 -0
  8. package/dist/client/assets/check-C0EQe2S8.js +1 -0
  9. package/dist/client/assets/chevron-down-DrspihmT.js +1 -0
  10. package/dist/client/assets/chevron-right-DIJHr8AN.js +1 -0
  11. package/dist/client/assets/colors-CQoWjU5E.js +1 -0
  12. package/dist/client/assets/command-kkF7_wdz.js +45 -0
  13. package/dist/client/assets/compare-K6jVFsiI.js +1 -0
  14. package/dist/client/assets/compare-TOnoe1EP.js +2 -0
  15. package/dist/client/assets/configure-DnlSnhtN.js +1 -0
  16. package/dist/client/assets/explorer-C7NclVKg.js +2 -0
  17. package/dist/client/assets/explorer-Xu2X6XXF.js +1 -0
  18. package/dist/client/assets/external-link-B9eNA-li.js +1 -0
  19. package/dist/client/assets/flamegraph-CRVZSAlj.js +13 -0
  20. package/dist/client/assets/floating-ui.dom-DLIT5tPE.js +1 -0
  21. package/dist/client/assets/formatting-CiC0SYI8.js +1 -0
  22. package/dist/client/assets/graph-6Vr74V1k.js +2 -0
  23. package/dist/client/assets/graph-CVzypIGU.js +2 -0
  24. package/dist/client/assets/graph-command-menu-D2MoVT2B.js +4 -0
  25. package/dist/client/assets/graph-command-menu-VWiiW3qy.css +1 -0
  26. package/dist/client/assets/hierarchy-C8xxGb_u.js +2 -0
  27. package/dist/client/assets/hierarchy-iO7d4oSK.js +2 -0
  28. package/dist/client/assets/import-display-D-jRyyjM.js +5 -0
  29. package/dist/client/assets/imports-CPggnrs-.js +2 -0
  30. package/dist/client/assets/imports-CodbPyUJ.js +1 -0
  31. package/dist/client/assets/index-Dj_rhLdR.js +12 -0
  32. package/dist/client/assets/input-BCFMF0aR.js +1 -0
  33. package/dist/client/assets/jsx-runtime-DWSWI4JT.js +1 -0
  34. package/dist/client/assets/lazyRouteComponent-PTSyFp1J.js +1 -0
  35. package/dist/client/assets/loading-state-CyC_hrTF.js +1 -0
  36. package/dist/client/assets/modviz-data-BiRqoDI5.js +1 -0
  37. package/dist/client/assets/modviz-layout-Do93E-IB.js +1 -0
  38. package/dist/client/assets/modviz-sigma-Xl8qHaxK.js +312 -0
  39. package/dist/client/assets/portal-BgAm3V3j.js +1 -0
  40. package/dist/client/assets/routes-DBtN8hrZ.js +1 -0
  41. package/dist/client/assets/schemas-B4zfTepZ.js +39 -0
  42. package/dist/client/assets/search-BYHxNrYn.js +1 -0
  43. package/dist/client/assets/search-params-BaZRBvGI.js +1 -0
  44. package/dist/client/assets/setup-view-j1o0TuZz.js +1 -0
  45. package/dist/client/assets/summary-D703Zh3x.js +1 -0
  46. package/dist/client/assets/tooltip-B1VDU9HG.js +1 -0
  47. package/dist/client/assets/trace-B67CM5s2.js +2 -0
  48. package/dist/client/assets/trace-Bwwdw3AM.js +1 -0
  49. package/dist/client/assets/treemap-BZf2shzY.js +5 -0
  50. package/dist/client/assets/treemap-Csroy8Gy.js +2 -0
  51. package/dist/client/assets/utils-DkkZd0ys.js +1 -0
  52. package/dist/client/favicon-16x16.png +0 -0
  53. package/dist/client/favicon-32x32.png +0 -0
  54. package/dist/client/favicon.ico +0 -0
  55. package/dist/client/favicon.png +0 -0
  56. package/dist/client/site.webmanifest +19 -0
  57. package/dist/mod/cli-options.js +225 -0
  58. package/dist/mod/cli.js +519 -0
  59. package/dist/mod/index.js +3 -0
  60. package/dist/mod/llm-analysis.js +29 -0
  61. package/dist/mod/llm-output.js +742 -0
  62. package/dist/mod/module-graph-plugins.js +60 -0
  63. package/dist/mod/production-server.js +103 -0
  64. package/dist/mod/runtime-host.js +217 -0
  65. package/dist/mod/snapshot-history.js +73 -0
  66. package/dist/mod/types.js +3 -0
  67. package/dist/server/assets/__23tanstack-start-plugin-adapters-3QxJs4a0.js +5 -0
  68. package/dist/server/assets/_tanstack-start-manifest_v-DMytuIue.js +188 -0
  69. package/dist/server/assets/button-Bqnnid5i.js +41 -0
  70. package/dist/server/assets/colors-DhAxrYua.js +100 -0
  71. package/dist/server/assets/command-SdxShIbL.js +138 -0
  72. package/dist/server/assets/compare-BFMiiUsB.js +562 -0
  73. package/dist/server/assets/compare-CpOqTpYu.js +10 -0
  74. package/dist/server/assets/configure-Bvd45DTI.js +288 -0
  75. package/dist/server/assets/explorer-C7dODpSv.js +379 -0
  76. package/dist/server/assets/explorer-CpSb0JTa.js +20 -0
  77. package/dist/server/assets/flamegraph-CdW-VG6I.js +198 -0
  78. package/dist/server/assets/formatting-iDlL4tA-.js +4 -0
  79. package/dist/server/assets/graph-C1G9H5O4.js +438 -0
  80. package/dist/server/assets/graph-DAGFGioS.js +45 -0
  81. package/dist/server/assets/graph-command-menu-BV5GtOWx.js +249 -0
  82. package/dist/server/assets/hierarchy-B4K-Zfn9.js +16 -0
  83. package/dist/server/assets/hierarchy-BGpWSG-f.js +104 -0
  84. package/dist/server/assets/import-display-BVIOWcsm.js +124 -0
  85. package/dist/server/assets/imports-B6JBDl_h.js +379 -0
  86. package/dist/server/assets/imports-BGe5tZJT.js +28 -0
  87. package/dist/server/assets/input-C5r-hBix.js +19 -0
  88. package/dist/server/assets/loading-state-CrvCWTtw.js +23 -0
  89. package/dist/server/assets/modviz-data-CUyTorv0.js +197 -0
  90. package/dist/server/assets/modviz-layout-BAH2ogse.js +253 -0
  91. package/dist/server/assets/modviz-server-DoMlAyFW.js +195 -0
  92. package/dist/server/assets/modviz-sigma-XYxARWqd.js +1441 -0
  93. package/dist/server/assets/rolldown-runtime-rSIU-vHC.js +13 -0
  94. package/dist/server/assets/router-DYJ-zDbU.js +353 -0
  95. package/dist/server/assets/routes-DInCacpY.js +244 -0
  96. package/dist/server/assets/search-params-BNApPgkX.js +26 -0
  97. package/dist/server/assets/setup-view-DjI49Iqr.js +91 -0
  98. package/dist/server/assets/start-Ba3KII43.js +4 -0
  99. package/dist/server/assets/summary-z3lXkLCQ.js +208 -0
  100. package/dist/server/assets/tooltip-Ck0DDfF7.js +24 -0
  101. package/dist/server/assets/trace-ColKOf9g.js +16 -0
  102. package/dist/server/assets/trace-eVs-hIZO.js +578 -0
  103. package/dist/server/assets/treemap-BbZ9M4GF.js +17 -0
  104. package/dist/server/assets/treemap-CrgWFoCF.js +912 -0
  105. package/dist/server/assets/utils-BQZm0uva.js +8 -0
  106. package/dist/server/server.js +5259 -0
  107. package/dist/shared/modviz-compare.js +120 -0
  108. package/dist/shared/modviz-trace.js +244 -0
  109. package/package.json +135 -0
@@ -0,0 +1,198 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-rSIU-vHC.js";
2
+ import { useEffect, useRef } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { Flamegraph, normalizeTreeNode } from "nanovis";
5
+ //#region src/components/graph/map-to-flamegraph.ts
6
+ function convertToNanovisHierarchyData(output, entryNodeId, options = {}) {
7
+ const maxDepth = options.maxDepth ?? 6;
8
+ const maxChildren = options.maxChildren ?? 24;
9
+ const includeExternal = options.includeExternal ?? true;
10
+ const entryNode = output.nodes.find((node) => node.path === entryNodeId);
11
+ const nodeMap = /* @__PURE__ */ new Map();
12
+ output.nodes.forEach((node) => {
13
+ nodeMap.set(node.path, {
14
+ id: node.path,
15
+ text: node.path.split("/").slice(-2).join("/"),
16
+ subtext: node.package?.name,
17
+ sizeSelf: node.importees.length + 1,
18
+ children: [],
19
+ meta: {
20
+ path: node.path,
21
+ type: node.type,
22
+ package: node.package?.name,
23
+ importees: node.importees,
24
+ imports: node.imports.length,
25
+ exports: node.exports.length,
26
+ originalNode: node
27
+ }
28
+ });
29
+ });
30
+ if (!entryNode) return {
31
+ id: "module-graph-root",
32
+ text: "Module Graph",
33
+ subtext: "No entrypoints found",
34
+ sizeSelf: 1,
35
+ children: [],
36
+ meta: {
37
+ path: "",
38
+ importees: [],
39
+ imports: 0,
40
+ exports: 0,
41
+ originalNode: {}
42
+ }
43
+ };
44
+ const rootNode = Object.assign({}, nodeMap.get(entryNodeId), { children: [] });
45
+ const allVisited = /* @__PURE__ */ new Set();
46
+ const stack = [{
47
+ depth: 0,
48
+ nodePath: entryNodeId,
49
+ parent: rootNode,
50
+ visited: new Set([entryNodeId])
51
+ }];
52
+ let stackSize = 0;
53
+ const childrenMap = /* @__PURE__ */ new Map();
54
+ while (stack.length > 0) {
55
+ stackSize++;
56
+ const { depth, nodePath, parent, visited } = stack.pop();
57
+ const wasAlreadyVisited = allVisited.has(nodePath);
58
+ allVisited.add(nodePath);
59
+ const node = nodeMap.get(nodePath);
60
+ if (!node) continue;
61
+ const currentTreeNode = Object.assign({}, node, { children: wasAlreadyVisited ? childrenMap.get(nodePath) : [] });
62
+ !wasAlreadyVisited && childrenMap.set(nodePath, currentTreeNode.children);
63
+ if (parent.children) parent.children.push(currentTreeNode);
64
+ if (wasAlreadyVisited) continue;
65
+ if (depth >= maxDepth) continue;
66
+ const importees = currentTreeNode.meta.importees.filter((importee) => includeExternal || !importee.includes("node_modules")).sort((left, right) => {
67
+ const leftNode = nodeMap.get(left);
68
+ return (nodeMap.get(right)?.meta.importees.length ?? 0) - (leftNode?.meta.importees.length ?? 0);
69
+ });
70
+ const visibleImportees = importees.slice(0, maxChildren);
71
+ visibleImportees.forEach((importee) => {
72
+ if (visited.has(importee)) {
73
+ const importeeNode = nodeMap.get(importee);
74
+ if (!importeeNode) return;
75
+ const importeeTreeNode = Object.assign({}, importeeNode, { children: [] });
76
+ currentTreeNode.children.push(importeeTreeNode);
77
+ return;
78
+ }
79
+ visited.add(importee);
80
+ stack.push({
81
+ depth: depth + 1,
82
+ nodePath: importee,
83
+ parent: currentTreeNode,
84
+ visited: new Set([...visited, importee])
85
+ });
86
+ });
87
+ if (importees.length > visibleImportees.length) currentTreeNode.children.push({
88
+ id: `${nodePath}#truncated`,
89
+ text: `… ${importees.length - visibleImportees.length} more`,
90
+ subtext: "pruned",
91
+ sizeSelf: 1,
92
+ children: [],
93
+ meta: {
94
+ path: `${nodePath}#truncated`,
95
+ importees: [],
96
+ imports: 0,
97
+ exports: 0,
98
+ originalNode: currentTreeNode.meta.originalNode
99
+ }
100
+ });
101
+ }
102
+ return rootNode;
103
+ }
104
+ //#endregion
105
+ //#region src/components/graph/flamegraph.tsx
106
+ var flamegraph_exports = /* @__PURE__ */ __exportAll({ Flamegraph: () => Flamegraph$1 });
107
+ var Flamegraph$1 = (props) => {
108
+ const containerRef = useRef(null);
109
+ const flamegraphRef = useRef(null);
110
+ const tooltipRef = useRef(null);
111
+ useEffect(() => {
112
+ if (!containerRef.current) return;
113
+ const entryNodeId = props.entryNodeId ?? props.output.metadata.entrypoints[0];
114
+ const flamegraph = new Flamegraph(normalizeTreeNode(convertToNanovisHierarchyData(props.output, entryNodeId, props.options)));
115
+ flamegraphRef.current = flamegraph;
116
+ const unsubSelect = flamegraph.events.on("select", (node) => {
117
+ console.log("Selected node:", {
118
+ text: node?.text,
119
+ subtext: node?.subtext,
120
+ meta: node?.meta
121
+ });
122
+ });
123
+ const unsubHover = flamegraph.events.on("hover", (node, event) => {
124
+ if (!tooltipRef.current) return;
125
+ if (node && node.meta) {
126
+ tooltipRef.current.style.display = "block";
127
+ if (event) {
128
+ tooltipRef.current.style.left = `${event.clientX + 10}px`;
129
+ tooltipRef.current.style.top = `${event.clientY - 10}px`;
130
+ }
131
+ tooltipRef.current.innerHTML = `
132
+ <div style="font-weight: bold; margin-bottom: 8px; color: #4CAF50;">
133
+ ${node.text || "root"}
134
+ </div>
135
+ ${node.meta.path ? `<div style="color: #ddd; font-size: 10px; margin-bottom: 4px;">${node.meta.path}</div>` : ""}
136
+ <div style="margin-bottom: 4px;">
137
+ Size: <span style="color: #FFD700;">${node.sizeSelf || 0}</span>
138
+ </div>
139
+ ${node.meta.type ? `<div style="margin-bottom: 4px;">Type: <span style="color: #87CEEB;">${node.meta.type}</span></div>` : ""}
140
+ ${node.meta.package ? `<div style="margin-bottom: 4px;">Package: <span style="color: #DDA0DD;">${node.meta.package}</span></div>` : ""}
141
+ ${node.meta.imports !== void 0 ? `<div style="margin-bottom: 4px;">Imports: <span style="color: #F0E68C;">${node.meta.imports}</span></div>` : ""}
142
+ ${node.meta.exports !== void 0 ? `<div>Exports: <span style="color: #F0E68C;">${node.meta.exports}</span></div>` : ""}
143
+ `;
144
+ } else tooltipRef.current.style.display = "none";
145
+ });
146
+ containerRef.current.innerHTML = "";
147
+ containerRef.current.appendChild(flamegraph.el);
148
+ return () => {
149
+ unsubSelect();
150
+ unsubHover();
151
+ if (flamegraphRef.current) flamegraphRef.current = null;
152
+ };
153
+ }, [
154
+ props.entryNodeId,
155
+ props.options,
156
+ props.output
157
+ ]);
158
+ return /* @__PURE__ */ jsxs("div", {
159
+ className: "relative w-full h-full min-h-0 flex flex-col",
160
+ children: [
161
+ /* @__PURE__ */ jsx("div", {
162
+ ref: containerRef,
163
+ className: "flex-1 min-h-0",
164
+ style: { minHeight: "400px" }
165
+ }),
166
+ /* @__PURE__ */ jsx("div", {
167
+ ref: tooltipRef,
168
+ style: {
169
+ position: "fixed",
170
+ background: "rgba(0, 0, 0, 0.9)",
171
+ color: "white",
172
+ padding: "12px",
173
+ borderRadius: "6px",
174
+ fontSize: "12px",
175
+ fontFamily: "monospace",
176
+ maxWidth: "300px",
177
+ boxShadow: "0 4px 8px rgba(0,0,0,0.3)",
178
+ pointerEvents: "none",
179
+ zIndex: 1e3,
180
+ display: "none"
181
+ }
182
+ }),
183
+ /* @__PURE__ */ jsxs("div", {
184
+ className: "absolute bottom-4 left-4 bg-black bg-opacity-70 text-white p-3 rounded text-xs font-mono z-10",
185
+ children: [
186
+ /* @__PURE__ */ jsx("div", {
187
+ className: "font-bold mb-2",
188
+ children: "Module Graph Flamegraph"
189
+ }),
190
+ /* @__PURE__ */ jsx("div", { children: "Click: Select node" }),
191
+ /* @__PURE__ */ jsx("div", { children: "Hover: View details" })
192
+ ]
193
+ })
194
+ ]
195
+ });
196
+ };
197
+ //#endregion
198
+ export { flamegraph_exports as n, Flamegraph$1 as t };
@@ -0,0 +1,4 @@
1
+ //#region src/utils/formatting.ts
2
+ var formatNumber = new Intl.NumberFormat("en-US");
3
+ //#endregion
4
+ export { formatNumber as t };
@@ -0,0 +1,438 @@
1
+ import { l as isNodeDetailsOpenAtom, o as currentNodeIdAtom, s as highlightedNodeIdAtom, t as GraphCommandMenu, u as selectedNodeIdsAtom } from "./graph-command-menu-BV5GtOWx.js";
2
+ import { t as Button } from "./button-Bqnnid5i.js";
3
+ import { t as Input } from "./input-C5r-hBix.js";
4
+ import { d as getWorkspacePackageNames, f as isModvizBundleReady, l as getNodeGroupingLabel, m as useModvizBundle, o as filterNodesByScope } from "./modviz-data-CUyTorv0.js";
5
+ import { t as LoadingState } from "./loading-state-CrvCWTtw.js";
6
+ import { n as getDefaultGraphLayoutSettings, t as Route$1 } from "./graph-DAGFGioS.js";
7
+ import { t as ModvizLayout } from "./modviz-layout-BAH2ogse.js";
8
+ import { t as SetupView } from "./setup-view-DjI49Iqr.js";
9
+ import { Suspense, lazy, useEffect, useMemo, useState } from "react";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ import { Eye, RotateCcw, Settings2, X } from "lucide-react";
12
+ import { Portal } from "@ark-ui/react/portal";
13
+ import { useAtom } from "@xstate/store/react";
14
+ import { FloatingPanel } from "@ark-ui/react/floating-panel";
15
+ //#region src/components/graph/graph-settings-panel.tsx
16
+ var areGraphSettingsEqual = (left, right) => {
17
+ return left.iterations === right.iterations && left.gravity === right.gravity && left.scalingRatio === right.scalingRatio && left.strongGravityMode === right.strongGravityMode && left.linLogMode === right.linLogMode && left.adjustSizes === right.adjustSizes && left.outboundAttractionDistribution === right.outboundAttractionDistribution && left.hideClusterLabels === right.hideClusterLabels && left.nodeSizeScale === right.nodeSizeScale;
18
+ };
19
+ function NumberField(props) {
20
+ return /* @__PURE__ */ jsxs("label", {
21
+ className: "space-y-2 text-sm text-slate-700 dark:text-slate-200",
22
+ children: [
23
+ /* @__PURE__ */ jsxs("span", {
24
+ className: "flex items-center justify-between gap-3 font-medium",
25
+ children: [/* @__PURE__ */ jsx("span", { children: props.label }), /* @__PURE__ */ jsx("span", {
26
+ className: "text-xs text-slate-500 dark:text-slate-400",
27
+ children: props.value
28
+ })]
29
+ }),
30
+ /* @__PURE__ */ jsx(Input, {
31
+ type: "number",
32
+ value: String(props.value),
33
+ min: props.min,
34
+ max: props.max,
35
+ step: props.step,
36
+ onChange: (event) => props.onChange(Number(event.currentTarget.value))
37
+ }),
38
+ /* @__PURE__ */ jsx("p", {
39
+ className: "text-xs leading-5 text-slate-500 dark:text-slate-400",
40
+ children: props.help
41
+ })
42
+ ]
43
+ });
44
+ }
45
+ function ToggleField(props) {
46
+ return /* @__PURE__ */ jsxs("label", {
47
+ className: "flex items-start gap-3 rounded-2xl border border-slate-200/70 bg-slate-50/80 p-3 dark:border-slate-800 dark:bg-slate-900/70",
48
+ children: [/* @__PURE__ */ jsx("input", {
49
+ type: "checkbox",
50
+ className: "mt-1 size-4 rounded border-slate-300",
51
+ checked: props.checked,
52
+ onChange: (event) => props.onChange(event.currentTarget.checked)
53
+ }), /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
54
+ className: "block text-sm font-medium text-slate-800 dark:text-slate-100",
55
+ children: props.label
56
+ }), /* @__PURE__ */ jsx("span", {
57
+ className: "mt-1 block text-xs leading-5 text-slate-500 dark:text-slate-400",
58
+ children: props.help
59
+ })] })]
60
+ });
61
+ }
62
+ function GraphSettingsPanel(props) {
63
+ if (!props.open) return null;
64
+ return /* @__PURE__ */ jsx(GraphSettingsPanelContent, { ...props }, JSON.stringify(props.settings));
65
+ }
66
+ function GraphSettingsPanelContent(props) {
67
+ const [draftSettings, setDraftSettings] = useState(props.settings);
68
+ const hasChanges = useMemo(() => !areGraphSettingsEqual(draftSettings, props.settings), [draftSettings, props.settings]);
69
+ const updateSetting = (key, value) => {
70
+ setDraftSettings({
71
+ ...draftSettings,
72
+ [key]: value
73
+ });
74
+ };
75
+ const applyChanges = () => {
76
+ if (!hasChanges) return;
77
+ props.onSettingsChange(draftSettings);
78
+ };
79
+ const resetSettings = () => {
80
+ setDraftSettings(props.onReset());
81
+ };
82
+ return /* @__PURE__ */ jsx(FloatingPanel.Root, {
83
+ open: true,
84
+ defaultSize: {
85
+ width: 420,
86
+ height: 640
87
+ },
88
+ minSize: {
89
+ width: 360,
90
+ height: 520
91
+ },
92
+ persistRect: true,
93
+ lazyMount: true,
94
+ children: /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(FloatingPanel.Positioner, {
95
+ className: "z-50",
96
+ children: /* @__PURE__ */ jsxs(FloatingPanel.Content, {
97
+ className: "flex h-full flex-col overflow-hidden rounded-[24px] border border-slate-200 bg-white shadow-[0_24px_80px_-36px_rgba(15,23,42,0.75)] dark:border-slate-800 dark:bg-slate-950",
98
+ children: [/* @__PURE__ */ jsx(FloatingPanel.DragTrigger, { children: /* @__PURE__ */ jsxs(FloatingPanel.Header, {
99
+ className: "flex cursor-move items-center justify-between border-b border-slate-200 bg-slate-50/90 px-4 py-3 dark:border-slate-800 dark:bg-slate-900/80",
100
+ children: [/* @__PURE__ */ jsxs("div", {
101
+ className: "flex items-center gap-2",
102
+ children: [/* @__PURE__ */ jsx("div", {
103
+ className: "rounded-xl bg-sky-100 p-2 text-sky-700 dark:bg-sky-500/15 dark:text-sky-300",
104
+ children: /* @__PURE__ */ jsx(Settings2, { className: "size-4" })
105
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(FloatingPanel.Title, {
106
+ className: "text-sm font-semibold text-slate-900 dark:text-slate-100",
107
+ children: "Graph settings"
108
+ }), /* @__PURE__ */ jsx("p", {
109
+ className: "text-xs text-slate-500 dark:text-slate-400",
110
+ children: "Layout controls for cluster spacing, gravity, and legibility."
111
+ })] })]
112
+ }), /* @__PURE__ */ jsxs("div", {
113
+ className: "flex items-center gap-2",
114
+ children: [
115
+ /* @__PURE__ */ jsxs(Button, {
116
+ variant: "outline",
117
+ size: "sm",
118
+ onClick: resetSettings,
119
+ children: [/* @__PURE__ */ jsx(RotateCcw, { className: "size-4" }), "Reset"]
120
+ }),
121
+ /* @__PURE__ */ jsx(Button, {
122
+ size: "sm",
123
+ onClick: applyChanges,
124
+ disabled: !hasChanges,
125
+ children: "Apply"
126
+ }),
127
+ /* @__PURE__ */ jsx(Button, {
128
+ variant: "ghost",
129
+ size: "icon",
130
+ onClick: () => props.onOpenChange(false),
131
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
132
+ })
133
+ ]
134
+ })]
135
+ }) }), /* @__PURE__ */ jsx(FloatingPanel.Body, {
136
+ className: "flex-1 overflow-y-auto px-4 py-4",
137
+ children: /* @__PURE__ */ jsxs("div", {
138
+ className: "space-y-5",
139
+ children: [
140
+ /* @__PURE__ */ jsx(NumberField, {
141
+ label: "Iterations",
142
+ value: draftSettings.iterations,
143
+ min: 10,
144
+ max: 500,
145
+ step: 10,
146
+ onChange: (value) => updateSetting("iterations", Math.max(10, value || 10)),
147
+ help: "Higher values let ForceAtlas2 settle longer before the graph renders."
148
+ }),
149
+ /* @__PURE__ */ jsx(NumberField, {
150
+ label: "Gravity",
151
+ value: draftSettings.gravity,
152
+ min: 0,
153
+ max: 1500,
154
+ step: 1,
155
+ onChange: (value) => updateSetting("gravity", Math.max(0, value || 0)),
156
+ help: "Pulls nodes back toward the center. Lower gravity spreads clusters out more."
157
+ }),
158
+ /* @__PURE__ */ jsx(NumberField, {
159
+ label: "Scaling ratio",
160
+ value: draftSettings.scalingRatio,
161
+ min: 1,
162
+ max: 500,
163
+ step: 1,
164
+ onChange: (value) => updateSetting("scalingRatio", Math.max(1, value || 1)),
165
+ help: "Primary spacing control. Higher values increase separation between nodes and clusters."
166
+ }),
167
+ /* @__PURE__ */ jsx(NumberField, {
168
+ label: "Node size scale",
169
+ value: draftSettings.nodeSizeScale,
170
+ min: .5,
171
+ max: 4,
172
+ step: .1,
173
+ onChange: (value) => updateSetting("nodeSizeScale", Math.max(.5, Number(value.toFixed(1)) || 1)),
174
+ help: "Amplifies the node size derived from inbound edge count."
175
+ }),
176
+ /* @__PURE__ */ jsxs("div", {
177
+ className: "grid gap-3",
178
+ children: [
179
+ /* @__PURE__ */ jsx(ToggleField, {
180
+ label: "Strong gravity mode",
181
+ checked: draftSettings.strongGravityMode,
182
+ onChange: (checked) => updateSetting("strongGravityMode", checked),
183
+ help: "Keeps distant nodes from drifting too far out of frame."
184
+ }),
185
+ /* @__PURE__ */ jsx(ToggleField, {
186
+ label: "LinLog mode",
187
+ checked: draftSettings.linLogMode,
188
+ onChange: (checked) => updateSetting("linLogMode", checked),
189
+ help: "Often improves community separation in clustered graphs."
190
+ }),
191
+ /* @__PURE__ */ jsx(ToggleField, {
192
+ label: "Adjust sizes",
193
+ checked: draftSettings.adjustSizes,
194
+ onChange: (checked) => updateSetting("adjustSizes", checked),
195
+ help: "Uses node size during layout to reduce collisions."
196
+ }),
197
+ /* @__PURE__ */ jsx(ToggleField, {
198
+ label: "Outbound attraction distribution",
199
+ checked: draftSettings.outboundAttractionDistribution,
200
+ onChange: (checked) => updateSetting("outboundAttractionDistribution", checked),
201
+ help: "Balances attraction when hubs create too much pull."
202
+ }),
203
+ /* @__PURE__ */ jsx(ToggleField, {
204
+ label: "Hide cluster labels",
205
+ checked: draftSettings.hideClusterLabels,
206
+ onChange: (checked) => updateSetting("hideClusterLabels", checked),
207
+ help: "Useful when labels cover dense areas during exploration."
208
+ })
209
+ ]
210
+ })
211
+ ]
212
+ })
213
+ })]
214
+ })
215
+ }) })
216
+ });
217
+ }
218
+ //#endregion
219
+ //#region src/routes/graph.tsx?tsr-split=component
220
+ var Sigma = lazy(() => import("./modviz-sigma-XYxARWqd.js").then((module) => ({ default: module.ModvizSigma })));
221
+ function GraphRoute() {
222
+ const bundle = useModvizBundle();
223
+ const search = Route$1.useSearch();
224
+ const navigate = Route$1.useNavigate();
225
+ const [settingsOpen, setSettingsOpen] = useState(false);
226
+ const [refreshNonce, setRefreshNonce] = useState(0);
227
+ const currentNodeId = useAtom(currentNodeIdAtom);
228
+ const isNodeDetailsOpen = useAtom(isNodeDetailsOpenAtom);
229
+ const selectedNodeIds = useAtom(selectedNodeIdsAtom);
230
+ if (!isModvizBundleReady(bundle)) return /* @__PURE__ */ jsx(ModvizLayout, {
231
+ title: "Bubble Graph",
232
+ description: "Force-directed cluster view for spatial exploration.",
233
+ children: /* @__PURE__ */ jsx(SetupView, { bundle })
234
+ });
235
+ const workspacePackageNames = useMemo(() => getWorkspacePackageNames(bundle.graph), [bundle.graph]);
236
+ const defaultLayoutSettings = useMemo(() => getDefaultGraphLayoutSettings(bundle.graph.nodes.length), [bundle.graph.nodes.length]);
237
+ const layoutSettings = useMemo(() => ({
238
+ adjustSizes: search.adjustSizes,
239
+ gravity: search.gravity,
240
+ hideClusterLabels: search.hideClusterLabels,
241
+ iterations: search.iterations,
242
+ linLogMode: search.linLogMode,
243
+ nodeSizeScale: search.nodeSizeScale,
244
+ outboundAttractionDistribution: search.outboundAttractionDistribution,
245
+ scalingRatio: search.scalingRatio,
246
+ strongGravityMode: search.strongGravityMode
247
+ }), [search]);
248
+ const scope = search.scope;
249
+ const externalGrouping = search.externalGrouping;
250
+ const filteredByScope = useMemo(() => filterNodesByScope(bundle.graph.nodes, workspacePackageNames, scope), [
251
+ bundle.graph.nodes,
252
+ scope,
253
+ workspacePackageNames
254
+ ]);
255
+ const filteredNodes = useMemo(() => {
256
+ if (!search.cluster) return filteredByScope;
257
+ return filteredByScope.filter((node) => getNodeGroupingLabel(node, workspacePackageNames, externalGrouping) === search.cluster);
258
+ }, [
259
+ externalGrouping,
260
+ filteredByScope,
261
+ search.cluster,
262
+ workspacePackageNames
263
+ ]);
264
+ const sigmaKey = useMemo(() => JSON.stringify({
265
+ cluster: search.cluster,
266
+ externalGrouping,
267
+ layoutSettings,
268
+ refreshNonce,
269
+ scope
270
+ }), [
271
+ externalGrouping,
272
+ layoutSettings,
273
+ refreshNonce,
274
+ scope,
275
+ search.cluster
276
+ ]);
277
+ const updateSearch = (patch) => navigate({
278
+ replace: true,
279
+ search: (previous) => ({
280
+ ...previous,
281
+ ...patch
282
+ })
283
+ });
284
+ const setFocusedNode = (nodePath) => {
285
+ highlightedNodeIdAtom.set(nodePath);
286
+ currentNodeIdAtom.set(nodePath);
287
+ selectedNodeIdsAtom.set((prev) => nodePath ? prev.includes(nodePath) ? prev.filter((id) => id !== nodePath) : [...prev, nodePath] : []);
288
+ };
289
+ useEffect(() => {
290
+ if (!search.focus) return;
291
+ setFocusedNode(search.focus);
292
+ }, [search.focus]);
293
+ return /* @__PURE__ */ jsxs(ModvizLayout, {
294
+ title: "Bubble Graph",
295
+ description: "Force-directed cluster view for spatial exploration. Open this only when you need the full graph canvas, then tune spacing and gravity from the floating settings panel.",
296
+ actions: /* @__PURE__ */ jsxs(Button, {
297
+ variant: "outline",
298
+ onClick: () => setSettingsOpen(true),
299
+ children: [/* @__PURE__ */ jsx(Settings2, { className: "size-4" }), "Layout settings"]
300
+ }),
301
+ children: [/* @__PURE__ */ jsxs("div", {
302
+ className: "flex h-[calc(100vh-14rem)] min-h-[680px] flex-col gap-4",
303
+ children: [
304
+ /* @__PURE__ */ jsxs("section", {
305
+ className: "flex flex-wrap items-center gap-2 rounded-[24px] border border-slate-200/70 bg-white/90 p-4 shadow-[0_16px_50px_-32px_rgba(15,23,42,0.55)] dark:border-slate-800 dark:bg-slate-950/70",
306
+ children: [[
307
+ ["all", "All nodes"],
308
+ ["workspace", "Monorepo only"],
309
+ ["external", "node_modules only"]
310
+ ].map(([value, label]) => /* @__PURE__ */ jsx(Button, {
311
+ variant: scope === value ? "default" : "outline",
312
+ size: "sm",
313
+ onClick: () => updateSearch({
314
+ scope: value,
315
+ cluster: ""
316
+ }),
317
+ children: label
318
+ }, value)), /* @__PURE__ */ jsxs("label", {
319
+ className: "ml-auto flex items-center gap-2 text-sm text-slate-600 dark:text-slate-300",
320
+ children: [/* @__PURE__ */ jsx("span", { children: "External grouping" }), /* @__PURE__ */ jsxs("select", {
321
+ className: "h-9 rounded-md border border-input bg-transparent px-3 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
322
+ value: externalGrouping,
323
+ onChange: (event) => updateSearch({
324
+ cluster: "",
325
+ externalGrouping: event.currentTarget.value
326
+ }),
327
+ children: [/* @__PURE__ */ jsx("option", {
328
+ value: "package",
329
+ children: "by package"
330
+ }), /* @__PURE__ */ jsx("option", {
331
+ value: "combined",
332
+ children: "single node_modules cluster"
333
+ })]
334
+ })]
335
+ })]
336
+ }),
337
+ /* @__PURE__ */ jsxs("section", {
338
+ className: "flex flex-wrap items-center gap-3 rounded-[24px] border border-slate-200/70 bg-white/90 p-4 shadow-[0_16px_50px_-32px_rgba(15,23,42,0.55)] dark:border-slate-800 dark:bg-slate-950/70",
339
+ children: [
340
+ /* @__PURE__ */ jsx("div", {
341
+ className: "min-w-[18rem] flex-1",
342
+ children: /* @__PURE__ */ jsx(GraphCommandMenu, {
343
+ nodes: filteredNodes,
344
+ onHighlight: (value) => {
345
+ if (!value) return highlightedNodeIdAtom.set(null);
346
+ highlightedNodeIdAtom.set(value);
347
+ },
348
+ onSelect: (value) => {
349
+ highlightedNodeIdAtom.set(null);
350
+ if (!value) {
351
+ setFocusedNode(null);
352
+ updateSearch({ focus: "" });
353
+ return;
354
+ }
355
+ setFocusedNode(value);
356
+ updateSearch({ focus: value });
357
+ }
358
+ })
359
+ }),
360
+ /* @__PURE__ */ jsxs(Button, {
361
+ variant: "outline",
362
+ onClick: () => isNodeDetailsOpenAtom.set(true),
363
+ disabled: !currentNodeId,
364
+ children: [/* @__PURE__ */ jsx(Eye, { className: "size-4" }), isNodeDetailsOpen ? "Details open" : "Open details"]
365
+ }),
366
+ /* @__PURE__ */ jsxs(Button, {
367
+ variant: "outline",
368
+ onClick: () => setFocusedNode(null),
369
+ disabled: selectedNodeIds.length === 0,
370
+ children: [
371
+ "Clear selection (",
372
+ selectedNodeIds.length,
373
+ ")"
374
+ ]
375
+ }),
376
+ /* @__PURE__ */ jsxs(Button, {
377
+ variant: "outline",
378
+ onClick: () => setRefreshNonce((value) => value + 1),
379
+ children: [/* @__PURE__ */ jsx(RotateCcw, { className: "size-4" }), "Refresh canvas"]
380
+ }),
381
+ currentNodeId ? /* @__PURE__ */ jsx(Button, {
382
+ variant: "outline",
383
+ onClick: () => {
384
+ setFocusedNode(null);
385
+ isNodeDetailsOpenAtom.set(false);
386
+ updateSearch({ focus: "" });
387
+ },
388
+ children: "Clear current node"
389
+ }) : null,
390
+ /* @__PURE__ */ jsxs("div", {
391
+ className: "text-sm text-slate-500 dark:text-slate-400",
392
+ children: [
393
+ filteredNodes.length,
394
+ " nodes, ",
395
+ bundle.graph.metadata.packages.length,
396
+ " workspace packages",
397
+ selectedNodeIds.length > 0 ? ` • ${selectedNodeIds.length} selected` : "",
398
+ currentNodeId ? ` • current ${currentNodeId.split("/").at(-1)}` : "",
399
+ search.cluster ? ` • filtered to ${search.cluster}` : ""
400
+ ]
401
+ })
402
+ ]
403
+ }),
404
+ /* @__PURE__ */ jsx("section", {
405
+ className: "min-h-0 flex-1 overflow-hidden rounded-[28px] border border-slate-200/70 bg-white/90 shadow-[0_20px_70px_-36px_rgba(15,23,42,0.55)] dark:border-slate-800 dark:bg-slate-950/70",
406
+ children: /* @__PURE__ */ jsx(Suspense, {
407
+ fallback: /* @__PURE__ */ jsx(LoadingState, {
408
+ label: "Loading graph…",
409
+ description: "Preparing the Sigma canvas and layout pass."
410
+ }),
411
+ children: /* @__PURE__ */ jsx(Sigma, {
412
+ output: {
413
+ ...bundle.graph,
414
+ nodes: filteredNodes
415
+ },
416
+ entryNode: search.focus || bundle.graph.metadata.entrypoints[0],
417
+ externalGrouping,
418
+ layoutSettings,
419
+ packages: bundle.graph.metadata.packages,
420
+ nodes: filteredNodes
421
+ }, sigmaKey)
422
+ })
423
+ })
424
+ ]
425
+ }), /* @__PURE__ */ jsx(GraphSettingsPanel, {
426
+ open: settingsOpen,
427
+ settings: layoutSettings,
428
+ onOpenChange: setSettingsOpen,
429
+ onSettingsChange: (nextSettings) => updateSearch(nextSettings),
430
+ onReset: () => {
431
+ updateSearch(defaultLayoutSettings);
432
+ return defaultLayoutSettings;
433
+ }
434
+ })]
435
+ });
436
+ }
437
+ //#endregion
438
+ export { GraphRoute as component };
@@ -0,0 +1,45 @@
1
+ import { t as parseSearchParam } from "./search-params-BNApPgkX.js";
2
+ import { createFileRoute, lazyRouteComponent } from "@tanstack/react-router";
3
+ //#region src/components/graph/common/graph-layout-settings.ts
4
+ var getDefaultGraphLayoutSettings = (nodeCount) => {
5
+ const densityFactor = Math.max(1, Math.sqrt(Math.max(nodeCount, 1)));
6
+ return {
7
+ iterations: Math.min(420, Math.max(220, Math.round(170 + densityFactor * 2.4))),
8
+ gravity: Number(Math.max(.15, Math.min(1.2, densityFactor / 90)).toFixed(2)),
9
+ scalingRatio: Math.min(220, Math.max(40, Math.round(28 + densityFactor * 2.6))),
10
+ strongGravityMode: false,
11
+ linLogMode: true,
12
+ adjustSizes: true,
13
+ outboundAttractionDistribution: true,
14
+ hideClusterLabels: false,
15
+ nodeSizeScale: .85
16
+ };
17
+ };
18
+ //#endregion
19
+ //#region src/routes/graph.tsx
20
+ var $$splitComponentImporter = () => import("./graph-C1G9H5O4.js");
21
+ var validateGraphSearch = (search) => {
22
+ const defaults = getDefaultGraphLayoutSettings(3961);
23
+ return {
24
+ adjustSizes: parseSearchParam.boolean(search.adjustSizes, defaults.adjustSizes),
25
+ cluster: parseSearchParam.string(search.cluster),
26
+ externalGrouping: search.externalGrouping === "combined" ? "combined" : "package",
27
+ focus: parseSearchParam.string(search.focus),
28
+ gravity: parseSearchParam.number(search.gravity, defaults.gravity),
29
+ hideClusterLabels: parseSearchParam.boolean(search.hideClusterLabels, defaults.hideClusterLabels),
30
+ iterations: Math.round(parseSearchParam.number(search.iterations, defaults.iterations)),
31
+ linLogMode: parseSearchParam.boolean(search.linLogMode, defaults.linLogMode),
32
+ nodeSizeScale: parseSearchParam.number(search.nodeSizeScale, defaults.nodeSizeScale),
33
+ outboundAttractionDistribution: parseSearchParam.boolean(search.outboundAttractionDistribution, defaults.outboundAttractionDistribution),
34
+ scalingRatio: parseSearchParam.number(search.scalingRatio, defaults.scalingRatio),
35
+ scope: search.scope === "workspace" || search.scope === "external" ? search.scope : "all",
36
+ strongGravityMode: parseSearchParam.boolean(search.strongGravityMode, defaults.strongGravityMode)
37
+ };
38
+ };
39
+ var Route = createFileRoute("/graph")({
40
+ ssr: false,
41
+ validateSearch: validateGraphSearch,
42
+ component: lazyRouteComponent($$splitComponentImporter, "component")
43
+ });
44
+ //#endregion
45
+ export { getDefaultGraphLayoutSettings as n, Route as t };