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,288 @@
1
+ import { t as Button } from "./button-Bqnnid5i.js";
2
+ import { t as Input } from "./input-C5r-hBix.js";
3
+ import { m as useModvizBundle } from "./modviz-data-CUyTorv0.js";
4
+ import { t as ModvizLayout } from "./modviz-layout-BAH2ogse.js";
5
+ import { useMemo, useState } from "react";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import { Check, Copy } from "lucide-react";
8
+ //#region src/routes/configure.tsx?tsr-split=component
9
+ var defaultCommandBuilderState = {
10
+ launchUi: true,
11
+ outputFile: "modviz.json",
12
+ barrelThreshold: 3,
13
+ excludePaths: "",
14
+ enableLlm: false,
15
+ enableAiAnalysis: false,
16
+ historyDir: "",
17
+ includePaths: "",
18
+ ignoreDynamic: false,
19
+ llmModel: "gpt-4.1-mini",
20
+ nodeModules: false,
21
+ packageQuery: "",
22
+ nodeQuery: "",
23
+ snapshotName: ""
24
+ };
25
+ var quoteCliValue = (value) => /\s/.test(value) ? JSON.stringify(value) : value;
26
+ var buildAnalyzeCommand = (entryFile, config) => {
27
+ const parts = [
28
+ "pnpm",
29
+ "exec",
30
+ "modviz",
31
+ "analyze",
32
+ quoteCliValue(entryFile)
33
+ ];
34
+ if (config.outputFile && config.outputFile !== defaultCommandBuilderState.outputFile) parts.push(`--output-file=${quoteCliValue(config.outputFile)}`);
35
+ if (config.barrelThreshold !== defaultCommandBuilderState.barrelThreshold) parts.push(`--barrel-threshold=${config.barrelThreshold}`);
36
+ if (config.includePaths.trim()) parts.push(`--include=${quoteCliValue(config.includePaths.trim())}`);
37
+ if (config.excludePaths.trim()) parts.push(`--exclude=${quoteCliValue(config.excludePaths.trim())}`);
38
+ if (config.historyDir.trim()) parts.push(`--history-dir=${quoteCliValue(config.historyDir.trim())}`);
39
+ if (config.launchUi) parts.push("--ui");
40
+ if (config.enableLlm) parts.push("--llm");
41
+ if (config.enableAiAnalysis) parts.push("--llm-analyze");
42
+ if (config.enableAiAnalysis && config.llmModel.trim()) parts.push(`--llm-model=${quoteCliValue(config.llmModel.trim())}`);
43
+ if (config.ignoreDynamic) parts.push("--ignore-dynamic");
44
+ if (config.nodeModules) parts.push("--node-modules");
45
+ if (config.snapshotName.trim()) parts.push(`--snapshot-name=${quoteCliValue(config.snapshotName.trim())}`);
46
+ if (config.packageQuery.trim()) parts.push(`--package=${quoteCliValue(config.packageQuery.trim())}`);
47
+ if (config.nodeQuery.trim()) parts.push(`--node=${quoteCliValue(config.nodeQuery.trim())}`);
48
+ return parts.join(" ");
49
+ };
50
+ function ConfigureRoute() {
51
+ const bundle = useModvizBundle();
52
+ const [config, setConfig] = useState(defaultCommandBuilderState);
53
+ const [didCopy, setDidCopy] = useState(false);
54
+ const updateConfig = (key, value) => {
55
+ setConfig((current) => ({
56
+ ...current,
57
+ [key]: value
58
+ }));
59
+ };
60
+ const copyToClipboard = async (text) => {
61
+ await navigator.clipboard.writeText(text);
62
+ setDidCopy(true);
63
+ window.setTimeout(() => setDidCopy(false), 2e3);
64
+ };
65
+ const cmd = useMemo(() => {
66
+ return buildAnalyzeCommand(bundle.graph?.metadata.entrypoints[0] || "./src/index.ts", config);
67
+ }, [bundle.graph?.metadata.entrypoints, config]);
68
+ const summary = bundle.summary;
69
+ return /* @__PURE__ */ jsx(ModvizLayout, {
70
+ projectTitle: bundle.projectTitle,
71
+ children: /* @__PURE__ */ jsxs("div", {
72
+ className: "max-w-3xl space-y-6",
73
+ children: [
74
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
75
+ className: "text-2xl font-bold text-slate-900 dark:text-slate-100",
76
+ children: "Command Configuration"
77
+ }), /* @__PURE__ */ jsx("p", {
78
+ className: "mt-1 text-sm text-slate-600 dark:text-slate-400",
79
+ children: "Configure and run the analyzer from your terminal."
80
+ })] }),
81
+ /* @__PURE__ */ jsxs("section", {
82
+ className: "rounded-[16px] 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",
83
+ children: [/* @__PURE__ */ jsx("h2", {
84
+ className: "text-sm font-semibold text-slate-900 dark:text-slate-100 mb-4",
85
+ children: "Build Command"
86
+ }), /* @__PURE__ */ jsxs("div", {
87
+ className: "space-y-4",
88
+ children: [
89
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
90
+ className: "text-xs font-medium text-slate-700 dark:text-slate-300",
91
+ children: "Output File"
92
+ }), /* @__PURE__ */ jsx(Input, {
93
+ value: config.outputFile,
94
+ onChange: (e) => updateConfig("outputFile", e.target.value),
95
+ placeholder: "modviz.json",
96
+ className: "mt-1"
97
+ })] }),
98
+ /* @__PURE__ */ jsxs("div", { children: [
99
+ /* @__PURE__ */ jsx("label", {
100
+ className: "text-xs font-medium text-slate-700 dark:text-slate-300",
101
+ children: "Barrel threshold"
102
+ }),
103
+ /* @__PURE__ */ jsx(Input, {
104
+ type: "number",
105
+ min: "1",
106
+ value: String(config.barrelThreshold),
107
+ onChange: (e) => updateConfig("barrelThreshold", Math.max(1, Number(e.target.value) || defaultCommandBuilderState.barrelThreshold)),
108
+ className: "mt-1"
109
+ }),
110
+ /* @__PURE__ */ jsx("p", {
111
+ className: "mt-1 text-xs text-slate-500 dark:text-slate-400",
112
+ children: "Files exporting at least this many symbols are treated as barrel files."
113
+ })
114
+ ] }),
115
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
116
+ className: "text-xs font-medium text-slate-700 dark:text-slate-300",
117
+ children: "Snapshot name (optional)"
118
+ }), /* @__PURE__ */ jsx(Input, {
119
+ value: config.snapshotName,
120
+ onChange: (e) => updateConfig("snapshotName", e.target.value),
121
+ placeholder: "before-refactor",
122
+ className: "mt-1"
123
+ })] }),
124
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
125
+ className: "text-xs font-medium text-slate-700 dark:text-slate-300",
126
+ children: "Include paths (optional)"
127
+ }), /* @__PURE__ */ jsx(Input, {
128
+ value: config.includePaths,
129
+ onChange: (e) => updateConfig("includePaths", e.target.value),
130
+ placeholder: "src/routes/**,src/components/**",
131
+ className: "mt-1"
132
+ })] }),
133
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
134
+ className: "text-xs font-medium text-slate-700 dark:text-slate-300",
135
+ children: "Exclude paths (optional)"
136
+ }), /* @__PURE__ */ jsx(Input, {
137
+ value: config.excludePaths,
138
+ onChange: (e) => updateConfig("excludePaths", e.target.value),
139
+ placeholder: "**/*.test.ts,**/*.stories.tsx",
140
+ className: "mt-1"
141
+ })] }),
142
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
143
+ className: "text-xs font-medium text-slate-700 dark:text-slate-300",
144
+ children: "History dir (optional)"
145
+ }), /* @__PURE__ */ jsx(Input, {
146
+ value: config.historyDir,
147
+ onChange: (e) => updateConfig("historyDir", e.target.value),
148
+ placeholder: ".modviz/history",
149
+ className: "mt-1"
150
+ })] }),
151
+ /* @__PURE__ */ jsx("div", {
152
+ className: "space-y-2",
153
+ children: [
154
+ {
155
+ key: "launchUi",
156
+ label: "Launch web UI after analyze (--ui)"
157
+ },
158
+ {
159
+ key: "enableLlm",
160
+ label: "Enable LLM analysis (--llm)"
161
+ },
162
+ {
163
+ key: "enableAiAnalysis",
164
+ label: "Generate AI summary (--llm-analyze)"
165
+ },
166
+ {
167
+ key: "ignoreDynamic",
168
+ label: "Ignore dynamic imports (--ignore-dynamic)"
169
+ },
170
+ {
171
+ key: "nodeModules",
172
+ label: "Include node_modules (--node-modules)"
173
+ }
174
+ ].map((field) => /* @__PURE__ */ jsxs("label", {
175
+ className: "flex cursor-pointer items-center gap-2",
176
+ children: [/* @__PURE__ */ jsx("input", {
177
+ type: "checkbox",
178
+ checked: config[field.key],
179
+ onChange: (e) => updateConfig(field.key, e.target.checked),
180
+ className: "rounded"
181
+ }), /* @__PURE__ */ jsx("span", {
182
+ className: "text-xs font-medium text-slate-700 dark:text-slate-300",
183
+ children: field.label
184
+ })]
185
+ }, field.key))
186
+ }),
187
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
188
+ className: "text-xs font-medium text-slate-700 dark:text-slate-300",
189
+ children: "AI model (optional)"
190
+ }), /* @__PURE__ */ jsx(Input, {
191
+ value: config.llmModel,
192
+ onChange: (e) => updateConfig("llmModel", e.target.value),
193
+ placeholder: "gpt-4.1-mini",
194
+ className: "mt-1 text-xs",
195
+ disabled: !config.enableAiAnalysis
196
+ })] }),
197
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
198
+ className: "text-xs font-medium text-slate-700 dark:text-slate-300",
199
+ children: "Filter package (optional)"
200
+ }), /* @__PURE__ */ jsx(Input, {
201
+ value: config.packageQuery,
202
+ onChange: (e) => updateConfig("packageQuery", e.target.value),
203
+ placeholder: "e.g., @namespace/package",
204
+ className: "mt-1 text-xs"
205
+ })] }),
206
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
207
+ className: "text-xs font-medium text-slate-700 dark:text-slate-300",
208
+ children: "Filter node (optional)"
209
+ }), /* @__PURE__ */ jsx(Input, {
210
+ value: config.nodeQuery,
211
+ onChange: (e) => updateConfig("nodeQuery", e.target.value),
212
+ placeholder: "e.g., src/utils",
213
+ className: "mt-1 text-xs"
214
+ })] }),
215
+ /* @__PURE__ */ jsxs("div", {
216
+ className: "pt-2 border-t border-slate-200 dark:border-slate-800",
217
+ children: [/* @__PURE__ */ jsx("p", {
218
+ className: "text-xs text-slate-600 dark:text-slate-400 mb-2",
219
+ children: "Run this command in your terminal:"
220
+ }), /* @__PURE__ */ jsxs("div", {
221
+ className: "flex items-center gap-2",
222
+ children: [/* @__PURE__ */ jsx("code", {
223
+ className: "flex-1 min-w-0 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 font-mono text-xs text-slate-800 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 overflow-x-auto",
224
+ children: cmd
225
+ }), /* @__PURE__ */ jsx(Button, {
226
+ size: "sm",
227
+ onClick: () => void copyToClipboard(cmd),
228
+ className: "shrink-0 gap-1.5",
229
+ title: "Copy command",
230
+ children: didCopy ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Check, { className: "size-3.5" }), "Copied"] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Copy, { className: "size-3.5" }), "Copy command"] })
231
+ })]
232
+ })]
233
+ })
234
+ ]
235
+ })]
236
+ }),
237
+ /* @__PURE__ */ jsxs("section", {
238
+ className: "rounded-[16px] 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",
239
+ children: [
240
+ /* @__PURE__ */ jsx("h2", {
241
+ className: "text-sm font-semibold text-slate-900 dark:text-slate-100 mb-3",
242
+ children: "Current Dataset"
243
+ }),
244
+ /* @__PURE__ */ jsxs("div", {
245
+ className: "grid gap-3 grid-cols-2 sm:grid-cols-4 text-xs",
246
+ children: [
247
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
248
+ className: "font-semibold text-slate-500 dark:text-slate-400",
249
+ children: "Nodes"
250
+ }), /* @__PURE__ */ jsx("p", {
251
+ className: "text-lg font-bold text-slate-900 dark:text-slate-100",
252
+ children: (summary?.overview.totalNodes ?? 0).toLocaleString()
253
+ })] }),
254
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
255
+ className: "font-semibold text-slate-500 dark:text-slate-400",
256
+ children: "Workspace"
257
+ }), /* @__PURE__ */ jsx("p", {
258
+ className: "text-lg font-bold text-slate-900 dark:text-slate-100",
259
+ children: (summary?.overview.workspaceNodes ?? 0).toLocaleString()
260
+ })] }),
261
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
262
+ className: "font-semibold text-slate-500 dark:text-slate-400",
263
+ children: "Packages"
264
+ }), /* @__PURE__ */ jsx("p", {
265
+ className: "text-lg font-bold text-slate-900 dark:text-slate-100",
266
+ children: (summary?.overview.workspacePackages ?? 0).toLocaleString()
267
+ })] }),
268
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
269
+ className: "font-semibold text-slate-500 dark:text-slate-400",
270
+ children: "LLM Report"
271
+ }), /* @__PURE__ */ jsx("p", {
272
+ className: "text-lg font-bold text-slate-900 dark:text-slate-100",
273
+ children: summary?.hasLlm ? "✓" : "—"
274
+ })] })
275
+ ]
276
+ }),
277
+ bundle.setup.status !== "ready" ? /* @__PURE__ */ jsx("p", {
278
+ className: "mt-4 text-sm text-slate-500 dark:text-slate-400",
279
+ children: bundle.setup.message
280
+ }) : null
281
+ ]
282
+ })
283
+ ]
284
+ })
285
+ });
286
+ }
287
+ //#endregion
288
+ export { ConfigureRoute as component };
@@ -0,0 +1,379 @@
1
+ import { t as Button } from "./button-Bqnnid5i.js";
2
+ import { t as Input } from "./input-C5r-hBix.js";
3
+ import { d as getWorkspacePackageNames, f as isModvizBundleReady, m as useModvizBundle, o as filterNodesByScope, u as getNodeScope } from "./modviz-data-CUyTorv0.js";
4
+ import { t as Route$1 } from "./explorer-CpSb0JTa.js";
5
+ import { t as ModvizLayout } from "./modviz-layout-BAH2ogse.js";
6
+ import { t as ImportDisplay } from "./import-display-BVIOWcsm.js";
7
+ import { t as SetupView } from "./setup-view-DjI49Iqr.js";
8
+ import { useMemo, useState } from "react";
9
+ import { Link } from "@tanstack/react-router";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ import { ChevronDown, ChevronRight, ExternalLink, FileCode2, Folder, FolderOpen } from "lucide-react";
12
+ //#region src/components/modviz/explorer-view.tsx
13
+ var defaultGraphSearch = {
14
+ adjustSizes: false,
15
+ cluster: "",
16
+ externalGrouping: "package",
17
+ focus: "",
18
+ gravity: 0,
19
+ hideClusterLabels: false,
20
+ iterations: 0,
21
+ linLogMode: false,
22
+ nodeSizeScale: 0,
23
+ outboundAttractionDistribution: true,
24
+ scalingRatio: 0,
25
+ scope: "all",
26
+ strongGravityMode: false
27
+ };
28
+ var defaultImportSearch = {
29
+ exclude: "",
30
+ include: "",
31
+ mode: "contains",
32
+ module: "",
33
+ preset: "",
34
+ scope: "all",
35
+ symbol: ""
36
+ };
37
+ function ExplorerView(props) {
38
+ const { graph } = props.bundle;
39
+ const workspacePackageNames = useMemo(() => getWorkspacePackageNames(graph), [graph]);
40
+ const scopedNodes = useMemo(() => filterNodesByScope(graph.nodes, workspacePackageNames, props.search.scope), [
41
+ graph.nodes,
42
+ props.search.scope,
43
+ workspacePackageNames
44
+ ]);
45
+ const tree = useMemo(() => buildFileTree(scopedNodes), [scopedNodes]);
46
+ const selectedNode = useMemo(() => scopedNodes.find((node) => node.path === props.search.selected) ?? scopedNodes[0] ?? null, [props.search.selected, scopedNodes]);
47
+ const [manuallyExpandedPaths, setManuallyExpandedPaths] = useState(() => /* @__PURE__ */ new Set());
48
+ const expandedPaths = useMemo(() => new Set([...manuallyExpandedPaths, ...selectedNode ? getAncestorPaths(selectedNode.path) : []]), [manuallyExpandedPaths, selectedNode]);
49
+ const selectedNodeScope = selectedNode == null ? "workspace" : getNodeScope(selectedNode, workspacePackageNames);
50
+ const normalizedQuery = props.search.q.trim().toLowerCase();
51
+ const visibleTree = useMemo(() => filterTree(tree, normalizedQuery), [normalizedQuery, tree]);
52
+ return /* @__PURE__ */ jsxs("div", {
53
+ className: "flex h-screen flex-col gap-6 lg:flex-row lg:h-auto",
54
+ children: [/* @__PURE__ */ jsxs("section", {
55
+ className: "flex flex-col rounded-[24px] border border-slate-200/70 bg-white/90 p-5 shadow-[0_16px_50px_-32px_rgba(15,23,42,0.55)] dark:border-slate-800 dark:bg-slate-950/70 lg:w-[35%]",
56
+ children: [
57
+ /* @__PURE__ */ jsx("div", {
58
+ className: "flex flex-wrap gap-2",
59
+ children: [
60
+ ["all", "All files"],
61
+ ["workspace", "Monorepo"],
62
+ ["external", "node_modules"]
63
+ ].map(([value, label]) => /* @__PURE__ */ jsx(Button, {
64
+ variant: props.search.scope === value ? "default" : "outline",
65
+ size: "sm",
66
+ onClick: () => props.onSearchChange({
67
+ scope: value,
68
+ selected: ""
69
+ }),
70
+ children: label
71
+ }, value))
72
+ }),
73
+ /* @__PURE__ */ jsxs("div", {
74
+ className: "mt-4 flex gap-3",
75
+ children: [
76
+ /* @__PURE__ */ jsx(Input, {
77
+ placeholder: "Search file or folder path",
78
+ value: props.search.q,
79
+ onChange: (event) => props.onSearchChange({ q: event.currentTarget.value })
80
+ }),
81
+ /* @__PURE__ */ jsx(Button, {
82
+ variant: "outline",
83
+ onClick: () => setManuallyExpandedPaths(/* @__PURE__ */ new Set()),
84
+ children: "Collapse"
85
+ }),
86
+ /* @__PURE__ */ jsx(Button, {
87
+ variant: "outline",
88
+ onClick: () => setManuallyExpandedPaths(new Set(flattenFolderPaths(visibleTree))),
89
+ children: "Expand"
90
+ })
91
+ ]
92
+ }),
93
+ /* @__PURE__ */ jsx("div", {
94
+ className: "mt-4 flex-1 overflow-auto",
95
+ children: /* @__PURE__ */ jsx("div", {
96
+ className: "space-y-1 pr-2",
97
+ children: visibleTree.children.map((child) => /* @__PURE__ */ jsx(FileTreeItem, {
98
+ node: child,
99
+ expandedPaths,
100
+ selectedPath: selectedNode?.path ?? "",
101
+ onToggle: (path) => {
102
+ setManuallyExpandedPaths((previous) => {
103
+ const next = new Set(previous);
104
+ if (next.has(path)) next.delete(path);
105
+ else next.add(path);
106
+ return next;
107
+ });
108
+ },
109
+ onSelect: (path) => props.onSearchChange({ selected: path })
110
+ }, child.id))
111
+ })
112
+ })
113
+ ]
114
+ }), /* @__PURE__ */ jsx("section", {
115
+ className: "flex flex-col rounded-[24px] border border-slate-200/70 bg-white/90 p-5 shadow-[0_16px_50px_-32px_rgba(15,23,42,0.55)] dark:border-slate-800 dark:bg-slate-950/70 lg:w-[65%] lg:overflow-y-auto",
116
+ children: selectedNode ? /* @__PURE__ */ jsxs("div", {
117
+ className: "space-y-6",
118
+ children: [
119
+ /* @__PURE__ */ jsxs("div", { children: [
120
+ /* @__PURE__ */ jsx("p", {
121
+ className: "text-xs font-semibold uppercase tracking-[0.22em] text-sky-700 dark:text-sky-300",
122
+ children: "Selected file"
123
+ }),
124
+ /* @__PURE__ */ jsx("h2", {
125
+ className: "mt-2 break-words text-lg font-semibold text-slate-900 dark:text-slate-100 sm:text-xl",
126
+ children: selectedNode.path
127
+ }),
128
+ /* @__PURE__ */ jsxs("p", {
129
+ className: "mt-2 text-sm text-slate-500 dark:text-slate-400",
130
+ children: [selectedNode.package?.name ? `${selectedNode.package.name} • ` : "", selectedNode.cluster ?? selectedNode.type]
131
+ })
132
+ ] }),
133
+ /* @__PURE__ */ jsxs("div", {
134
+ className: "flex flex-wrap gap-2",
135
+ children: [
136
+ /* @__PURE__ */ jsxs(Link, {
137
+ to: "/graph",
138
+ search: {
139
+ ...defaultGraphSearch,
140
+ focus: selectedNode.path,
141
+ scope: selectedNodeScope
142
+ },
143
+ className: "inline-flex h-9 items-center gap-2 rounded-md border border-slate-200 bg-white px-4 text-sm font-medium text-slate-700 shadow-xs hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100",
144
+ children: [/* @__PURE__ */ jsx(ExternalLink, { className: "size-4" }), "Open in graph"]
145
+ }),
146
+ /* @__PURE__ */ jsxs(Link, {
147
+ to: "/imports",
148
+ search: {
149
+ ...defaultImportSearch,
150
+ include: selectedNode.path,
151
+ scope: selectedNodeScope
152
+ },
153
+ className: "inline-flex h-9 items-center gap-2 rounded-md border border-slate-200 bg-white px-4 text-sm font-medium text-slate-700 shadow-xs hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100",
154
+ children: [/* @__PURE__ */ jsx(ExternalLink, { className: "size-4" }), "Use as importer filter"]
155
+ }),
156
+ /* @__PURE__ */ jsxs(Link, {
157
+ to: "/trace",
158
+ search: {
159
+ package: "",
160
+ node: selectedNode.path,
161
+ limit: 10
162
+ },
163
+ className: "inline-flex h-9 items-center gap-2 rounded-md border border-slate-200 bg-white px-4 text-sm font-medium text-slate-700 shadow-xs hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100",
164
+ children: [/* @__PURE__ */ jsx(ExternalLink, { className: "size-4" }), "Trace origins"]
165
+ })
166
+ ]
167
+ }),
168
+ /* @__PURE__ */ jsxs("div", {
169
+ className: "grid gap-4 md:grid-cols-3",
170
+ children: [
171
+ /* @__PURE__ */ jsx(Metric, {
172
+ label: "Direct imports",
173
+ value: selectedNode.imports.length
174
+ }),
175
+ /* @__PURE__ */ jsx(Metric, {
176
+ label: "Imported by",
177
+ value: selectedNode.importedBy.length
178
+ }),
179
+ /* @__PURE__ */ jsx(Metric, {
180
+ label: "Exports",
181
+ value: selectedNode.exports.length
182
+ })
183
+ ]
184
+ }),
185
+ /* @__PURE__ */ jsx(CollapsibleSection, {
186
+ title: "Imports",
187
+ description: "Grouped into import statements so scanning looks closer to the source file.",
188
+ children: /* @__PURE__ */ jsx(ImportDisplay, {
189
+ imports: selectedNode.imports,
190
+ emptyMessage: "No direct imports recorded for this file.",
191
+ showViewToggle: true
192
+ })
193
+ }),
194
+ /* @__PURE__ */ jsx(CollapsibleSection, {
195
+ title: "Imported by",
196
+ description: "Direct inbound edges into this file.",
197
+ children: /* @__PURE__ */ jsx(ImportedByCard, {
198
+ items: selectedNode.importedBy,
199
+ onSelect: (path) => props.onSearchChange({ selected: path })
200
+ })
201
+ })
202
+ ]
203
+ }) : /* @__PURE__ */ jsx("div", {
204
+ className: "flex min-h-[24rem] items-center justify-center text-sm text-slate-500 dark:text-slate-400",
205
+ children: "No file matched the current explorer scope."
206
+ })
207
+ })]
208
+ });
209
+ }
210
+ function Metric(props) {
211
+ return /* @__PURE__ */ jsxs("div", {
212
+ className: "rounded-[20px] bg-slate-50/90 p-4 dark:bg-slate-900/70",
213
+ children: [/* @__PURE__ */ jsx("p", {
214
+ className: "text-xs font-semibold uppercase tracking-[0.18em] text-slate-500 dark:text-slate-400",
215
+ children: props.label
216
+ }), /* @__PURE__ */ jsx("p", {
217
+ className: "mt-2 text-3xl font-semibold text-slate-900 dark:text-slate-100",
218
+ children: props.value
219
+ })]
220
+ });
221
+ }
222
+ function CollapsibleSection(props) {
223
+ return /* @__PURE__ */ jsxs("details", {
224
+ open: true,
225
+ className: "group rounded-[24px] border border-slate-200/70 bg-slate-50/80 p-4 dark:border-slate-800 dark:bg-slate-900/70",
226
+ children: [/* @__PURE__ */ jsxs("summary", {
227
+ className: "flex cursor-pointer list-none items-center gap-3 text-left",
228
+ children: [/* @__PURE__ */ jsx(ChevronDown, { className: "size-5 shrink-0 -rotate-90 transition-transform group-open:rotate-0" }), /* @__PURE__ */ jsxs("div", {
229
+ className: "flex-1",
230
+ children: [/* @__PURE__ */ jsx("h3", {
231
+ className: "text-base font-semibold text-slate-900 dark:text-slate-100",
232
+ children: props.title
233
+ }), /* @__PURE__ */ jsx("p", {
234
+ className: "mt-1 text-sm text-slate-500 dark:text-slate-400",
235
+ children: props.description
236
+ })]
237
+ })]
238
+ }), /* @__PURE__ */ jsx("div", {
239
+ className: "mt-4",
240
+ children: props.children
241
+ })]
242
+ });
243
+ }
244
+ function ImportedByCard(props) {
245
+ return /* @__PURE__ */ jsx("div", {
246
+ className: "max-h-[28rem] space-y-2 overflow-auto pr-1",
247
+ children: props.items.length ? props.items.map((item) => /* @__PURE__ */ jsxs("button", {
248
+ type: "button",
249
+ onClick: () => props.onSelect(item),
250
+ className: "flex w-full items-center gap-2 rounded-2xl bg-white px-3 py-3 text-left text-sm text-slate-700 hover:bg-sky-50 hover:text-sky-800 dark:bg-slate-950/80 dark:text-slate-200 dark:hover:bg-sky-500/10 dark:hover:text-sky-200",
251
+ children: [/* @__PURE__ */ jsx(FileCode2, { className: "size-4 shrink-0" }), /* @__PURE__ */ jsx("span", {
252
+ className: "truncate",
253
+ children: item
254
+ })]
255
+ }, item)) : /* @__PURE__ */ jsx("p", {
256
+ className: "rounded-2xl bg-white px-4 py-6 text-sm text-slate-500 dark:bg-slate-950/80 dark:text-slate-400",
257
+ children: "No inbound importers recorded for this file."
258
+ })
259
+ });
260
+ }
261
+ function FileTreeItem(props) {
262
+ const depth = props.depth ?? 0;
263
+ const isExpanded = props.expandedPaths.has(props.node.path);
264
+ if (props.node.kind === "file") return /* @__PURE__ */ jsxs("button", {
265
+ type: "button",
266
+ onClick: () => props.onSelect(props.node.path),
267
+ className: `flex w-full items-center gap-2 rounded-xl px-3 py-2 text-left text-sm ${props.selectedPath === props.node.path ? "bg-sky-100 text-sky-900 dark:bg-sky-500/20 dark:text-sky-100" : "text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-slate-900"}`,
268
+ style: { paddingLeft: `${depth * 16 + 12}px` },
269
+ children: [/* @__PURE__ */ jsx(FileCode2, { className: "size-4 shrink-0" }), /* @__PURE__ */ jsx("span", {
270
+ className: "truncate",
271
+ children: props.node.label
272
+ })]
273
+ });
274
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("button", {
275
+ type: "button",
276
+ onClick: () => props.onToggle(props.node.path),
277
+ className: "flex w-full items-center gap-2 rounded-xl px-3 py-2 text-left text-sm text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-slate-900",
278
+ style: { paddingLeft: `${depth * 16 + 12}px` },
279
+ children: [
280
+ isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-4" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" }),
281
+ isExpanded ? /* @__PURE__ */ jsx(FolderOpen, { className: "size-4" }) : /* @__PURE__ */ jsx(Folder, { className: "size-4" }),
282
+ /* @__PURE__ */ jsx("span", {
283
+ className: "truncate",
284
+ children: props.node.label
285
+ })
286
+ ]
287
+ }), isExpanded ? /* @__PURE__ */ jsx("div", {
288
+ className: "space-y-1",
289
+ children: props.node.children.map((child) => /* @__PURE__ */ jsx(FileTreeItem, {
290
+ node: child,
291
+ expandedPaths: props.expandedPaths,
292
+ selectedPath: props.selectedPath,
293
+ onToggle: props.onToggle,
294
+ onSelect: props.onSelect,
295
+ depth: depth + 1
296
+ }, child.id))
297
+ }) : null] });
298
+ }
299
+ function buildFileTree(nodes) {
300
+ const root = {
301
+ id: "root",
302
+ label: "root",
303
+ path: "",
304
+ kind: "folder",
305
+ children: []
306
+ };
307
+ for (const node of nodes) {
308
+ const segments = node.path.split("/").filter(Boolean);
309
+ let cursor = root;
310
+ let currentPath = "";
311
+ segments.forEach((segment, index) => {
312
+ currentPath = currentPath ? `${currentPath}/${segment}` : segment;
313
+ const isFile = index === segments.length - 1;
314
+ let child = cursor.children.find((existing) => existing.label === segment && existing.kind === (isFile ? "file" : "folder"));
315
+ if (!child) {
316
+ child = {
317
+ id: currentPath,
318
+ label: segment,
319
+ path: currentPath,
320
+ kind: isFile ? "file" : "folder",
321
+ children: [],
322
+ file: isFile ? node : void 0
323
+ };
324
+ cursor.children.push(child);
325
+ cursor.children.sort((left, right) => {
326
+ if (left.kind !== right.kind) return left.kind === "folder" ? -1 : 1;
327
+ return left.label.localeCompare(right.label);
328
+ });
329
+ }
330
+ cursor = child;
331
+ });
332
+ }
333
+ return root;
334
+ }
335
+ function filterTree(node, query) {
336
+ if (!query) return node;
337
+ if (node.kind === "file") return node.path.toLowerCase().includes(query) ? node : {
338
+ ...node,
339
+ children: []
340
+ };
341
+ const children = node.children.map((child) => filterTree(child, query)).filter((child) => child.kind === "file" ? child.path.toLowerCase().includes(query) : child.children.length || child.path.toLowerCase().includes(query));
342
+ return {
343
+ ...node,
344
+ children
345
+ };
346
+ }
347
+ function flattenFolderPaths(node) {
348
+ if (node.kind === "file") return [];
349
+ return [node.path, ...node.children.flatMap(flattenFolderPaths)];
350
+ }
351
+ function getAncestorPaths(path) {
352
+ const segments = path.split("/").filter(Boolean);
353
+ return segments.map((_, index) => segments.slice(0, index + 1).join("/"));
354
+ }
355
+ //#endregion
356
+ //#region src/routes/explorer.tsx?tsr-split=component
357
+ function ExplorerRoute() {
358
+ const bundle = useModvizBundle();
359
+ const search = Route$1.useSearch();
360
+ const navigate = Route$1.useNavigate();
361
+ return /* @__PURE__ */ jsx(ModvizLayout, {
362
+ projectTitle: bundle.projectTitle,
363
+ title: "File Explorer",
364
+ description: "Browse the monorepo or node_modules tree, select any file, then inspect its direct imports and importers without opening the force-directed graph.",
365
+ children: isModvizBundleReady(bundle) ? /* @__PURE__ */ jsx(ExplorerView, {
366
+ bundle,
367
+ search,
368
+ onSearchChange: (patch) => navigate({
369
+ replace: true,
370
+ search: (previous) => ({
371
+ ...previous,
372
+ ...patch
373
+ })
374
+ })
375
+ }) : /* @__PURE__ */ jsx(SetupView, { bundle })
376
+ });
377
+ }
378
+ //#endregion
379
+ export { ExplorerRoute as component };
@@ -0,0 +1,20 @@
1
+ import { createFileRoute, lazyRouteComponent } from "@tanstack/react-router";
2
+ import { z } from "zod";
3
+ //#region src/routes/explorer.tsx
4
+ var $$splitComponentImporter = () => import("./explorer-C7dODpSv.js");
5
+ var explorerSearchSchema = z.object({
6
+ q: z.string().catch(""),
7
+ selected: z.string().catch(""),
8
+ scope: z.enum([
9
+ "all",
10
+ "workspace",
11
+ "external"
12
+ ]).catch("workspace")
13
+ });
14
+ var Route = createFileRoute("/explorer")({
15
+ ssr: false,
16
+ validateSearch: (search) => explorerSearchSchema.parse(search),
17
+ component: lazyRouteComponent($$splitComponentImporter, "component")
18
+ });
19
+ //#endregion
20
+ export { Route as t };