archtracker-mcp 0.6.0 → 0.7.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.
@@ -0,0 +1,1572 @@
1
+ "use strict";
2
+ (() => {
3
+ // src/web/client/viewer.ts
4
+ var w = window;
5
+ var I18N = {
6
+ en: {
7
+ "tab.graph": "Graph",
8
+ "tab.hierarchy": "Hierarchy",
9
+ "stats.files": "Files",
10
+ "stats.edges": "Edges",
11
+ "stats.circular": "Circular",
12
+ "settings.title": "Settings",
13
+ "settings.theme": "Theme",
14
+ "settings.fontSize": "Font Size",
15
+ "settings.nodeSize": "Node Size",
16
+ "settings.linkOpacity": "Link Opacity",
17
+ "settings.gravity": "Gravity",
18
+ "settings.language": "Language",
19
+ "settings.export": "Export",
20
+ "impact.title": "Impact Simulation",
21
+ "impact.btn": "Impact",
22
+ "impact.transitive": "files affected",
23
+ "search.placeholder": "Search files...",
24
+ "legend.circular": "Circular dep",
25
+ "legend.orphan": "Orphan",
26
+ "legend.highCoupling": "High coupling",
27
+ "legend.imports": "imports",
28
+ "legend.importedBy": "imported by",
29
+ "detail.importedBy": "Imported by",
30
+ "detail.imports": "Imports",
31
+ "detail.none": "none",
32
+ "detail.dir": "Dir",
33
+ "detail.dependencies": "Dependencies",
34
+ "detail.dependents": "Dependents",
35
+ "tooltip.imports": "imports",
36
+ "tooltip.importedBy": "imported by",
37
+ "help.graph": "Scroll: zoom \xB7 Drag: pan \xB7 Click: select \xB7 / search",
38
+ "help.hierarchy": "Scroll to navigate \xB7 Click to highlight",
39
+ "help.diff": "Green=added \xB7 Red=removed \xB7 Yellow=modified \xB7 Blue=affected",
40
+ "tab.diff": "Diff",
41
+ "diff.addedLabel": "Added",
42
+ "diff.removedLabel": "Removed",
43
+ "diff.modifiedLabel": "Modified",
44
+ "diff.affectedLabel": "Affected",
45
+ "diff.showAll": "Show all",
46
+ "diff.focusChanges": "Focus changes",
47
+ "diff.noImpact": "No downstream impact",
48
+ "diff.affectedByChange": "Affected by this change"
49
+ },
50
+ ja: {
51
+ "tab.graph": "\u30B0\u30E9\u30D5",
52
+ "tab.hierarchy": "\u968E\u5C64\u56F3",
53
+ "stats.files": "\u30D5\u30A1\u30A4\u30EB",
54
+ "stats.edges": "\u30A8\u30C3\u30B8",
55
+ "stats.circular": "\u5FAA\u74B0\u53C2\u7167",
56
+ "settings.title": "\u8A2D\u5B9A",
57
+ "settings.theme": "\u30C6\u30FC\u30DE",
58
+ "settings.fontSize": "\u30D5\u30A9\u30F3\u30C8\u30B5\u30A4\u30BA",
59
+ "settings.nodeSize": "\u30CE\u30FC\u30C9\u30B5\u30A4\u30BA",
60
+ "settings.linkOpacity": "\u30EA\u30F3\u30AF\u900F\u660E\u5EA6",
61
+ "settings.gravity": "\u91CD\u529B",
62
+ "settings.language": "\u8A00\u8A9E",
63
+ "settings.export": "\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8",
64
+ "impact.title": "\u5F71\u97FF\u7BC4\u56F2\u30B7\u30DF\u30E5\u30EC\u30FC\u30B7\u30E7\u30F3",
65
+ "impact.btn": "\u5F71\u97FF",
66
+ "impact.transitive": "\u30D5\u30A1\u30A4\u30EB\u306B\u5F71\u97FF",
67
+ "search.placeholder": "\u30D5\u30A1\u30A4\u30EB\u691C\u7D22...",
68
+ "legend.circular": "\u5FAA\u74B0\u53C2\u7167",
69
+ "legend.orphan": "\u5B64\u7ACB",
70
+ "legend.highCoupling": "\u9AD8\u7D50\u5408",
71
+ "legend.imports": "import\u5148",
72
+ "legend.importedBy": "import\u5143",
73
+ "detail.importedBy": "import\u5143",
74
+ "detail.imports": "import\u5148",
75
+ "detail.none": "\u306A\u3057",
76
+ "detail.dir": "\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA",
77
+ "detail.dependencies": "\u4F9D\u5B58\u5148",
78
+ "detail.dependents": "\u88AB\u4F9D\u5B58",
79
+ "tooltip.imports": "import\u5148",
80
+ "tooltip.importedBy": "import\u5143",
81
+ "help.graph": "\u30B9\u30AF\u30ED\u30FC\u30EB: \u30BA\u30FC\u30E0 \xB7 \u30C9\u30E9\u30C3\u30B0: \u79FB\u52D5 \xB7 \u30AF\u30EA\u30C3\u30AF: \u9078\u629E \xB7 / \u691C\u7D22",
82
+ "help.hierarchy": "\u30B9\u30AF\u30ED\u30FC\u30EB\u3067\u79FB\u52D5 \xB7 \u30AF\u30EA\u30C3\u30AF\u3067\u30CF\u30A4\u30E9\u30A4\u30C8",
83
+ "help.diff": "\u7DD1=\u8FFD\u52A0 \xB7 \u8D64=\u524A\u9664 \xB7 \u9EC4=\u5909\u66F4 \xB7 \u9752=\u5F71\u97FF",
84
+ "tab.diff": "\u5DEE\u5206",
85
+ "diff.addedLabel": "\u8FFD\u52A0",
86
+ "diff.removedLabel": "\u524A\u9664",
87
+ "diff.modifiedLabel": "\u5909\u66F4",
88
+ "diff.affectedLabel": "\u5F71\u97FF",
89
+ "diff.showAll": "\u5168\u8868\u793A",
90
+ "diff.focusChanges": "\u5909\u66F4\u306E\u307F\u8868\u793A",
91
+ "diff.noImpact": "\u4E0B\u6D41\u3078\u306E\u5F71\u97FF\u306A\u3057",
92
+ "diff.affectedByChange": "\u3053\u306E\u5909\u66F4\u306E\u5F71\u97FF\u7BC4\u56F2"
93
+ }
94
+ };
95
+ var currentLang = w.__ARCH.locale;
96
+ function applyI18n() {
97
+ const msgs = I18N[currentLang] || I18N.en;
98
+ document.querySelectorAll("[data-i18n]").forEach((el) => {
99
+ const key = el.getAttribute("data-i18n");
100
+ if (msgs[key]) el.textContent = msgs[key];
101
+ });
102
+ document.querySelectorAll("[data-i18n-placeholder]").forEach((el) => {
103
+ const key = el.getAttribute("data-i18n-placeholder");
104
+ if (msgs[key]) el.placeholder = msgs[key];
105
+ });
106
+ document.querySelectorAll(".lang-btn").forEach((b) => b.classList.toggle("active", b.dataset.lang === currentLang));
107
+ }
108
+ w.setLang = (lang) => {
109
+ currentLang = lang;
110
+ applyI18n();
111
+ saveSettings();
112
+ };
113
+ function i(key) {
114
+ return (I18N[currentLang] || I18N.en)[key] || key;
115
+ }
116
+ function esc(s) {
117
+ return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
118
+ }
119
+ var STORAGE_KEY = "archtracker-settings";
120
+ function saveSettings() {
121
+ const s = { theme: document.body.getAttribute("data-theme") || "dark", fontSize: document.getElementById("font-size-val").textContent, nodeSize: document.getElementById("node-size-val").textContent, linkOpacity: document.getElementById("link-opacity-val").textContent, gravity: document.getElementById("gravity-val").textContent, layerGravity: document.getElementById("layer-gravity-val").textContent, lang: currentLang, projectTitle: document.getElementById("project-title").textContent };
122
+ try {
123
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(s));
124
+ } catch (_e) {
125
+ }
126
+ }
127
+ function loadSettings() {
128
+ try {
129
+ return JSON.parse(localStorage.getItem(STORAGE_KEY)) || null;
130
+ } catch (_e) {
131
+ return null;
132
+ }
133
+ }
134
+ var nodeScale = 1;
135
+ var baseLinkOpacity = 0.4;
136
+ w.toggleSettings = () => document.getElementById("settings-panel").classList.toggle("open");
137
+ w.setTheme = (theme) => {
138
+ document.body.setAttribute("data-theme", theme === "light" ? "light" : "");
139
+ document.querySelectorAll(".theme-btn[data-theme-val]").forEach((b) => b.classList.toggle("active", b.dataset.themeVal === theme));
140
+ saveSettings();
141
+ };
142
+ w.setFontSize = (v) => {
143
+ document.getElementById("font-size-val").textContent = v;
144
+ const scale = v / 13;
145
+ if (typeof node !== "undefined") {
146
+ node.select("text").attr("font-size", (d) => (d.dependents >= 3 ? 12 : 10) * scale);
147
+ }
148
+ saveSettings();
149
+ };
150
+ w.setNodeScale = (v) => {
151
+ nodeScale = v / 100;
152
+ document.getElementById("node-size-val").textContent = v;
153
+ if (typeof node !== "undefined") {
154
+ node.select("circle").attr("r", (d) => nodeRadius(d) * nodeScale);
155
+ node.select("text").attr("dx", (d) => nodeRadius(d) * nodeScale + 4);
156
+ simulation.force("collision", d3.forceCollide().radius((d) => nodeRadius(d) * nodeScale + 4));
157
+ simulation.alpha(0.3).restart();
158
+ }
159
+ saveSettings();
160
+ };
161
+ w.setLinkOpacity = (v) => {
162
+ baseLinkOpacity = v / 100;
163
+ document.getElementById("link-opacity-val").textContent = v;
164
+ if (typeof link !== "undefined") link.attr("opacity", baseLinkOpacity);
165
+ saveSettings();
166
+ };
167
+ var gravityStrength = 150;
168
+ w.setGravity = (v) => {
169
+ gravityStrength = +v;
170
+ document.getElementById("gravity-val").textContent = v;
171
+ if (typeof simulation !== "undefined") {
172
+ if (typeof updateLayerPhysics === "function") {
173
+ updateLayerPhysics();
174
+ } else {
175
+ simulation.force("charge", d3.forceManyBody().strength(-gravityStrength).distanceMax(500));
176
+ }
177
+ simulation.alpha(0.5).restart();
178
+ }
179
+ saveSettings();
180
+ };
181
+ var layerGravity = 12;
182
+ w.setLayerGravity = (v) => {
183
+ layerGravity = +v;
184
+ document.getElementById("layer-gravity-val").textContent = v;
185
+ if (typeof simulation !== "undefined" && typeof updateLayerPhysics === "function") {
186
+ updateLayerPhysics();
187
+ simulation.alpha(0.5).restart();
188
+ }
189
+ saveSettings();
190
+ };
191
+ w.exportSVG = () => {
192
+ const activeView = document.querySelector(".view.active svg");
193
+ if (!activeView) return;
194
+ const clone = activeView.cloneNode(true);
195
+ clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
196
+ const blob = new Blob([clone.outerHTML], { type: "image/svg+xml" });
197
+ const a = document.createElement("a");
198
+ a.href = URL.createObjectURL(blob);
199
+ a.download = (document.getElementById("project-title").textContent || "graph") + ".svg";
200
+ a.click();
201
+ URL.revokeObjectURL(a.href);
202
+ };
203
+ w.exportPNG = () => {
204
+ const activeView = document.querySelector(".view.active svg");
205
+ if (!activeView) return;
206
+ const clone = activeView.cloneNode(true);
207
+ clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
208
+ const svgStr = new XMLSerializer().serializeToString(clone);
209
+ const canvas = document.createElement("canvas");
210
+ const bbox = activeView.getBoundingClientRect();
211
+ canvas.width = bbox.width * 2;
212
+ canvas.height = bbox.height * 2;
213
+ const ctx = canvas.getContext("2d");
214
+ ctx.scale(2, 2);
215
+ const img = new Image();
216
+ img.onload = () => {
217
+ ctx.drawImage(img, 0, 0);
218
+ const a = document.createElement("a");
219
+ a.href = canvas.toDataURL("image/png");
220
+ a.download = (document.getElementById("project-title").textContent || "graph") + ".png";
221
+ a.click();
222
+ };
223
+ img.src = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgStr)));
224
+ };
225
+ var DATA = w.__ARCH.data;
226
+ var LAYERS = w.__ARCH.layers;
227
+ var CROSS_EDGES = w.__ARCH.crossEdges;
228
+ var W = window.innerWidth;
229
+ var H = window.innerHeight - 44;
230
+ var circularSet = new Set(DATA.circularFiles);
231
+ var titleEl = document.getElementById("project-title");
232
+ titleEl.textContent = DATA.projectName;
233
+ titleEl.addEventListener("blur", () => {
234
+ if (!titleEl.textContent.trim()) titleEl.textContent = DATA.projectName;
235
+ document.title = titleEl.textContent + " \u2014 Architecture Viewer";
236
+ saveSettings();
237
+ });
238
+ titleEl.addEventListener("keydown", (e) => {
239
+ if (e.key === "Enter") {
240
+ e.preventDefault();
241
+ titleEl.blur();
242
+ }
243
+ });
244
+ var _savedSettings = loadSettings();
245
+ if (_savedSettings) {
246
+ if (_savedSettings.theme) w.setTheme(_savedSettings.theme);
247
+ if (_savedSettings.lang) {
248
+ currentLang = _savedSettings.lang;
249
+ applyI18n();
250
+ }
251
+ if (_savedSettings.projectTitle) {
252
+ titleEl.textContent = _savedSettings.projectTitle;
253
+ document.title = _savedSettings.projectTitle + " \u2014 Architecture Viewer";
254
+ }
255
+ if (_savedSettings.fontSize) {
256
+ document.getElementById("font-size-slider").value = _savedSettings.fontSize;
257
+ document.getElementById("font-size-val").textContent = _savedSettings.fontSize;
258
+ }
259
+ if (_savedSettings.nodeSize) {
260
+ document.getElementById("node-size-slider").value = _savedSettings.nodeSize;
261
+ document.getElementById("node-size-val").textContent = _savedSettings.nodeSize;
262
+ nodeScale = _savedSettings.nodeSize / 100;
263
+ }
264
+ if (_savedSettings.linkOpacity) {
265
+ document.getElementById("link-opacity-slider").value = _savedSettings.linkOpacity;
266
+ document.getElementById("link-opacity-val").textContent = _savedSettings.linkOpacity;
267
+ baseLinkOpacity = _savedSettings.linkOpacity / 100;
268
+ }
269
+ if (_savedSettings.gravity) {
270
+ document.getElementById("gravity-slider").value = _savedSettings.gravity;
271
+ document.getElementById("gravity-val").textContent = _savedSettings.gravity;
272
+ gravityStrength = +_savedSettings.gravity;
273
+ }
274
+ if (_savedSettings.layerGravity) {
275
+ document.getElementById("layer-gravity-slider").value = _savedSettings.layerGravity;
276
+ document.getElementById("layer-gravity-val").textContent = _savedSettings.layerGravity;
277
+ layerGravity = +_savedSettings.layerGravity;
278
+ }
279
+ }
280
+ document.getElementById("s-files").textContent = String(DATA.nodes.length);
281
+ document.getElementById("s-edges").textContent = String(DATA.links.length);
282
+ document.getElementById("s-circular").textContent = String(DATA.circularFiles.length);
283
+ var dirColor = d3.scaleOrdinal().domain(DATA.dirs).range(["#58a6ff", "#3fb950", "#d2a8ff", "#f0883e", "#79c0ff", "#56d4dd", "#db61a2", "#f778ba", "#ffa657", "#7ee787"]);
284
+ var layerColorMap = {};
285
+ var activeLayers = /* @__PURE__ */ new Set();
286
+ if (LAYERS) {
287
+ LAYERS.forEach((l) => {
288
+ layerColorMap[l.name] = l.color;
289
+ });
290
+ document.getElementById("layer-gravity-setting").style.display = "";
291
+ }
292
+ function nodeColor(d) {
293
+ if (circularSet.has(d.id)) return "#f97583";
294
+ if (d.isOrphan) return "#484f58";
295
+ if (LAYERS && d.layer && layerColorMap[d.layer] && activeLayers.size !== 1) return layerColorMap[d.layer];
296
+ return dirColor(d.dir);
297
+ }
298
+ function nodeRadius(d) {
299
+ return Math.max(5, Math.min(22, 4 + d.dependents * 1.8));
300
+ }
301
+ function fileName(id) {
302
+ return id.split("/").pop();
303
+ }
304
+ var hierBuilt = false;
305
+ var diffBuilt = false;
306
+ var hierRelayout = null;
307
+ var hierSyncFromTab = null;
308
+ document.querySelectorAll(".tab").forEach((tab) => {
309
+ tab.addEventListener("click", () => {
310
+ document.querySelectorAll(".tab").forEach((t) => t.classList.remove("active"));
311
+ document.querySelectorAll(".view").forEach((v) => v.classList.remove("active"));
312
+ tab.classList.add("active");
313
+ document.getElementById(tab.dataset.view).classList.add("active");
314
+ if (tab.dataset.view === "hier-view") {
315
+ if (!hierBuilt) {
316
+ buildHierarchy();
317
+ hierBuilt = true;
318
+ }
319
+ if (hierSyncFromTab) {
320
+ hierSyncFromTab();
321
+ hierRelayout();
322
+ }
323
+ }
324
+ if (tab.dataset.view === "diff-view") {
325
+ if (!diffBuilt && buildDiffView) {
326
+ buildDiffView();
327
+ diffBuilt = true;
328
+ }
329
+ }
330
+ });
331
+ });
332
+ var tooltip = document.getElementById("tooltip");
333
+ var tooltipHideTimer = null;
334
+ var tooltipLocked = false;
335
+ function showTooltip(e, d) {
336
+ clearTimeout(tooltipHideTimer);
337
+ document.getElementById("tt-name").textContent = d.id;
338
+ document.getElementById("tt-dep-count").textContent = d.deps;
339
+ document.getElementById("tt-dpt-count").textContent = d.dependents;
340
+ const out = (d.dependencies || []).map((x) => '<div class="tt-out">\u2192 ' + esc(x) + "</div>");
341
+ const inc = (d.dependentsList || []).map((x) => '<div class="tt-in">\u2190 ' + esc(x) + "</div>");
342
+ document.getElementById("tt-details").innerHTML = [...out, ...inc].join("");
343
+ tooltip.style.display = "block";
344
+ positionTooltip(e);
345
+ }
346
+ function positionTooltip(e) {
347
+ const gap = 24;
348
+ const tw = 420, th = tooltip.offsetHeight || 200;
349
+ let x = e.clientX + gap;
350
+ let y = e.clientY - th - 12;
351
+ if (x + tw > window.innerWidth) x = e.clientX - tw - gap;
352
+ if (y < 50) y = e.clientY + gap;
353
+ if (y + th > window.innerHeight) y = window.innerHeight - th - 8;
354
+ if (x < 8) x = 8;
355
+ tooltip.style.left = x + "px";
356
+ tooltip.style.top = y + "px";
357
+ }
358
+ function scheduleHideTooltip() {
359
+ clearTimeout(tooltipHideTimer);
360
+ tooltipHideTimer = setTimeout(() => {
361
+ if (!tooltipLocked) {
362
+ tooltip.style.display = "none";
363
+ if (!pinnedNode) resetGraphHighlight();
364
+ }
365
+ }, 250);
366
+ }
367
+ tooltip.addEventListener("mouseenter", () => {
368
+ clearTimeout(tooltipHideTimer);
369
+ tooltipLocked = true;
370
+ });
371
+ tooltip.addEventListener("mouseleave", () => {
372
+ tooltipLocked = false;
373
+ scheduleHideTooltip();
374
+ });
375
+ var svg = d3.select("#graph-svg").attr("width", W).attr("height", H);
376
+ var g = svg.append("g");
377
+ var zoom = d3.zoom().scaleExtent([0.05, 10]).on("zoom", (e) => g.attr("transform", e.transform));
378
+ svg.call(zoom);
379
+ svg.call(zoom.transform, d3.zoomIdentity.translate(W / 2, H / 2).scale(0.7));
380
+ w.zoomIn = () => svg.transition().duration(300).call(zoom.scaleBy, 1.4);
381
+ w.zoomOut = () => svg.transition().duration(300).call(zoom.scaleBy, 0.7);
382
+ w.zoomFit = () => {
383
+ const b = g.node().getBBox();
384
+ if (!b.width) return;
385
+ const s = Math.min(W / (b.width + 80), H / (b.height + 80)) * 0.9;
386
+ svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity.translate(W / 2 - (b.x + b.width / 2) * s, H / 2 - (b.y + b.height / 2) * s).scale(s));
387
+ };
388
+ var defs = svg.append("defs");
389
+ ["#30363d", "#58a6ff", "#3fb950"].forEach((c, idx) => {
390
+ defs.append("marker").attr("id", "arrow-" + idx).attr("viewBox", "0 -4 8 8").attr("refX", 8).attr("refY", 0).attr("markerWidth", 7).attr("markerHeight", 7).attr("orient", "auto").append("path").attr("d", "M0,-3.5L8,0L0,3.5Z").attr("fill", c);
391
+ });
392
+ var link = g.append("g").selectAll("line").data(DATA.links).join("line").attr("stroke", (d) => d.type === "type-only" ? "#1f3d5c" : "#30363d").attr("stroke-width", 1).attr("stroke-dasharray", (d) => d.type === "type-only" ? "4,3" : d.type === "dynamic" ? "6,3" : null).attr("marker-end", "url(#arrow-0)").attr("opacity", baseLinkOpacity);
393
+ defs.append("marker").attr("id", "arrow-cross").attr("viewBox", "0 -4 8 8").attr("refX", 8).attr("refY", 0).attr("markerWidth", 7).attr("markerHeight", 7).attr("orient", "auto").append("path").attr("d", "M0,-3.5L8,0L0,3.5Z").attr("fill", "#f0883e");
394
+ var crossLinkData = (CROSS_EDGES || []).map((e) => ({
395
+ source: e.fromLayer + "/" + e.fromFile,
396
+ target: e.toLayer + "/" + e.toFile,
397
+ sourceLayer: e.fromLayer,
398
+ targetLayer: e.toLayer,
399
+ type: e.type || "api-call",
400
+ label: e.label || e.type || ""
401
+ })).filter((e) => DATA.nodes.some((n) => n.id === e.source) && DATA.nodes.some((n) => n.id === e.target));
402
+ var crossLinkG = g.append("g");
403
+ var crossLink = crossLinkG.selectAll("line").data(crossLinkData).join("line").attr("stroke", "#f0883e").attr("stroke-width", 2).attr("stroke-dasharray", "8,4").attr("marker-end", "url(#arrow-cross)").attr("opacity", 0.7);
404
+ var crossLabel = crossLinkG.selectAll("text").data(crossLinkData).join("text").text((d) => d.label).attr("font-size", 9).attr("fill", "#f0883e").attr("text-anchor", "middle").attr("opacity", 0.8).attr("pointer-events", "none");
405
+ var node = g.append("g").selectAll("g").data(DATA.nodes).join("g").attr("cursor", "pointer").call(d3.drag().on("start", dragStart).on("drag", dragging).on("end", dragEnd));
406
+ node.append("circle").attr("r", (d) => nodeRadius(d) * nodeScale).attr("fill", nodeColor).attr("stroke", (d) => d.deps >= 5 ? "var(--yellow)" : nodeColor(d)).attr("stroke-width", (d) => d.deps >= 5 ? 2.5 : 1.5).attr("stroke-opacity", (d) => d.deps >= 5 ? 0.8 : 0.3);
407
+ node.append("text").text((d) => fileName(d.id).replace(/\.tsx?$/, "")).attr("dx", (d) => nodeRadius(d) * nodeScale + 4).attr("dy", 3.5).attr("font-size", (d) => d.dependents >= 3 ? 12 : 10).attr("font-weight", (d) => d.dependents >= 3 ? 600 : 400).attr("fill", (d) => d.dependents >= 3 ? "var(--text)" : "var(--text-dim)").attr("opacity", (d) => d.dependents >= 1 || d.deps >= 3 ? 1 : 0.5).attr("pointer-events", "none");
408
+ var simulation = d3.forceSimulation(DATA.nodes).force("link", d3.forceLink(DATA.links).id((d) => d.id).distance(70).strength(0.25)).force("charge", d3.forceManyBody().strength(-gravityStrength).distanceMax(500)).force("center", d3.forceCenter(0, 0)).force("collision", d3.forceCollide().radius((d) => nodeRadius(d) * nodeScale + 4)).force("x", d3.forceX(0).strength(0.03)).force("y", d3.forceY(0).strength(0.03)).on("tick", () => {
409
+ link.each(function(d) {
410
+ const dx = d.target.x - d.source.x, dy = d.target.y - d.source.y;
411
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
412
+ const rT = nodeRadius(d.target) * nodeScale, rS = nodeRadius(d.source) * nodeScale;
413
+ d3.select(this).attr("x1", d.source.x + dx / dist * rS).attr("y1", d.source.y + dy / dist * rS).attr("x2", d.target.x - dx / dist * rT).attr("y2", d.target.y - dy / dist * rT);
414
+ });
415
+ node.attr("transform", (d) => `translate(${d.x},${d.y})`);
416
+ });
417
+ var hullGroup = null;
418
+ var activeDirs = new Set(DATA.dirs);
419
+ var dirCounts = {};
420
+ DATA.nodes.forEach((n) => dirCounts[n.dir] = (dirCounts[n.dir] || 0) + 1);
421
+ var applyLayerFilter = null;
422
+ var updateLayerPhysics = null;
423
+ var crossLinksUserEnabled = true;
424
+ if (LAYERS && LAYERS.length > 0) {
425
+ let getLayerCenters = function() {
426
+ if (activeLayers.size <= 1) return allLayerCenters;
427
+ const selected = LAYERS.filter((l) => activeLayers.has(l.name));
428
+ const count = selected.length;
429
+ const compactRadius = Math.max(40, Math.min(W, H) * 0.03 * Math.sqrt(count));
430
+ const centers = {};
431
+ selected.forEach((l, idx) => {
432
+ const angle = 2 * Math.PI * idx / count - Math.PI / 2;
433
+ centers[l.name] = { x: Math.cos(angle) * compactRadius, y: Math.sin(angle) * compactRadius };
434
+ });
435
+ return centers;
436
+ }, clusterForce = function() {
437
+ let nodes;
438
+ function force(alpha) {
439
+ const centroids = {};
440
+ const counts = {};
441
+ nodes.forEach((n) => {
442
+ if (!n.layer) return;
443
+ if (!centroids[n.layer]) {
444
+ centroids[n.layer] = { x: 0, y: 0 };
445
+ counts[n.layer] = 0;
446
+ }
447
+ centroids[n.layer].x += n.x;
448
+ centroids[n.layer].y += n.y;
449
+ counts[n.layer]++;
450
+ });
451
+ Object.keys(centroids).forEach((k) => {
452
+ centroids[k].x /= counts[k];
453
+ centroids[k].y /= counts[k];
454
+ });
455
+ const strength = 0.2;
456
+ nodes.forEach((n) => {
457
+ if (!n.layer || !centroids[n.layer]) return;
458
+ n.vx += (centroids[n.layer].x - n.x) * alpha * strength;
459
+ n.vy += (centroids[n.layer].y - n.y) * alpha * strength;
460
+ });
461
+ }
462
+ force.initialize = (n) => {
463
+ nodes = n;
464
+ };
465
+ return force;
466
+ }, updateHulls = function() {
467
+ if (!hullGroup) return;
468
+ hullGroup.selectAll("*").remove();
469
+ LAYERS.forEach((layer) => {
470
+ if (activeLayers.size > 0 && !activeLayers.has(layer.name)) return;
471
+ const layerNodes = DATA.nodes.filter((n) => n.layer === layer.name);
472
+ if (layerNodes.length === 0) return;
473
+ const points = [];
474
+ layerNodes.forEach((n) => {
475
+ if (n.x == null || n.y == null) return;
476
+ const r = nodeRadius(n) * nodeScale + 30;
477
+ for (let a = 0; a < Math.PI * 2; a += Math.PI / 4) {
478
+ points.push([n.x + Math.cos(a) * r, n.y + Math.sin(a) * r]);
479
+ }
480
+ });
481
+ if (points.length < 3) {
482
+ const cx = layerNodes.reduce((s, n) => s + (n.x || 0), 0) / layerNodes.length;
483
+ const cy = layerNodes.reduce((s, n) => s + (n.y || 0), 0) / layerNodes.length;
484
+ const maxR = Math.max(60, ...layerNodes.map((n) => {
485
+ const dx = (n.x || 0) - cx, dy = (n.y || 0) - cy;
486
+ return Math.sqrt(dx * dx + dy * dy) + nodeRadius(n) * nodeScale + 30;
487
+ }));
488
+ hullGroup.append("circle").attr("cx", cx).attr("cy", cy).attr("r", maxR).attr("class", "layer-hull").attr("fill", layer.color).attr("stroke", layer.color);
489
+ hullGroup.append("text").attr("class", "layer-hull-label").attr("x", cx).attr("y", cy - maxR - 8).attr("text-anchor", "middle").attr("fill", layer.color).text(layer.name);
490
+ return;
491
+ }
492
+ const hull = d3.polygonHull(points);
493
+ if (!hull) return;
494
+ hullGroup.append("path").attr("class", "layer-hull").attr("d", d3.line().curve(d3.curveCatmullRomClosed.alpha(0.5))(hull)).attr("fill", layer.color).attr("stroke", layer.color);
495
+ const topPt = hull.reduce((best, p) => p[1] < best[1] ? p : best, hull[0]);
496
+ hullGroup.append("text").attr("class", "layer-hull-label").attr("x", topPt[0]).attr("y", topPt[1] - 10).attr("text-anchor", "middle").attr("fill", layer.color).text(layer.name);
497
+ });
498
+ }, syncLayerTabUI = function() {
499
+ allTab.classList.toggle("active", activeLayers.size === 0);
500
+ layerTabsEl.querySelectorAll(".layer-tab[data-layer]").forEach((t) => {
501
+ t.classList.toggle("active", activeLayers.has(t.dataset.layer));
502
+ });
503
+ layerRowEl.querySelectorAll(".layer-pill[data-layer]").forEach((p) => {
504
+ p.classList.toggle("active", activeLayers.has(p.dataset.layer));
505
+ });
506
+ };
507
+ getLayerCenters2 = getLayerCenters, clusterForce2 = clusterForce, updateHulls2 = updateHulls, syncLayerTabUI2 = syncLayerTabUI;
508
+ const allLayerCount = LAYERS.length;
509
+ const allBaseRadius = Math.max(60, Math.min(W, H) * 0.04 * Math.sqrt(allLayerCount));
510
+ const allLayerCenters = {};
511
+ LAYERS.forEach((l, idx) => {
512
+ const angle = 2 * Math.PI * idx / allLayerCount - Math.PI / 2;
513
+ allLayerCenters[l.name] = { x: Math.cos(angle) * allBaseRadius, y: Math.sin(angle) * allBaseRadius };
514
+ });
515
+ const layerStrength = layerGravity / 100;
516
+ simulation.force("x", null).force("y", null).force("center", null);
517
+ simulation.force("layerX", d3.forceX((d) => allLayerCenters[d.layer]?.x || 0).strength((d) => d.layer ? layerStrength : 0.03));
518
+ simulation.force("layerY", d3.forceY((d) => allLayerCenters[d.layer]?.y || 0).strength((d) => d.layer ? layerStrength : 0.03));
519
+ simulation.force("cluster", clusterForce());
520
+ simulation.force("link").strength((l) => {
521
+ const sLayer = l.source.layer ?? l.source;
522
+ const tLayer = l.target.layer ?? l.target;
523
+ return sLayer === tLayer ? 0.4 : 0.1;
524
+ });
525
+ hullGroup = g.insert("g", ":first-child");
526
+ simulation.on("tick", () => {
527
+ link.each(function(d) {
528
+ const dx = d.target.x - d.source.x, dy = d.target.y - d.source.y;
529
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
530
+ const rT = nodeRadius(d.target) * nodeScale, rS = nodeRadius(d.source) * nodeScale;
531
+ d3.select(this).attr("x1", d.source.x + dx / dist * rS).attr("y1", d.source.y + dy / dist * rS).attr("x2", d.target.x - dx / dist * rT).attr("y2", d.target.y - dy / dist * rT);
532
+ });
533
+ node.attr("transform", (d) => `translate(${d.x},${d.y})`);
534
+ if (crossLinkData.length > 0) {
535
+ const nodeById = {};
536
+ DATA.nodes.forEach((n) => {
537
+ nodeById[n.id] = n;
538
+ });
539
+ crossLink.each(function(d) {
540
+ const sN = nodeById[d.source], tN = nodeById[d.target];
541
+ if (!sN || !tN) return;
542
+ const dx = tN.x - sN.x, dy = tN.y - sN.y;
543
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
544
+ const rS = nodeRadius(sN) * nodeScale, rT = nodeRadius(tN) * nodeScale;
545
+ d3.select(this).attr("x1", sN.x + dx / dist * rS).attr("y1", sN.y + dy / dist * rS).attr("x2", tN.x - dx / dist * rT).attr("y2", tN.y - dy / dist * rT);
546
+ });
547
+ crossLabel.each(function(d) {
548
+ const sN = nodeById[d.source], tN = nodeById[d.target];
549
+ if (!sN || !tN) return;
550
+ d3.select(this).attr("x", (sN.x + tN.x) / 2).attr("y", (sN.y + tN.y) / 2 - 6);
551
+ });
552
+ }
553
+ updateHulls();
554
+ });
555
+ const layerLegend = document.getElementById("layer-legend");
556
+ LAYERS.forEach((layer) => {
557
+ const item = document.createElement("div");
558
+ item.className = "legend-item";
559
+ item.innerHTML = '<div class="legend-dot" style="background:' + esc(layer.color) + '"></div> ' + esc(layer.name);
560
+ layerLegend.appendChild(item);
561
+ });
562
+ if (CROSS_EDGES && CROSS_EDGES.length > 0) {
563
+ const crossItem = document.createElement("div");
564
+ crossItem.className = "legend-item";
565
+ crossItem.innerHTML = '<span style="color:#f0883e;font-size:11px">- - \u2192</span> Cross-layer link';
566
+ layerLegend.appendChild(crossItem);
567
+ }
568
+ const sep = document.createElement("hr");
569
+ sep.style.cssText = "border:none;border-top:1px solid var(--border);margin:6px 0;";
570
+ layerLegend.appendChild(sep);
571
+ const layerTabsEl = document.getElementById("layer-tabs");
572
+ const allTab = document.createElement("div");
573
+ allTab.className = "layer-tab active";
574
+ allTab.textContent = "All";
575
+ allTab.onclick = () => {
576
+ activeLayers.clear();
577
+ syncLayerTabUI();
578
+ applyLayerFilter();
579
+ if (hierBuilt && hierSyncFromTab) {
580
+ hierSyncFromTab();
581
+ hierRelayout();
582
+ }
583
+ };
584
+ layerTabsEl.appendChild(allTab);
585
+ LAYERS.forEach((layer) => {
586
+ const tab = document.createElement("div");
587
+ tab.className = "layer-tab";
588
+ tab.dataset.layer = layer.name;
589
+ tab.innerHTML = '<div class="lt-dot" style="background:' + esc(layer.color) + '"></div>' + esc(layer.name);
590
+ tab.onclick = (e) => {
591
+ if (e.shiftKey) {
592
+ activeLayers.clear();
593
+ activeLayers.add(layer.name);
594
+ } else {
595
+ if (activeLayers.has(layer.name)) activeLayers.delete(layer.name);
596
+ else activeLayers.add(layer.name);
597
+ }
598
+ syncLayerTabUI();
599
+ applyLayerFilter();
600
+ if (hierBuilt && hierSyncFromTab) {
601
+ hierSyncFromTab();
602
+ hierRelayout();
603
+ }
604
+ };
605
+ layerTabsEl.appendChild(tab);
606
+ });
607
+ applyLayerFilter = function() {
608
+ const isSingleLayer = activeLayers.size === 1;
609
+ const hasLayerFilter = activeLayers.size > 0;
610
+ node.attr("display", (d) => {
611
+ if (!activeDirs.has(d.dir)) return "none";
612
+ if (hasLayerFilter && !activeLayers.has(d.layer)) return "none";
613
+ return null;
614
+ });
615
+ link.attr("display", (l) => {
616
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
617
+ const sN = DATA.nodes.find((n) => n.id === s), tN = DATA.nodes.find((n) => n.id === t);
618
+ if (!sN || !tN) return "none";
619
+ if (!activeDirs.has(sN.dir) || !activeDirs.has(tN.dir)) return "none";
620
+ if (hasLayerFilter && (!activeLayers.has(sN.layer) || !activeLayers.has(tN.layer))) return "none";
621
+ return null;
622
+ });
623
+ node.select("circle").attr("fill", nodeColor).attr("stroke", (d) => d.deps >= 5 ? "var(--yellow)" : nodeColor(d));
624
+ if (typeof crossLink !== "undefined") {
625
+ if (!crossLinksUserEnabled || isSingleLayer) {
626
+ crossLink.attr("display", "none");
627
+ crossLabel.attr("display", "none");
628
+ } else if (hasLayerFilter) {
629
+ crossLink.attr("display", (d) => activeLayers.has(d.sourceLayer) && activeLayers.has(d.targetLayer) ? null : "none");
630
+ crossLabel.attr("display", (d) => activeLayers.has(d.sourceLayer) && activeLayers.has(d.targetLayer) ? null : "none");
631
+ } else {
632
+ crossLink.attr("display", null);
633
+ crossLabel.attr("display", null);
634
+ }
635
+ }
636
+ const visibleNodes = DATA.nodes.filter((d) => {
637
+ if (!activeDirs.has(d.dir)) return false;
638
+ if (hasLayerFilter && !activeLayers.has(d.layer)) return false;
639
+ return true;
640
+ });
641
+ const visibleIds = new Set(visibleNodes.map((n) => n.id));
642
+ const visibleEdges = DATA.links.filter((l) => {
643
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
644
+ return visibleIds.has(s) && visibleIds.has(t);
645
+ });
646
+ document.getElementById("s-files").textContent = String(visibleNodes.length);
647
+ document.getElementById("s-edges").textContent = String(visibleEdges.length);
648
+ const visCirc = DATA.circularFiles.filter((f) => visibleIds.has(f));
649
+ document.getElementById("s-circular").textContent = String(visCirc.length);
650
+ updateHulls();
651
+ updateLayerPhysics();
652
+ simulation.alpha(0.6).restart();
653
+ setTimeout(() => w.zoomFit(), 600);
654
+ };
655
+ updateLayerPhysics = function() {
656
+ const isSingleLayer = activeLayers.size === 1;
657
+ const lStrength = layerGravity / 100;
658
+ if (isSingleLayer) {
659
+ simulation.force("charge", d3.forceManyBody().strength(-gravityStrength * 3).distanceMax(800));
660
+ simulation.force("layerX", d3.forceX(0).strength(0.03));
661
+ simulation.force("layerY", d3.forceY(0).strength(0.03));
662
+ } else {
663
+ const centers = getLayerCenters();
664
+ simulation.force("charge", d3.forceManyBody().strength(-gravityStrength).distanceMax(500));
665
+ simulation.force("layerX", d3.forceX((d) => centers[d.layer]?.x || 0).strength((d) => d.layer ? lStrength : 0.03));
666
+ simulation.force("layerY", d3.forceY((d) => centers[d.layer]?.y || 0).strength((d) => d.layer ? lStrength : 0.03));
667
+ }
668
+ };
669
+ const layerRowEl = document.getElementById("filter-layer-row");
670
+ const dirPanelEl = document.getElementById("filter-dir-panel");
671
+ const dirToggle = document.createElement("div");
672
+ dirToggle.id = "filter-dir-toggle";
673
+ dirToggle.textContent = "\u25B8 Dirs";
674
+ dirToggle.onclick = () => {
675
+ dirToggle.classList.toggle("open");
676
+ dirPanelEl.classList.toggle("open");
677
+ dirToggle.textContent = dirPanelEl.classList.contains("open") ? "\u25BE Dirs" : "\u25B8 Dirs";
678
+ };
679
+ layerRowEl.appendChild(dirToggle);
680
+ if (crossLinkData.length > 0) {
681
+ document.getElementById("cross-layer-setting").style.display = "";
682
+ w.toggleCrossLinks = () => {
683
+ crossLinksUserEnabled = !crossLinksUserEnabled;
684
+ const btn = document.getElementById("cross-link-toggle");
685
+ btn.textContent = crossLinksUserEnabled ? "ON" : "OFF";
686
+ btn.classList.toggle("active", crossLinksUserEnabled);
687
+ applyLayerFilter();
688
+ };
689
+ }
690
+ LAYERS.forEach((layer) => {
691
+ const layerNodes = DATA.nodes.filter((n) => n.layer === layer.name);
692
+ const pill = document.createElement("div");
693
+ pill.className = "layer-pill";
694
+ pill.dataset.layer = layer.name;
695
+ pill.innerHTML = '<div class="lp-dot" style="background:' + esc(layer.color) + '"></div>' + esc(layer.name) + ' <span class="lp-count">' + layerNodes.length + "</span>";
696
+ pill.onclick = () => {
697
+ if (activeLayers.has(layer.name)) activeLayers.delete(layer.name);
698
+ else activeLayers.add(layer.name);
699
+ syncLayerTabUI();
700
+ applyLayerFilter();
701
+ };
702
+ pill.onmouseenter = () => {
703
+ if (pinnedNode) return;
704
+ node.select("circle").transition().duration(120).attr("opacity", (d) => d.layer === layer.name ? 1 : 0.1);
705
+ node.select("text").transition().duration(120).attr("opacity", (d) => d.layer === layer.name ? 1 : 0.05);
706
+ };
707
+ pill.onmouseleave = () => {
708
+ if (pinnedNode) return;
709
+ node.select("circle").transition().duration(150).attr("opacity", 1);
710
+ node.select("text").transition().duration(150).attr("opacity", (d) => d.dependents >= 1 || d.deps >= 3 ? 1 : 0.5);
711
+ };
712
+ layerRowEl.appendChild(pill);
713
+ const layerDirs = [...new Set(layerNodes.map((n) => n.dir))].sort();
714
+ if (layerDirs.length > 0) {
715
+ const group = document.createElement("div");
716
+ group.className = "dir-group";
717
+ const label = document.createElement("div");
718
+ label.className = "dir-group-label";
719
+ label.innerHTML = '<div class="dg-dot" style="background:' + esc(layer.color) + '"></div>' + esc(layer.name);
720
+ group.appendChild(label);
721
+ const pillsWrap = document.createElement("div");
722
+ pillsWrap.className = "dir-group-pills";
723
+ layerDirs.forEach((dir) => {
724
+ const dp = document.createElement("div");
725
+ dp.className = "filter-pill active";
726
+ const shortDir = dir.includes("/") ? dir.substring(dir.indexOf("/") + 1) : dir;
727
+ dp.innerHTML = '<div class="pill-dot" style="background:' + dirColor(dir) + '"></div>' + esc(shortDir || ".") + ' <span class="pill-count">' + (dirCounts[dir] || 0) + "</span>";
728
+ dp.onclick = () => {
729
+ if (activeDirs.has(dir)) {
730
+ activeDirs.delete(dir);
731
+ dp.classList.remove("active");
732
+ } else {
733
+ activeDirs.add(dir);
734
+ dp.classList.add("active");
735
+ }
736
+ applyLayerFilter();
737
+ };
738
+ pillsWrap.appendChild(dp);
739
+ });
740
+ group.appendChild(pillsWrap);
741
+ dirPanelEl.appendChild(group);
742
+ }
743
+ });
744
+ w._origApplyFilter = applyFilter;
745
+ }
746
+ var getLayerCenters2;
747
+ var clusterForce2;
748
+ var updateHulls2;
749
+ var syncLayerTabUI2;
750
+ setTimeout(() => w.zoomFit(), 1500);
751
+ if (_savedSettings) {
752
+ if (_savedSettings.fontSize) w.setFontSize(_savedSettings.fontSize);
753
+ }
754
+ var pinnedNode = null;
755
+ function highlightNode(d) {
756
+ const conn = /* @__PURE__ */ new Set([d.id]);
757
+ DATA.links.forEach((l) => {
758
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
759
+ if (s === d.id) conn.add(t);
760
+ if (t === d.id) conn.add(s);
761
+ });
762
+ node.select("circle").transition().duration(150).attr("opacity", (n) => conn.has(n.id) ? 1 : 0.1);
763
+ node.select("text").transition().duration(150).attr("opacity", (n) => conn.has(n.id) ? 1 : 0.05);
764
+ link.transition().duration(150).attr("opacity", (l) => {
765
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
766
+ return s === d.id || t === d.id ? 0.9 : 0.03;
767
+ }).attr("stroke", (l) => {
768
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
769
+ if (s === d.id) return "#58a6ff";
770
+ if (t === d.id) return "#3fb950";
771
+ return l.type === "type-only" ? "#1f3d5c" : "#30363d";
772
+ }).attr("stroke-width", (l) => {
773
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
774
+ return s === d.id || t === d.id ? 2 : 1;
775
+ }).attr("marker-end", (l) => {
776
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
777
+ if (s === d.id) return "url(#arrow-1)";
778
+ if (t === d.id) return "url(#arrow-2)";
779
+ return "url(#arrow-0)";
780
+ });
781
+ }
782
+ function resetGraphHighlight() {
783
+ pinnedNode = null;
784
+ node.select("circle").transition().duration(200).attr("opacity", 1);
785
+ node.select("text").transition().duration(200).attr("opacity", (d) => d.dependents >= 1 || d.deps >= 3 ? 1 : 0.5);
786
+ link.transition().duration(200).attr("opacity", baseLinkOpacity).attr("stroke", (d) => d.type === "type-only" ? "#1f3d5c" : "#30363d").attr("stroke-width", 1).attr("marker-end", "url(#arrow-0)");
787
+ }
788
+ node.on("mouseover", (e, d) => {
789
+ showTooltip(e, d);
790
+ if (!pinnedNode) highlightNode(d);
791
+ }).on("mousemove", (e) => positionTooltip(e)).on("mouseout", () => {
792
+ scheduleHideTooltip();
793
+ });
794
+ node.on("click", (e, d) => {
795
+ e.stopPropagation();
796
+ pinnedNode = d;
797
+ highlightNode(d);
798
+ showDetail(d);
799
+ });
800
+ svg.on("click", () => {
801
+ resetGraphHighlight();
802
+ tooltip.style.display = "none";
803
+ tooltipLocked = false;
804
+ w.closeDetail();
805
+ });
806
+ function showDetail(d) {
807
+ const p = document.getElementById("detail");
808
+ document.getElementById("d-name").textContent = d.id;
809
+ document.getElementById("d-meta").innerHTML = i("detail.dir") + ": " + esc(d.dir) + "<br>" + i("detail.dependencies") + ": " + d.deps + " \xB7 " + i("detail.dependents") + ": " + d.dependents;
810
+ const deptL = document.getElementById("d-dependents"), depsL = document.getElementById("d-deps");
811
+ deptL.innerHTML = (d.dependentsList || []).map((x) => '<li data-focus="' + esc(x) + '">\u2190 ' + esc(x) + "</li>").join("") || '<li style="color:var(--text-muted)">' + i("detail.none") + "</li>";
812
+ depsL.innerHTML = (d.dependencies || []).map((x) => '<li data-focus="' + esc(x) + '">\u2192 ' + esc(x) + "</li>").join("") || '<li style="color:var(--text-muted)">' + i("detail.none") + "</li>";
813
+ p.classList.add("open");
814
+ }
815
+ document.getElementById("d-dependents").addEventListener("click", function(e) {
816
+ const li = e.target.closest("li[data-focus]");
817
+ if (li) w.focusNode(li.dataset.focus);
818
+ });
819
+ document.getElementById("d-deps").addEventListener("click", function(e) {
820
+ const li = e.target.closest("li[data-focus]");
821
+ if (li) w.focusNode(li.dataset.focus);
822
+ });
823
+ w.closeDetail = () => document.getElementById("detail").classList.remove("open");
824
+ w.focusNode = (id) => {
825
+ const n = DATA.nodes.find((x) => x.id === id);
826
+ if (!n) return;
827
+ showDetail(n);
828
+ svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity.translate(W / 2 - n.x * 1.5, H / 2 - n.y * 1.5).scale(1.5));
829
+ };
830
+ function dragStart(e, d) {
831
+ if (!e.active) simulation.alphaTarget(0.3).restart();
832
+ d.fx = d.x;
833
+ d.fy = d.y;
834
+ }
835
+ function dragging(e, d) {
836
+ d.fx = e.x;
837
+ d.fy = e.y;
838
+ }
839
+ function dragEnd(e, _d) {
840
+ if (!e.active) simulation.alphaTarget(0);
841
+ }
842
+ var searchInput = document.getElementById("search");
843
+ document.addEventListener("keydown", (e) => {
844
+ if (e.key === "/" && document.activeElement !== searchInput) {
845
+ e.preventDefault();
846
+ searchInput.focus();
847
+ }
848
+ if (e.key === "Escape") {
849
+ searchInput.value = "";
850
+ searchInput.blur();
851
+ resetGraphHighlight();
852
+ }
853
+ });
854
+ searchInput.addEventListener("input", (e) => {
855
+ const q = e.target.value.toLowerCase();
856
+ if (!q) {
857
+ resetGraphHighlight();
858
+ return;
859
+ }
860
+ node.select("circle").attr("opacity", (d) => d.id.toLowerCase().includes(q) ? 1 : 0.06);
861
+ node.select("text").attr("opacity", (d) => d.id.toLowerCase().includes(q) ? 1 : 0.04);
862
+ link.attr("opacity", 0.03);
863
+ });
864
+ if (!LAYERS) {
865
+ const filterRowEl = document.getElementById("filter-layer-row");
866
+ DATA.dirs.forEach((dir) => {
867
+ const pill = document.createElement("div");
868
+ pill.className = "filter-pill active";
869
+ pill.innerHTML = '<div class="pill-dot" style="background:' + dirColor(dir) + '"></div>' + esc(dir || ".") + ' <span class="pill-count">' + dirCounts[dir] + "</span>";
870
+ pill.onclick = () => {
871
+ if (activeDirs.has(dir)) {
872
+ activeDirs.delete(dir);
873
+ pill.classList.remove("active");
874
+ } else {
875
+ activeDirs.add(dir);
876
+ pill.classList.add("active");
877
+ }
878
+ applyFilter();
879
+ };
880
+ pill.onmouseenter = () => {
881
+ if (pinnedNode) return;
882
+ node.select("circle").transition().duration(120).attr("opacity", (d) => d.dir === dir ? 1 : 0.1);
883
+ node.select("text").transition().duration(120).attr("opacity", (d) => d.dir === dir ? 1 : 0.05);
884
+ };
885
+ pill.onmouseleave = () => {
886
+ if (pinnedNode) return;
887
+ node.select("circle").transition().duration(150).attr("opacity", 1);
888
+ node.select("text").transition().duration(150).attr("opacity", (d) => d.dependents >= 1 || d.deps >= 3 ? 1 : 0.5);
889
+ };
890
+ filterRowEl.appendChild(pill);
891
+ });
892
+ }
893
+ function applyFilter() {
894
+ if (LAYERS) {
895
+ if (typeof applyLayerFilter === "function") {
896
+ applyLayerFilter();
897
+ return;
898
+ }
899
+ }
900
+ node.attr("display", (d) => activeDirs.has(d.dir) ? null : "none");
901
+ link.attr("display", (l) => {
902
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
903
+ const sD = DATA.nodes.find((n) => n.id === s)?.dir, tD = DATA.nodes.find((n) => n.id === t)?.dir;
904
+ return activeDirs.has(sD) && activeDirs.has(tD) ? null : "none";
905
+ });
906
+ }
907
+ var impactMode = false;
908
+ var impactBadge = document.getElementById("impact-badge");
909
+ w.toggleImpactMode = () => {
910
+ impactMode = !impactMode;
911
+ document.getElementById("impact-btn").classList.toggle("active", impactMode);
912
+ if (!impactMode) {
913
+ impactBadge.style.display = "none";
914
+ resetGraphHighlight();
915
+ }
916
+ };
917
+ function getTransitiveDependents(startId) {
918
+ const result = /* @__PURE__ */ new Set();
919
+ const queue = [startId];
920
+ const revMap = {};
921
+ DATA.links.forEach((l) => {
922
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
923
+ if (!revMap[t]) revMap[t] = [];
924
+ revMap[t].push(s);
925
+ });
926
+ while (queue.length) {
927
+ const id = queue.shift();
928
+ if (result.has(id)) continue;
929
+ result.add(id);
930
+ (revMap[id] || []).forEach((x) => queue.push(x));
931
+ }
932
+ return result;
933
+ }
934
+ node.on("click", (e, d) => {
935
+ if (!impactMode) {
936
+ e.stopPropagation();
937
+ pinnedNode = d;
938
+ highlightNode(d);
939
+ showDetail(d);
940
+ return;
941
+ }
942
+ e.stopPropagation();
943
+ const affected = getTransitiveDependents(d.id);
944
+ node.select("circle").transition().duration(200).attr("opacity", (n) => affected.has(n.id) ? 1 : 0.06).attr("stroke", (n) => affected.has(n.id) && n.id !== d.id ? "var(--red)" : n.deps >= 5 ? "var(--yellow)" : nodeColor(n)).attr("stroke-width", (n) => affected.has(n.id) ? 3 : 1.5);
945
+ node.select("text").transition().duration(200).attr("opacity", (n) => affected.has(n.id) ? 1 : 0.04);
946
+ link.transition().duration(200).attr("opacity", (l) => {
947
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
948
+ return affected.has(s) && affected.has(t) ? 0.8 : 0.03;
949
+ }).attr("stroke", (l) => {
950
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
951
+ return affected.has(s) && affected.has(t) ? "var(--red)" : l.type === "type-only" ? "#1f3d5c" : "#30363d";
952
+ });
953
+ impactBadge.textContent = d.id.split("/").pop() + " \u2192 " + (affected.size - 1) + " " + i("impact.transitive");
954
+ impactBadge.style.display = "block";
955
+ });
956
+ window.addEventListener("resize", () => {
957
+ const ww = window.innerWidth, hh = window.innerHeight - 44;
958
+ svg.attr("width", ww).attr("height", hh);
959
+ });
960
+ function buildHierarchy() {
961
+ const hSvg = d3.select("#hier-svg");
962
+ const hG = hSvg.append("g");
963
+ const hZoom = d3.zoom().scaleExtent([0.1, 4]).on("zoom", (e) => hG.attr("transform", e.transform));
964
+ hSvg.call(hZoom);
965
+ const nodeMap = {};
966
+ DATA.nodes.forEach((n) => nodeMap[n.id] = n);
967
+ const importsMap = {};
968
+ DATA.links.forEach((l) => {
969
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
970
+ if (!importsMap[s]) importsMap[s] = [];
971
+ importsMap[s].push(t);
972
+ });
973
+ const entryPoints = DATA.nodes.filter((n) => n.dependents === 0).map((n) => n.id);
974
+ const layers = {};
975
+ const visited = /* @__PURE__ */ new Set();
976
+ const queue = entryPoints.map((id) => ({ id, layer: 0 }));
977
+ DATA.nodes.forEach((n) => {
978
+ if (n.isOrphan) layers[n.id] = 0;
979
+ });
980
+ while (queue.length > 0) {
981
+ const { id, layer } = queue.shift();
982
+ if (visited.has(id) && (layers[id] ?? -1) >= layer) continue;
983
+ layers[id] = Math.max(layers[id] ?? 0, layer);
984
+ visited.add(id);
985
+ (importsMap[id] || []).forEach((t) => queue.push({ id: t, layer: layer + 1 }));
986
+ }
987
+ DATA.nodes.forEach((n) => {
988
+ if (!(n.id in layers)) layers[n.id] = 0;
989
+ });
990
+ const maxLayer = Math.max(0, ...Object.values(layers));
991
+ const layerGroups = {};
992
+ for (let li = 0; li <= maxLayer; li++) layerGroups[li] = [];
993
+ Object.entries(layers).forEach(([id, l]) => layerGroups[l].push(id));
994
+ Object.values(layerGroups).forEach((arr) => arr.sort((a, b) => (nodeMap[a]?.dir || "").localeCompare(nodeMap[b]?.dir || "") || a.localeCompare(b)));
995
+ const boxW = 200, boxH = 30, gapX = 24, gapY = 70, padY = 60, padX = 40;
996
+ const positions = {};
997
+ let maxRowWidth = 0;
998
+ for (let layer = 0; layer <= maxLayer; layer++) {
999
+ const items = layerGroups[layer];
1000
+ maxRowWidth = Math.max(maxRowWidth, items.length * (boxW + gapX) - gapX);
1001
+ }
1002
+ for (let layer = 0; layer <= maxLayer; layer++) {
1003
+ const items = layerGroups[layer], rowWidth = items.length * (boxW + gapX) - gapX, startX = padX + (maxRowWidth - rowWidth) / 2;
1004
+ items.forEach((id, idx) => {
1005
+ positions[id] = { x: startX + idx * (boxW + gapX), y: padY + layer * (boxH + gapY) };
1006
+ });
1007
+ }
1008
+ const totalW = maxRowWidth + padX * 2, totalH = padY * 2 + (maxLayer + 1) * (boxH + gapY);
1009
+ hSvg.attr("width", Math.max(totalW, W)).attr("height", Math.max(totalH, H));
1010
+ const linkG = hG.append("g");
1011
+ DATA.links.forEach((l) => {
1012
+ const sId = l.source.id ?? l.source, tId = l.target.id ?? l.target;
1013
+ const s = positions[sId], t = positions[tId];
1014
+ if (!s || !t) return;
1015
+ const x1 = s.x + boxW / 2, y1 = s.y + boxH, x2 = t.x + boxW / 2, y2 = t.y, midY = (y1 + y2) / 2;
1016
+ linkG.append("path").attr("class", "hier-link").attr("d", `M${x1},${y1} C${x1},${midY} ${x2},${midY} ${x2},${y2}`).attr("stroke", l.type === "type-only" ? "#1f3d5c" : "var(--border)").attr("stroke-dasharray", l.type === "type-only" ? "4,3" : null).attr("data-source", sId).attr("data-target", tId);
1017
+ });
1018
+ hSvg.append("defs").append("marker").attr("id", "harrow").attr("viewBox", "0 -3 6 6").attr("refX", 6).attr("refY", 0).attr("markerWidth", 6).attr("markerHeight", 6).attr("orient", "auto").append("path").attr("d", "M0,-3L6,0L0,3Z").attr("fill", "var(--border)");
1019
+ linkG.selectAll("path").attr("marker-end", "url(#harrow)");
1020
+ for (let layer = 0; layer <= maxLayer; layer++) {
1021
+ if (!layerGroups[layer].length) continue;
1022
+ hG.append("text").attr("class", "hier-layer-label").attr("font-size", 11).attr("data-depth-idx", layer).attr("x", 12).attr("y", padY + layer * (boxH + gapY) + boxH / 2 + 4).text("L" + layer);
1023
+ }
1024
+ const nodeG = hG.append("g");
1025
+ DATA.nodes.forEach((n) => {
1026
+ const pos = positions[n.id];
1027
+ if (!pos) return;
1028
+ const gn = nodeG.append("g").attr("class", "hier-node").attr("transform", `translate(${pos.x},${pos.y})`);
1029
+ gn.append("rect").attr("width", boxW).attr("height", boxH).attr("fill", "var(--bg-card)").attr("stroke", nodeColor(n)).attr("stroke-width", circularSet.has(n.id) ? 2 : 1.5);
1030
+ gn.append("text").attr("x", 8).attr("y", boxH / 2 + 4).attr("font-size", 11).text(fileName(n.id).length > 24 ? fileName(n.id).slice(0, 22) + "\u2026" : fileName(n.id));
1031
+ gn.append("text").attr("x", boxW - 8).attr("y", boxH / 2 + 4).attr("text-anchor", "end").attr("font-size", 10).attr("fill", "var(--text-muted)").text(n.dependents > 0 ? "\u2191" + n.dependents : "");
1032
+ gn.append("text").attr("x", 8).attr("y", -4).attr("font-size", 9).attr("fill", dirColor(n.dir)).attr("opacity", 0.7).text(n.dir);
1033
+ gn.node().__data_id = n.id;
1034
+ gn.on("mouseover", (e) => {
1035
+ showTooltip(e, n);
1036
+ if (!hierPinned) hierHighlight(n.id);
1037
+ }).on("mousemove", (e) => positionTooltip(e)).on("mouseout", () => {
1038
+ scheduleHideTooltip();
1039
+ if (!hierPinned) hierResetHighlight();
1040
+ }).on("click", (e) => {
1041
+ e.stopPropagation();
1042
+ hierPinned = n.id;
1043
+ hierHighlight(n.id);
1044
+ showHierDetail(n);
1045
+ });
1046
+ });
1047
+ let hierPinned = null;
1048
+ function hierHighlight(nId) {
1049
+ linkG.selectAll("path").attr("stroke", function() {
1050
+ const s = this.getAttribute("data-source"), t = this.getAttribute("data-target");
1051
+ if (s === nId) return "#58a6ff";
1052
+ if (t === nId) return "#3fb950";
1053
+ return this.getAttribute("stroke-dasharray") ? "#1f3d5c" : "var(--border)";
1054
+ }).attr("stroke-width", function() {
1055
+ const s = this.getAttribute("data-source"), t = this.getAttribute("data-target");
1056
+ return s === nId || t === nId ? 2.5 : 1;
1057
+ }).attr("opacity", function() {
1058
+ const s = this.getAttribute("data-source"), t = this.getAttribute("data-target");
1059
+ return s === nId || t === nId ? 1 : 0.15;
1060
+ });
1061
+ nodeG.selectAll(".hier-node").attr("opacity", function() {
1062
+ const id = this.__data_id;
1063
+ if (id === nId) return 1;
1064
+ const connected = DATA.links.some((l) => {
1065
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
1066
+ return s === nId && t === id || t === nId && s === id;
1067
+ });
1068
+ return connected ? 1 : 0.3;
1069
+ });
1070
+ }
1071
+ function hierResetHighlight() {
1072
+ hierPinned = null;
1073
+ linkG.selectAll("path").attr("stroke", function() {
1074
+ return this.getAttribute("stroke-dasharray") ? "#1f3d5c" : "var(--border)";
1075
+ }).attr("stroke-width", 1).attr("opacity", 1);
1076
+ nodeG.selectAll(".hier-node").attr("opacity", 1);
1077
+ }
1078
+ function showHierDetail(n) {
1079
+ const p = document.getElementById("hier-detail");
1080
+ document.getElementById("hd-name").textContent = n.id;
1081
+ document.getElementById("hd-meta").innerHTML = i("detail.dir") + ": " + esc(n.dir) + "<br>" + i("detail.dependencies") + ": " + n.deps + " \xB7 " + i("detail.dependents") + ": " + n.dependents;
1082
+ document.getElementById("hd-dependents").innerHTML = (n.dependentsList || []).map((x) => "<li>\u2190 " + esc(x) + "</li>").join("") || '<li style="color:var(--text-muted)">' + i("detail.none") + "</li>";
1083
+ document.getElementById("hd-deps").innerHTML = (n.dependencies || []).map((x) => "<li>\u2192 " + esc(x) + "</li>").join("") || '<li style="color:var(--text-muted)">' + i("detail.none") + "</li>";
1084
+ p.classList.add("open");
1085
+ }
1086
+ w.closeHierDetail = () => {
1087
+ document.getElementById("hier-detail").classList.remove("open");
1088
+ hierResetHighlight();
1089
+ tooltip.style.display = "none";
1090
+ tooltipLocked = false;
1091
+ };
1092
+ hSvg.on("click", () => {
1093
+ w.closeHierDetail();
1094
+ });
1095
+ const hFilterRow = document.getElementById("hier-filter-row");
1096
+ const hFilterBar = document.getElementById("hier-filter-bar");
1097
+ if (hFilterBar) hFilterBar.style.display = "";
1098
+ const hActiveLayers = /* @__PURE__ */ new Set();
1099
+ function hierRelayoutInner() {
1100
+ function isVisible(nId) {
1101
+ const nd = nodeMap[nId];
1102
+ if (!nd) return false;
1103
+ if (LAYERS && nd.layer && hActiveLayers.size > 0 && !hActiveLayers.has(nd.layer)) return false;
1104
+ return true;
1105
+ }
1106
+ const visibleDepths = [];
1107
+ const visLayerGroups = {};
1108
+ for (let depth = 0; depth <= maxLayer; depth++) {
1109
+ const visItems = layerGroups[depth].filter(function(id) {
1110
+ return isVisible(id);
1111
+ });
1112
+ if (visItems.length > 0) {
1113
+ visLayerGroups[depth] = visItems;
1114
+ visibleDepths.push(depth);
1115
+ }
1116
+ }
1117
+ const newPositions = {};
1118
+ let newMaxRowWidth = 0;
1119
+ visibleDepths.forEach(function(depth) {
1120
+ newMaxRowWidth = Math.max(newMaxRowWidth, visLayerGroups[depth].length * (boxW + gapX) - gapX);
1121
+ });
1122
+ visibleDepths.forEach(function(depth, yIdx) {
1123
+ const items = visLayerGroups[depth];
1124
+ const rowWidth = items.length * (boxW + gapX) - gapX;
1125
+ const startX = padX + (newMaxRowWidth - rowWidth) / 2;
1126
+ items.forEach(function(id, idx) {
1127
+ newPositions[id] = { x: startX + idx * (boxW + gapX), y: padY + yIdx * (boxH + gapY) };
1128
+ });
1129
+ });
1130
+ const newTotalW = (newMaxRowWidth || 0) + padX * 2;
1131
+ const newTotalH = padY * 2 + Math.max(1, visibleDepths.length) * (boxH + gapY);
1132
+ hSvg.attr("width", Math.max(newTotalW, W)).attr("height", Math.max(newTotalH, H));
1133
+ nodeG.selectAll(".hier-node").each(function() {
1134
+ const nId = this.__data_id;
1135
+ const el = d3.select(this);
1136
+ if (!isVisible(nId) || !newPositions[nId]) {
1137
+ el.attr("display", "none");
1138
+ } else {
1139
+ el.attr("display", null).transition().duration(300).attr("transform", "translate(" + newPositions[nId].x + "," + newPositions[nId].y + ")");
1140
+ }
1141
+ });
1142
+ linkG.selectAll("path").each(function() {
1143
+ const sId = this.getAttribute("data-source");
1144
+ const tId = this.getAttribute("data-target");
1145
+ const el = d3.select(this);
1146
+ if (!isVisible(sId) || !isVisible(tId) || !newPositions[sId] || !newPositions[tId]) {
1147
+ el.attr("display", "none");
1148
+ } else {
1149
+ const s = newPositions[sId], t = newPositions[tId];
1150
+ const x1 = s.x + boxW / 2, y1 = s.y + boxH;
1151
+ const x2 = t.x + boxW / 2, y2 = t.y;
1152
+ const midY = (y1 + y2) / 2;
1153
+ el.attr("display", null).transition().duration(300).attr("d", "M" + x1 + "," + y1 + " C" + x1 + "," + midY + " " + x2 + "," + midY + " " + x2 + "," + y2);
1154
+ }
1155
+ });
1156
+ hG.selectAll(".hier-layer-label").each(function() {
1157
+ const depthIdx = +this.getAttribute("data-depth-idx");
1158
+ const el = d3.select(this);
1159
+ const yIdx = visibleDepths.indexOf(depthIdx);
1160
+ if (yIdx === -1) {
1161
+ el.attr("display", "none");
1162
+ } else {
1163
+ el.attr("display", null).transition().duration(300).attr("y", padY + yIdx * (boxH + gapY) + boxH / 2 + 4);
1164
+ }
1165
+ });
1166
+ if (hierPinned && !isVisible(hierPinned)) {
1167
+ w.closeHierDetail();
1168
+ }
1169
+ }
1170
+ function hierSyncFromTabInner() {
1171
+ if (!LAYERS) return;
1172
+ hActiveLayers.clear();
1173
+ activeLayers.forEach(function(name) {
1174
+ hActiveLayers.add(name);
1175
+ });
1176
+ hFilterRow.querySelectorAll(".layer-pill").forEach(function(p) {
1177
+ const ln = p.dataset.layer;
1178
+ if (ln === "all") {
1179
+ p.classList.toggle("active", hActiveLayers.size === 0);
1180
+ } else {
1181
+ p.classList.toggle("active", hActiveLayers.has(ln));
1182
+ }
1183
+ });
1184
+ }
1185
+ if (LAYERS) {
1186
+ const allPill = document.createElement("div");
1187
+ allPill.className = "layer-pill active";
1188
+ allPill.style.fontWeight = "400";
1189
+ allPill.textContent = "All";
1190
+ allPill.dataset.layer = "all";
1191
+ allPill.onclick = () => {
1192
+ hActiveLayers.clear();
1193
+ hFilterRow.querySelectorAll(".layer-pill").forEach((p) => p.classList.remove("active"));
1194
+ allPill.classList.add("active");
1195
+ hierRelayoutInner();
1196
+ };
1197
+ hFilterRow.appendChild(allPill);
1198
+ LAYERS.forEach((layer) => {
1199
+ const pill = document.createElement("div");
1200
+ pill.className = "layer-pill";
1201
+ pill.dataset.layer = layer.name;
1202
+ const count = DATA.nodes.filter((n) => n.layer === layer.name).length;
1203
+ pill.innerHTML = '<div class="lp-dot" style="background:' + esc(layer.color) + '"></div>' + esc(layer.name) + ' <span class="lp-count">' + count + "</span>";
1204
+ pill.onclick = (e) => {
1205
+ if (e.shiftKey) {
1206
+ hActiveLayers.clear();
1207
+ hActiveLayers.add(layer.name);
1208
+ } else {
1209
+ if (hActiveLayers.has(layer.name)) hActiveLayers.delete(layer.name);
1210
+ else hActiveLayers.add(layer.name);
1211
+ }
1212
+ hFilterRow.querySelectorAll(".layer-pill").forEach(function(p) {
1213
+ const ln = p.dataset.layer;
1214
+ if (ln === "all") p.classList.toggle("active", hActiveLayers.size === 0);
1215
+ else p.classList.toggle("active", hActiveLayers.has(ln));
1216
+ });
1217
+ hierRelayoutInner();
1218
+ };
1219
+ hFilterRow.appendChild(pill);
1220
+ });
1221
+ } else {
1222
+ const hActiveDirs = new Set(DATA.dirs);
1223
+ DATA.dirs.forEach((dir) => {
1224
+ const pill = document.createElement("div");
1225
+ pill.className = "filter-pill active";
1226
+ pill.innerHTML = '<div class="pill-dot" style="background:' + dirColor(dir) + '"></div>' + esc(dir || ".") + ' <span class="pill-count">' + (dirCounts[dir] || 0) + "</span>";
1227
+ pill.onclick = () => {
1228
+ if (hActiveDirs.has(dir)) {
1229
+ hActiveDirs.delete(dir);
1230
+ pill.classList.remove("active");
1231
+ } else {
1232
+ hActiveDirs.add(dir);
1233
+ pill.classList.add("active");
1234
+ }
1235
+ nodeG.selectAll(".hier-node").attr("opacity", function() {
1236
+ const nId = this.__data_id;
1237
+ return hActiveDirs.has(nodeMap[nId]?.dir) ? 1 : 0.1;
1238
+ });
1239
+ };
1240
+ hFilterRow.appendChild(pill);
1241
+ });
1242
+ }
1243
+ hierRelayout = hierRelayoutInner;
1244
+ hierSyncFromTab = hierSyncFromTabInner;
1245
+ hSvg.call(hZoom.transform, d3.zoomIdentity.translate(
1246
+ Math.max(0, (W - totalW) / 2),
1247
+ 20
1248
+ ).scale(Math.min(1, W / (totalW + 40), H / (totalH + 40))));
1249
+ if (activeLayers.size > 0) {
1250
+ hierSyncFromTabInner();
1251
+ hierRelayoutInner();
1252
+ }
1253
+ }
1254
+ var DIFF = w.__ARCH.diff;
1255
+ var buildDiffView = null;
1256
+ if (DIFF) {
1257
+ let isDiffNode = function(id) {
1258
+ return addedSet.has(id) || removedSet.has(id) || modifiedSet.has(id) || affectedSet.has(id);
1259
+ }, diffStatus = function(id) {
1260
+ if (addedSet.has(id)) return "Added";
1261
+ if (removedSet.has(id)) return "Removed";
1262
+ if (modifiedSet.has(id)) return "Modified";
1263
+ if (affectedSet.has(id)) return "Affected";
1264
+ return "Unchanged";
1265
+ }, diffStatusColor = function(id) {
1266
+ if (addedSet.has(id)) return "var(--green)";
1267
+ if (removedSet.has(id)) return "var(--red)";
1268
+ if (modifiedSet.has(id)) return "var(--yellow)";
1269
+ if (affectedSet.has(id)) return "var(--accent)";
1270
+ return "var(--text-muted)";
1271
+ }, getImpactChain = function(startId) {
1272
+ const result = /* @__PURE__ */ new Set();
1273
+ const queue = [startId];
1274
+ while (queue.length) {
1275
+ const id = queue.shift();
1276
+ if (result.has(id)) continue;
1277
+ result.add(id);
1278
+ (diffRevMap[id] || []).forEach(function(x) {
1279
+ queue.push(x);
1280
+ });
1281
+ }
1282
+ return result;
1283
+ }, applyDiffFilter = function() {
1284
+ dNode.attr("display", function(d) {
1285
+ if (!diffFocusMode) return null;
1286
+ return isDiffNode(d.id) ? null : "none";
1287
+ });
1288
+ dNode.select("circle").attr("opacity", function(d) {
1289
+ if (diffFocusMode) return isDiffNode(d.id) ? 1 : 0;
1290
+ return isDiffNode(d.id) ? 1 : 0.12;
1291
+ });
1292
+ dNode.select("text").attr("opacity", function(d) {
1293
+ if (diffFocusMode) return isDiffNode(d.id) ? 1 : 0;
1294
+ return isDiffNode(d.id) ? 1 : 0.08;
1295
+ });
1296
+ dLink.attr("display", function(l) {
1297
+ if (!diffFocusMode) return null;
1298
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
1299
+ return isDiffNode(s) && isDiffNode(t) ? null : "none";
1300
+ });
1301
+ dLink.attr("opacity", function(l) {
1302
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
1303
+ if (isDiffNode(s) && isDiffNode(t)) return 0.6;
1304
+ if (isDiffNode(s) || isDiffNode(t)) return 0.15;
1305
+ return diffFocusMode ? 0 : 0.05;
1306
+ });
1307
+ dLink.attr("stroke", function(l) {
1308
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
1309
+ if (isDiffNode(s) && isDiffNode(t)) return diffStatusColor(s);
1310
+ return "#30363d";
1311
+ });
1312
+ dNode.select("circle").attr("stroke-width", function(d) {
1313
+ return isDiffNode(d.id) ? 3 : 1;
1314
+ });
1315
+ }, resetDiffHighlight = function() {
1316
+ applyDiffFilter();
1317
+ }, highlightDiffImpact = function(d) {
1318
+ const chain = getImpactChain(d.id);
1319
+ dNode.select("circle").transition().duration(200).attr("opacity", function(n) {
1320
+ return chain.has(n.id) ? 1 : 0.04;
1321
+ }).attr("stroke-width", function(n) {
1322
+ return chain.has(n.id) && n.id !== d.id ? 3 : isDiffNode(n.id) ? 3 : 1;
1323
+ }).attr("stroke", function(n) {
1324
+ return chain.has(n.id) && n.id !== d.id ? "var(--red)" : diffStatusColor(n.id);
1325
+ });
1326
+ dNode.select("text").transition().duration(200).attr("opacity", function(n) {
1327
+ return chain.has(n.id) ? 1 : 0.03;
1328
+ });
1329
+ dLink.transition().duration(200).attr("opacity", function(l) {
1330
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
1331
+ return chain.has(s) && chain.has(t) ? 0.8 : 0.03;
1332
+ }).attr("stroke", function(l) {
1333
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
1334
+ return chain.has(s) && chain.has(t) ? "var(--red)" : "#30363d";
1335
+ });
1336
+ return chain;
1337
+ }, showDiffDetail = function(d) {
1338
+ const panel = document.getElementById("diff-detail");
1339
+ document.getElementById("dd-name").textContent = d.id;
1340
+ const statusEl = document.getElementById("dd-status");
1341
+ statusEl.textContent = diffStatus(d.id);
1342
+ statusEl.style.color = diffStatusColor(d.id);
1343
+ document.getElementById("dd-meta").innerHTML = i("detail.dir") + ": " + esc(d.dir) + "<br>" + i("detail.dependencies") + ": " + d.deps + " \xB7 " + i("detail.dependents") + ": " + d.dependents;
1344
+ const chain = getImpactChain(d.id);
1345
+ chain.delete(d.id);
1346
+ const affectedList = document.getElementById("dd-affected");
1347
+ if (chain.size > 0) {
1348
+ affectedList.innerHTML = Array.from(chain).map(function(id) {
1349
+ return '<li style="color:' + diffStatusColor(id) + '">\u2190 ' + esc(id) + ' <span style="font-size:10px;color:var(--text-muted)">(' + diffStatus(id) + ")</span></li>";
1350
+ }).join("");
1351
+ } else {
1352
+ affectedList.innerHTML = '<li style="color:var(--text-muted)">' + i("diff.noImpact") + "</li>";
1353
+ }
1354
+ const depsList = document.getElementById("dd-deps");
1355
+ depsList.innerHTML = (d.dependencies || []).map(function(x) {
1356
+ return '<li style="color:' + diffStatusColor(x) + '">\u2192 ' + esc(x) + "</li>";
1357
+ }).join("") || '<li style="color:var(--text-muted)">' + i("detail.none") + "</li>";
1358
+ panel.style.display = "block";
1359
+ };
1360
+ isDiffNode2 = isDiffNode, diffStatus2 = diffStatus, diffStatusColor2 = diffStatusColor, getImpactChain2 = getImpactChain, applyDiffFilter2 = applyDiffFilter, resetDiffHighlight2 = resetDiffHighlight, highlightDiffImpact2 = highlightDiffImpact, showDiffDetail2 = showDiffDetail;
1361
+ document.getElementById("diff-tab").style.display = "";
1362
+ const addedSet = new Set(DIFF.added || []);
1363
+ const removedSet = new Set(DIFF.removed || []);
1364
+ const modifiedSet = new Set(DIFF.modified || []);
1365
+ const affectedSet = new Set((DIFF.affectedDependents || []).map((a) => a.file));
1366
+ document.getElementById("diff-added-count").textContent = String(addedSet.size);
1367
+ document.getElementById("diff-removed-count").textContent = String(removedSet.size);
1368
+ document.getElementById("diff-modified-count").textContent = String(modifiedSet.size);
1369
+ document.getElementById("diff-affected-count").textContent = String(affectedSet.size);
1370
+ const diffRevMap = {};
1371
+ DATA.links.forEach(function(l) {
1372
+ const s = l.source.id ?? l.source, t = l.target.id ?? l.target;
1373
+ if (!diffRevMap[t]) diffRevMap[t] = [];
1374
+ diffRevMap[t].push(s);
1375
+ });
1376
+ let diffFocusMode = false;
1377
+ let dNode, dLink, dSim, simNodes, simLinks;
1378
+ w.toggleDiffFocus = function() {
1379
+ diffFocusMode = !diffFocusMode;
1380
+ const btn = document.getElementById("diff-focus-btn");
1381
+ btn.classList.toggle("active", diffFocusMode);
1382
+ btn.textContent = diffFocusMode ? i("diff.showAll") : i("diff.focusChanges");
1383
+ if (!diffBuilt) return;
1384
+ applyDiffFilter();
1385
+ };
1386
+ w.closeDiffDetail = function() {
1387
+ document.getElementById("diff-detail").style.display = "none";
1388
+ if (diffBuilt) resetDiffHighlight();
1389
+ };
1390
+ buildDiffView = function() {
1391
+ const dSvg = d3.select("#diff-svg").attr("width", W).attr("height", H);
1392
+ const dG = dSvg.append("g");
1393
+ const dZoom = d3.zoom().scaleExtent([0.05, 10]).on("zoom", (e) => dG.attr("transform", e.transform));
1394
+ dSvg.call(dZoom);
1395
+ function diffColor(d) {
1396
+ if (addedSet.has(d.id)) return "var(--green)";
1397
+ if (removedSet.has(d.id)) return "var(--red)";
1398
+ if (modifiedSet.has(d.id)) return "var(--yellow)";
1399
+ if (affectedSet.has(d.id)) return "var(--accent)";
1400
+ return "#30363d";
1401
+ }
1402
+ const dDefs = dSvg.append("defs");
1403
+ dDefs.append("marker").attr("id", "darrow").attr("viewBox", "0 -4 8 8").attr("refX", 8).attr("refY", 0).attr("markerWidth", 7).attr("markerHeight", 7).attr("orient", "auto").append("path").attr("d", "M0,-3.5L8,0L0,3.5Z").attr("fill", "#30363d");
1404
+ [["var(--green)", "darrow-g"], ["var(--red)", "darrow-r"], ["var(--yellow)", "darrow-y"], ["var(--accent)", "darrow-a"]].forEach(function(pair) {
1405
+ dDefs.append("marker").attr("id", pair[1]).attr("viewBox", "0 -4 8 8").attr("refX", 8).attr("refY", 0).attr("markerWidth", 7).attr("markerHeight", 7).attr("orient", "auto").append("path").attr("d", "M0,-3.5L8,0L0,3.5Z").attr("fill", pair[0]);
1406
+ });
1407
+ simNodes = DATA.nodes.map((d) => ({ ...d, x: void 0, y: void 0, vx: void 0, vy: void 0 }));
1408
+ simLinks = DATA.links.map((d) => ({ source: d.source.id ?? d.source, target: d.target.id ?? d.target, type: d.type }));
1409
+ dLink = dG.append("g").selectAll("line").data(simLinks).join("line").attr("stroke", "#30363d").attr("stroke-width", 1).attr("marker-end", "url(#darrow)").attr("opacity", 0.05);
1410
+ dNode = dG.append("g").selectAll("g").data(simNodes).join("g").attr("cursor", "pointer");
1411
+ dNode.append("circle").attr("r", (d) => nodeRadius(d) * nodeScale).attr("fill", diffColor).attr("stroke", diffColor).attr("stroke-width", (d) => isDiffNode(d.id) ? 3 : 1).attr("opacity", (d) => isDiffNode(d.id) ? 1 : 0.12);
1412
+ dNode.append("text").text((d) => fileName(d.id).replace(/\.tsx?$/, "")).attr("dx", (d) => nodeRadius(d) * nodeScale + 4).attr("dy", 3.5).attr("font-size", 11).attr("fill", (d) => isDiffNode(d.id) ? "var(--text)" : "var(--text-muted)").attr("opacity", (d) => isDiffNode(d.id) ? 1 : 0.08).attr("pointer-events", "none");
1413
+ dSim = d3.forceSimulation(simNodes).force("link", d3.forceLink(simLinks).id((d) => d.id).distance(70).strength(0.25)).force("charge", d3.forceManyBody().strength(-150).distanceMax(500)).force("center", d3.forceCenter(0, 0)).force("collision", d3.forceCollide().radius((d) => nodeRadius(d) * nodeScale + 4));
1414
+ let dHullGroup = null;
1415
+ if (LAYERS && LAYERS.length > 0) {
1416
+ const dLayerCenters = {};
1417
+ const dLayerCount = LAYERS.length;
1418
+ const dBaseRadius = Math.max(60, Math.min(W, H) * 0.04 * Math.sqrt(dLayerCount));
1419
+ LAYERS.forEach(function(l, idx) {
1420
+ const angle = 2 * Math.PI * idx / dLayerCount - Math.PI / 2;
1421
+ dLayerCenters[l.name] = { x: Math.cos(angle) * dBaseRadius, y: Math.sin(angle) * dBaseRadius };
1422
+ });
1423
+ dSim.force("center", null);
1424
+ dSim.force("layerX", d3.forceX(function(d) {
1425
+ return dLayerCenters[d.layer]?.x || 0;
1426
+ }).strength(function(d) {
1427
+ return d.layer ? 0.12 : 0.03;
1428
+ }));
1429
+ dSim.force("layerY", d3.forceY(function(d) {
1430
+ return dLayerCenters[d.layer]?.y || 0;
1431
+ }).strength(function(d) {
1432
+ return d.layer ? 0.12 : 0.03;
1433
+ }));
1434
+ dSim.force("link").strength(function(l) {
1435
+ const sL = l.source.layer ?? l.source, tL = l.target.layer ?? l.target;
1436
+ return sL === tL ? 0.4 : 0.1;
1437
+ });
1438
+ dSim.force("cluster", (function() {
1439
+ let ns;
1440
+ function f(alpha) {
1441
+ const centroids = {}, counts = {};
1442
+ ns.forEach(function(n) {
1443
+ if (!n.layer) return;
1444
+ if (!centroids[n.layer]) {
1445
+ centroids[n.layer] = { x: 0, y: 0 };
1446
+ counts[n.layer] = 0;
1447
+ }
1448
+ centroids[n.layer].x += n.x;
1449
+ centroids[n.layer].y += n.y;
1450
+ counts[n.layer]++;
1451
+ });
1452
+ Object.keys(centroids).forEach(function(k) {
1453
+ centroids[k].x /= counts[k];
1454
+ centroids[k].y /= counts[k];
1455
+ });
1456
+ ns.forEach(function(n) {
1457
+ if (!n.layer || !centroids[n.layer]) return;
1458
+ n.vx += (centroids[n.layer].x - n.x) * alpha * 0.2;
1459
+ n.vy += (centroids[n.layer].y - n.y) * alpha * 0.2;
1460
+ });
1461
+ }
1462
+ f.initialize = function(n) {
1463
+ ns = n;
1464
+ };
1465
+ return f;
1466
+ })());
1467
+ dHullGroup = dG.insert("g", ":first-child");
1468
+ }
1469
+ function updateDiffHulls() {
1470
+ if (!dHullGroup) return;
1471
+ dHullGroup.selectAll("*").remove();
1472
+ LAYERS.forEach(function(layer) {
1473
+ const layerNodes = simNodes.filter(function(n) {
1474
+ return n.layer === layer.name;
1475
+ });
1476
+ if (layerNodes.length === 0) return;
1477
+ if (diffFocusMode && !layerNodes.some(function(n) {
1478
+ return isDiffNode(n.id);
1479
+ })) return;
1480
+ const hasDiff = layerNodes.some(function(n) {
1481
+ return isDiffNode(n.id);
1482
+ });
1483
+ const points = [];
1484
+ layerNodes.forEach(function(n) {
1485
+ if (n.x == null || n.y == null) return;
1486
+ if (diffFocusMode && !isDiffNode(n.id)) return;
1487
+ const r = nodeRadius(n) * nodeScale + 30;
1488
+ for (let a = 0; a < Math.PI * 2; a += Math.PI / 4) {
1489
+ points.push([n.x + Math.cos(a) * r, n.y + Math.sin(a) * r]);
1490
+ }
1491
+ });
1492
+ const fillOp = hasDiff ? 0.15 : 0.06;
1493
+ const strokeOp = hasDiff ? 0.6 : 0.2;
1494
+ const sw = hasDiff ? 2.5 : 1;
1495
+ if (points.length < 6) {
1496
+ const cx = layerNodes.reduce(function(s, n) {
1497
+ return s + (n.x || 0);
1498
+ }, 0) / layerNodes.length;
1499
+ const cy = layerNodes.reduce(function(s, n) {
1500
+ return s + (n.y || 0);
1501
+ }, 0) / layerNodes.length;
1502
+ dHullGroup.append("circle").attr("cx", cx).attr("cy", cy).attr("r", 50).attr("fill", layer.color).attr("fill-opacity", fillOp).attr("stroke", layer.color).attr("stroke-opacity", strokeOp).attr("stroke-width", sw);
1503
+ } else {
1504
+ const hull = d3.polygonHull(points);
1505
+ if (hull) {
1506
+ dHullGroup.append("path").attr("d", "M" + hull.map(function(p) {
1507
+ return p.join(",");
1508
+ }).join("L") + "Z").attr("fill", layer.color).attr("fill-opacity", fillOp).attr("stroke", layer.color).attr("stroke-opacity", strokeOp).attr("stroke-width", sw).attr("stroke-dasharray", hasDiff ? null : "6,3");
1509
+ }
1510
+ }
1511
+ const visNodes = diffFocusMode ? layerNodes.filter(function(n) {
1512
+ return isDiffNode(n.id);
1513
+ }) : layerNodes;
1514
+ if (visNodes.length === 0) return;
1515
+ const lx = visNodes.reduce(function(s, n) {
1516
+ return s + (n.x || 0);
1517
+ }, 0) / visNodes.length;
1518
+ const ly = Math.min.apply(null, visNodes.map(function(n) {
1519
+ return n.y || 0;
1520
+ })) - 25;
1521
+ dHullGroup.append("text").attr("x", lx).attr("y", ly).attr("text-anchor", "middle").attr("fill", layer.color).attr("fill-opacity", hasDiff ? 0.9 : 0.4).attr("font-size", 12).attr("font-weight", 600).text(layer.name);
1522
+ });
1523
+ }
1524
+ let dTickCount = 0;
1525
+ dSim.on("tick", function() {
1526
+ dLink.each(function(d) {
1527
+ const dx = d.target.x - d.source.x, dy = d.target.y - d.source.y, dist = Math.sqrt(dx * dx + dy * dy) || 1;
1528
+ const rT = nodeRadius(d.target) * nodeScale, rS = nodeRadius(d.source) * nodeScale;
1529
+ d3.select(this).attr("x1", d.source.x + dx / dist * rS).attr("y1", d.source.y + dy / dist * rS).attr("x2", d.target.x - dx / dist * rT).attr("y2", d.target.y - dy / dist * rT);
1530
+ });
1531
+ dNode.attr("transform", function(d) {
1532
+ return "translate(" + d.x + "," + d.y + ")";
1533
+ });
1534
+ if (++dTickCount % 5 === 0) updateDiffHulls();
1535
+ });
1536
+ dNode.on("click", function(e, d) {
1537
+ e.stopPropagation();
1538
+ highlightDiffImpact(d);
1539
+ showDiffDetail(d);
1540
+ });
1541
+ dSvg.on("click", function() {
1542
+ w.closeDiffDetail();
1543
+ });
1544
+ dNode.on("mouseover", function(e, d) {
1545
+ showTooltip(e, d);
1546
+ }).on("mousemove", function(e) {
1547
+ positionTooltip(e);
1548
+ }).on("mouseout", function() {
1549
+ scheduleHideTooltip();
1550
+ });
1551
+ applyDiffFilter();
1552
+ let dAutoFitDone = false;
1553
+ dSim.on("end", function() {
1554
+ if (dAutoFitDone) return;
1555
+ dAutoFitDone = true;
1556
+ const b = dG.node().getBBox();
1557
+ if (!b.width) return;
1558
+ const s = Math.min(W / (b.width + 80), H / (b.height + 80)) * 0.9;
1559
+ dSvg.call(dZoom.transform, d3.zoomIdentity.translate(W / 2 - (b.x + b.width / 2) * s, H / 2 - (b.y + b.height / 2) * s).scale(s));
1560
+ });
1561
+ };
1562
+ }
1563
+ var isDiffNode2;
1564
+ var diffStatus2;
1565
+ var diffStatusColor2;
1566
+ var getImpactChain2;
1567
+ var applyDiffFilter2;
1568
+ var resetDiffHighlight2;
1569
+ var highlightDiffImpact2;
1570
+ var showDiffDetail2;
1571
+ applyI18n();
1572
+ })();