codebase-onboarder 0.6.0 → 1.0.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.
Files changed (526) hide show
  1. package/README.md +284 -53
  2. package/bin/build-if-source.js +23 -0
  3. package/bin/onboarder.js +0 -0
  4. package/cli/agent/command.d.ts +71 -0
  5. package/cli/agent/command.js +177 -0
  6. package/cli/agent/command.js.map +1 -0
  7. package/cli/agent/config.d.ts +31 -0
  8. package/cli/agent/config.js +98 -0
  9. package/cli/agent/config.js.map +1 -0
  10. package/cli/agent/contracts.d.ts +77 -0
  11. package/cli/agent/contracts.js +2 -0
  12. package/cli/agent/contracts.js.map +1 -0
  13. package/cli/agent/files.d.ts +19 -0
  14. package/cli/agent/files.js +109 -0
  15. package/cli/agent/files.js.map +1 -0
  16. package/cli/agent/gitSource.d.ts +18 -0
  17. package/cli/agent/gitSource.js +69 -0
  18. package/cli/agent/gitSource.js.map +1 -0
  19. package/cli/agent/github.d.ts +21 -0
  20. package/cli/agent/github.js +94 -0
  21. package/cli/agent/github.js.map +1 -0
  22. package/cli/agent/mcp.d.ts +15 -0
  23. package/cli/agent/mcp.js +78 -0
  24. package/cli/agent/mcp.js.map +1 -0
  25. package/cli/agent/process.d.ts +18 -0
  26. package/cli/agent/process.js +118 -0
  27. package/cli/agent/process.js.map +1 -0
  28. package/cli/agent/protocol.d.ts +3 -0
  29. package/cli/agent/protocol.js +26 -0
  30. package/cli/agent/protocol.js.map +1 -0
  31. package/cli/agent/runner.d.ts +34 -0
  32. package/cli/agent/runner.js +152 -0
  33. package/cli/agent/runner.js.map +1 -0
  34. package/cli/agent/stdio.d.ts +1 -0
  35. package/cli/agent/stdio.js +62 -0
  36. package/cli/agent/stdio.js.map +1 -0
  37. package/cli/agent/tools.d.ts +7 -0
  38. package/cli/agent/tools.js +201 -0
  39. package/cli/agent/tools.js.map +1 -0
  40. package/cli/chat/app.d.ts +16 -0
  41. package/cli/chat/app.js +345 -0
  42. package/cli/chat/app.js.map +1 -0
  43. package/cli/chat/commands.d.ts +11 -0
  44. package/cli/chat/commands.js +120 -0
  45. package/cli/chat/commands.js.map +1 -0
  46. package/cli/chat/composer.d.ts +75 -0
  47. package/cli/chat/composer.js +467 -0
  48. package/cli/chat/composer.js.map +1 -0
  49. package/cli/chat/controller.d.ts +60 -0
  50. package/cli/chat/controller.js +393 -0
  51. package/cli/chat/controller.js.map +1 -0
  52. package/cli/chat/logo.d.ts +3 -0
  53. package/cli/chat/logo.js +14 -0
  54. package/cli/chat/logo.js.map +1 -0
  55. package/cli/chat/models.d.ts +18 -0
  56. package/cli/chat/models.js +88 -0
  57. package/cli/chat/models.js.map +1 -0
  58. package/cli/chat/profile.d.ts +5 -0
  59. package/cli/chat/profile.js +98 -0
  60. package/cli/chat/profile.js.map +1 -0
  61. package/cli/chat/render.d.ts +40 -0
  62. package/cli/chat/render.js +259 -0
  63. package/cli/chat/render.js.map +1 -0
  64. package/cli/chat/screen.d.ts +26 -0
  65. package/cli/chat/screen.js +117 -0
  66. package/cli/chat/screen.js.map +1 -0
  67. package/cli/chat/source.d.ts +29 -0
  68. package/cli/chat/source.js +116 -0
  69. package/cli/chat/source.js.map +1 -0
  70. package/cli/chat/store.d.ts +43 -0
  71. package/cli/chat/store.js +94 -0
  72. package/cli/chat/store.js.map +1 -0
  73. package/cli/chat/text.d.ts +4 -0
  74. package/cli/chat/text.js +27 -0
  75. package/cli/chat/text.js.map +1 -0
  76. package/cli/commands.d.ts +26 -0
  77. package/cli/commands.js +836 -811
  78. package/cli/commands.js.map +1 -0
  79. package/cli/explorer/advanced.d.ts +14 -0
  80. package/cli/explorer/advanced.js +321 -329
  81. package/cli/explorer/advanced.js.map +1 -0
  82. package/cli/explorer/app.d.ts +2 -0
  83. package/cli/explorer/app.js +404 -314
  84. package/cli/explorer/app.js.map +1 -0
  85. package/cli/explorer/assistant.d.ts +9 -0
  86. package/cli/explorer/assistant.js +58 -0
  87. package/cli/explorer/assistant.js.map +1 -0
  88. package/cli/explorer/commands.d.ts +17 -0
  89. package/cli/explorer/commands.js +373 -302
  90. package/cli/explorer/commands.js.map +1 -0
  91. package/cli/explorer/featureViews.d.ts +124 -0
  92. package/cli/explorer/featureViews.js +179 -0
  93. package/cli/explorer/featureViews.js.map +1 -0
  94. package/cli/explorer/github.d.ts +21 -0
  95. package/cli/explorer/github.js +42 -44
  96. package/cli/explorer/github.js.map +1 -0
  97. package/cli/explorer/graphs.d.ts +7 -0
  98. package/cli/explorer/graphs.js +90 -95
  99. package/cli/explorer/graphs.js.map +1 -0
  100. package/cli/explorer/picker.d.ts +22 -0
  101. package/cli/explorer/picker.js +150 -0
  102. package/cli/explorer/picker.js.map +1 -0
  103. package/cli/explorer/report.d.ts +85 -0
  104. package/cli/explorer/report.js +60 -0
  105. package/cli/explorer/report.js.map +1 -0
  106. package/cli/explorer/session.d.ts +449 -0
  107. package/cli/explorer/session.js +143 -144
  108. package/cli/explorer/session.js.map +1 -0
  109. package/cli/explorer/views.d.ts +19 -0
  110. package/cli/explorer/views.js +465 -482
  111. package/cli/explorer/views.js.map +1 -0
  112. package/cli/explorer/wrap.d.ts +1 -0
  113. package/cli/explorer/wrap.js +28 -44
  114. package/cli/explorer/wrap.js.map +1 -0
  115. package/cli/main.d.ts +1 -0
  116. package/cli/main.js +251 -164
  117. package/cli/main.js.map +1 -0
  118. package/cli/prompt.d.ts +5 -0
  119. package/cli/prompt.js +84 -80
  120. package/cli/prompt.js.map +1 -0
  121. package/cli/scanOptions.d.ts +2 -0
  122. package/cli/scanOptions.js +11 -0
  123. package/cli/scanOptions.js.map +1 -0
  124. package/cli/ui.d.ts +33 -0
  125. package/cli/ui.js +35 -44
  126. package/cli/ui.js.map +1 -0
  127. package/cli/wizard.d.ts +187 -0
  128. package/cli/wizard.js +304 -284
  129. package/cli/wizard.js.map +1 -0
  130. package/docs/HERMES_RESEARCH.md +51 -0
  131. package/package.json +16 -4
  132. package/public/app.d.ts +1 -0
  133. package/public/app.js +1133 -1011
  134. package/public/app.js.map +1 -0
  135. package/public/assets/onboarder-mark.svg +11 -0
  136. package/public/browser.d.ts +71 -0
  137. package/public/browser.js +2 -0
  138. package/public/browser.js.map +1 -0
  139. package/public/index.html +54 -20
  140. package/public/js/about.d.ts +6 -0
  141. package/public/js/about.js +152 -172
  142. package/public/js/about.js.map +1 -0
  143. package/public/js/aiDraft.d.ts +4 -0
  144. package/public/js/aiDraft.js +166 -125
  145. package/public/js/aiDraft.js.map +1 -0
  146. package/public/js/analysisPanel.d.ts +7 -0
  147. package/public/js/analysisPanel.js +166 -166
  148. package/public/js/analysisPanel.js.map +1 -0
  149. package/public/js/analysisReport.d.ts +38 -0
  150. package/public/js/analysisReport.js +105 -110
  151. package/public/js/analysisReport.js.map +1 -0
  152. package/public/js/api.d.ts +29 -0
  153. package/public/js/api.js +198 -166
  154. package/public/js/api.js.map +1 -0
  155. package/public/js/atlas.d.ts +6 -0
  156. package/public/js/atlas.js +50 -58
  157. package/public/js/atlas.js.map +1 -0
  158. package/public/js/blameView.d.ts +2 -0
  159. package/public/js/blameView.js +36 -37
  160. package/public/js/blameView.js.map +1 -0
  161. package/public/js/codeTab.d.ts +3 -0
  162. package/public/js/codeTab.js +175 -180
  163. package/public/js/codeTab.js.map +1 -0
  164. package/public/js/codeViewer.d.ts +5 -0
  165. package/public/js/codeViewer.js +162 -150
  166. package/public/js/codeViewer.js.map +1 -0
  167. package/public/js/components/FileChip.d.ts +9 -0
  168. package/public/js/components/FileChip.js +45 -49
  169. package/public/js/components/FileChip.js.map +1 -0
  170. package/public/js/components/MetricSparkline.d.ts +7 -0
  171. package/public/js/components/MetricSparkline.js +35 -44
  172. package/public/js/components/MetricSparkline.js.map +1 -0
  173. package/public/js/components/RiskBadge.d.ts +9 -0
  174. package/public/js/components/RiskBadge.js +55 -63
  175. package/public/js/components/RiskBadge.js.map +1 -0
  176. package/public/js/deepAnalysisView.d.ts +99 -0
  177. package/public/js/deepAnalysisView.js +282 -302
  178. package/public/js/deepAnalysisView.js.map +1 -0
  179. package/public/js/diagramPane.d.ts +9 -0
  180. package/public/js/diagramPane.js +138 -135
  181. package/public/js/diagramPane.js.map +1 -0
  182. package/public/js/diffView.d.ts +6 -0
  183. package/public/js/diffView.js +222 -209
  184. package/public/js/diffView.js.map +1 -0
  185. package/public/js/docsView.d.ts +2 -0
  186. package/public/js/docsView.js +441 -442
  187. package/public/js/docsView.js.map +1 -0
  188. package/public/js/dom.d.ts +197 -0
  189. package/public/js/dom.js +5 -0
  190. package/public/js/dom.js.map +1 -0
  191. package/public/js/fileSourceBrowser.d.ts +5 -0
  192. package/public/js/fileSourceBrowser.js +33 -41
  193. package/public/js/fileSourceBrowser.js.map +1 -0
  194. package/public/js/flameGraph.d.ts +5 -0
  195. package/public/js/flameGraph.js +62 -72
  196. package/public/js/flameGraph.js.map +1 -0
  197. package/public/js/forceGraph.d.ts +10 -0
  198. package/public/js/forceGraph.js +581 -556
  199. package/public/js/forceGraph.js.map +1 -0
  200. package/public/js/heatmap.d.ts +4 -0
  201. package/public/js/heatmap.js +142 -164
  202. package/public/js/heatmap.js.map +1 -0
  203. package/public/js/highlight.d.ts +2 -0
  204. package/public/js/highlight.js +116 -107
  205. package/public/js/highlight.js.map +1 -0
  206. package/public/js/html.d.ts +2 -0
  207. package/public/js/html.js +14 -44
  208. package/public/js/html.js.map +1 -0
  209. package/public/js/insightsView.d.ts +5 -0
  210. package/public/js/insightsView.js +93 -111
  211. package/public/js/insightsView.js.map +1 -0
  212. package/public/js/inspector.d.ts +37 -0
  213. package/public/js/inspector.js +416 -445
  214. package/public/js/inspector.js.map +1 -0
  215. package/public/js/llm.d.ts +115 -0
  216. package/public/js/llm.js +246 -278
  217. package/public/js/llm.js.map +1 -0
  218. package/public/js/mapView.d.ts +3 -0
  219. package/public/js/mapView.js +121 -120
  220. package/public/js/mapView.js.map +1 -0
  221. package/public/js/markdown.d.ts +3 -0
  222. package/public/js/markdown.js +84 -91
  223. package/public/js/markdown.js.map +1 -0
  224. package/public/js/mindmap.d.ts +32 -0
  225. package/public/js/mindmap.js +244 -259
  226. package/public/js/mindmap.js.map +1 -0
  227. package/public/js/mobilePanels.d.ts +5 -0
  228. package/public/js/mobilePanels.js +50 -0
  229. package/public/js/mobilePanels.js.map +1 -0
  230. package/public/js/repoFiles.d.ts +12 -0
  231. package/public/js/repoFiles.js +29 -28
  232. package/public/js/repoFiles.js.map +1 -0
  233. package/public/js/reviewView.d.ts +28 -0
  234. package/public/js/reviewView.js +256 -0
  235. package/public/js/reviewView.js.map +1 -0
  236. package/public/js/sbomView.d.ts +3 -0
  237. package/public/js/sbomView.js +59 -67
  238. package/public/js/sbomView.js.map +1 -0
  239. package/public/js/scanCache.d.ts +16 -0
  240. package/public/js/scanCache.js +67 -65
  241. package/public/js/scanCache.js.map +1 -0
  242. package/public/js/search.d.ts +7 -0
  243. package/public/js/search.js +868 -800
  244. package/public/js/search.js.map +1 -0
  245. package/public/js/searchLocal.d.ts +42 -0
  246. package/public/js/searchLocal.js +87 -0
  247. package/public/js/searchLocal.js.map +1 -0
  248. package/public/js/serverSettings.d.ts +5 -0
  249. package/public/js/serverSettings.js +153 -164
  250. package/public/js/serverSettings.js.map +1 -0
  251. package/public/js/state.d.ts +798 -0
  252. package/public/js/state.js +114 -122
  253. package/public/js/state.js.map +1 -0
  254. package/public/js/tour.d.ts +6 -0
  255. package/public/js/tour.js +5 -7
  256. package/public/js/tour.js.map +1 -0
  257. package/public/js/transitions.d.ts +2 -0
  258. package/public/js/transitions.js +9 -10
  259. package/public/js/transitions.js.map +1 -0
  260. package/public/js/tree.d.ts +12 -0
  261. package/public/js/tree.js +157 -154
  262. package/public/js/tree.js.map +1 -0
  263. package/public/js/workflowsView.d.ts +3 -0
  264. package/public/js/workflowsView.js +15 -22
  265. package/public/js/workflowsView.js.map +1 -0
  266. package/public/search.css +495 -0
  267. package/public/styles.css +188 -346
  268. package/public/workbench.css +166 -0
  269. package/server/apiAuth.d.ts +3 -0
  270. package/server/apiAuth.js +18 -20
  271. package/server/apiAuth.js.map +1 -0
  272. package/server/apiDiff.d.ts +2 -0
  273. package/server/apiDiff.js +20 -18
  274. package/server/apiDiff.js.map +1 -0
  275. package/server/apiDocs.d.ts +2 -0
  276. package/server/apiDocs.js +38 -40
  277. package/server/apiDocs.js.map +1 -0
  278. package/server/apiFile.d.ts +1 -0
  279. package/server/apiFile.js +14 -17
  280. package/server/apiFile.js.map +1 -0
  281. package/server/apiGitBlame.d.ts +2 -0
  282. package/server/apiGitBlame.js +50 -51
  283. package/server/apiGitBlame.js.map +1 -0
  284. package/server/apiMcp.d.ts +4 -0
  285. package/server/apiMcp.js +64 -66
  286. package/server/apiMcp.js.map +1 -0
  287. package/server/apiReview.d.ts +2 -0
  288. package/server/apiReview.js +45 -0
  289. package/server/apiReview.js.map +1 -0
  290. package/server/apiScan.d.ts +5 -0
  291. package/server/apiScan.js +90 -71
  292. package/server/apiScan.js.map +1 -0
  293. package/server/apiSearch.d.ts +33 -0
  294. package/server/apiSearch.js +184 -200
  295. package/server/apiSearch.js.map +1 -0
  296. package/server/apiSettings.d.ts +4 -0
  297. package/server/apiSettings.js +84 -85
  298. package/server/apiSettings.js.map +1 -0
  299. package/server/apiTools.d.ts +3 -0
  300. package/server/apiTools.js +55 -56
  301. package/server/apiTools.js.map +1 -0
  302. package/server/auth.d.ts +5 -0
  303. package/server/auth.js +54 -51
  304. package/server/auth.js.map +1 -0
  305. package/server/config.d.ts +42 -0
  306. package/server/config.js +189 -194
  307. package/server/config.js.map +1 -0
  308. package/server/daemon.d.ts +12 -0
  309. package/server/daemon.js +124 -114
  310. package/server/daemon.js.map +1 -0
  311. package/server/fileSourceNode.d.ts +2 -0
  312. package/server/fileSourceNode.js +34 -36
  313. package/server/fileSourceNode.js.map +1 -0
  314. package/server/gitClone.d.ts +9 -0
  315. package/server/gitClone.js +59 -61
  316. package/server/gitClone.js.map +1 -0
  317. package/server/gitDiff.d.ts +36 -0
  318. package/server/gitDiff.js +271 -169
  319. package/server/gitDiff.js.map +1 -0
  320. package/server/gitHistory.d.ts +12 -0
  321. package/server/gitHistory.js +80 -80
  322. package/server/gitHistory.js.map +1 -0
  323. package/server/gitRemote.d.ts +12 -0
  324. package/server/gitRemote.js +37 -38
  325. package/server/gitRemote.js.map +1 -0
  326. package/server/hermesChat.d.ts +13 -0
  327. package/server/hermesChat.js +145 -0
  328. package/server/hermesChat.js.map +1 -0
  329. package/server/htmlText.d.ts +4 -0
  330. package/server/htmlText.js +16 -23
  331. package/server/htmlText.js.map +1 -0
  332. package/server/http.d.ts +6 -0
  333. package/server/http.js +27 -34
  334. package/server/http.js.map +1 -0
  335. package/server/httpGuards.d.ts +2 -0
  336. package/server/httpGuards.js +47 -43
  337. package/server/httpGuards.js.map +1 -0
  338. package/server/https.d.ts +34 -0
  339. package/server/https.js +121 -111
  340. package/server/https.js.map +1 -0
  341. package/server/index.d.ts +40 -0
  342. package/server/index.js +139 -147
  343. package/server/index.js.map +1 -0
  344. package/server/layout.d.ts +26 -0
  345. package/server/layout.js +59 -64
  346. package/server/layout.js.map +1 -0
  347. package/server/llmProxy.d.ts +3 -0
  348. package/server/llmProxy.js +137 -135
  349. package/server/llmProxy.js.map +1 -0
  350. package/server/logger.d.ts +38 -0
  351. package/server/logger.js +198 -211
  352. package/server/logger.js.map +1 -0
  353. package/server/mcp/analysis.d.ts +29 -0
  354. package/server/mcp/analysis.js +99 -120
  355. package/server/mcp/analysis.js.map +1 -0
  356. package/server/mcp/http.d.ts +9 -0
  357. package/server/mcp/http.js +128 -137
  358. package/server/mcp/http.js.map +1 -0
  359. package/server/mcp/runner.d.ts +68 -0
  360. package/server/mcp/runner.js +208 -218
  361. package/server/mcp/runner.js.map +1 -0
  362. package/server/mcp/server.d.ts +29 -0
  363. package/server/mcp/server.js +167 -180
  364. package/server/mcp/server.js.map +1 -0
  365. package/server/mcp/standalone.d.ts +1 -0
  366. package/server/mcp/standalone.js +8 -10
  367. package/server/mcp/standalone.js.map +1 -0
  368. package/server/mcp/tools.d.ts +2020 -0
  369. package/server/mcp/tools.js +523 -574
  370. package/server/mcp/tools.js.map +1 -0
  371. package/server/paths.d.ts +3 -0
  372. package/server/paths.js +17 -15
  373. package/server/paths.js.map +1 -0
  374. package/server/pidfile.d.ts +9 -0
  375. package/server/pidfile.js +43 -45
  376. package/server/pidfile.js.map +1 -0
  377. package/server/review.d.ts +5 -0
  378. package/server/review.js +35 -0
  379. package/server/review.js.map +1 -0
  380. package/server/router.d.ts +33 -0
  381. package/server/router.js +195 -197
  382. package/server/router.js.map +1 -0
  383. package/server/scanRequest.d.ts +3 -0
  384. package/server/scanRequest.js +36 -0
  385. package/server/scanRequest.js.map +1 -0
  386. package/server/searchIndex.d.ts +17 -0
  387. package/server/searchIndex.js +83 -80
  388. package/server/searchIndex.js.map +1 -0
  389. package/server/sessions.d.ts +14 -0
  390. package/server/sessions.js +62 -70
  391. package/server/sessions.js.map +1 -0
  392. package/server/startup.d.ts +118 -0
  393. package/server/startup.js +143 -148
  394. package/server/startup.js.map +1 -0
  395. package/server/static.d.ts +6 -0
  396. package/server/static.js +28 -31
  397. package/server/static.js.map +1 -0
  398. package/server/tools/install.d.ts +4 -0
  399. package/server/tools/install.js +176 -185
  400. package/server/tools/install.js.map +1 -0
  401. package/server/tools/parse.d.ts +8 -0
  402. package/server/tools/parse.js +112 -114
  403. package/server/tools/parse.js.map +1 -0
  404. package/server/tools/platform.d.ts +9 -0
  405. package/server/tools/platform.js +49 -47
  406. package/server/tools/platform.js.map +1 -0
  407. package/server/tools/registry.d.ts +137 -0
  408. package/server/tools/registry.js +173 -172
  409. package/server/tools/registry.js.map +1 -0
  410. package/server/tools/scan.d.ts +34 -0
  411. package/server/tools/scan.js +89 -98
  412. package/server/tools/scan.js.map +1 -0
  413. package/server/tools.d.ts +34 -0
  414. package/server/tools.js +94 -107
  415. package/server/tools.js.map +1 -0
  416. package/server/tunnel.d.ts +29 -0
  417. package/server/tunnel.js +61 -66
  418. package/server/tunnel.js.map +1 -0
  419. package/shared/analyzer/docs.d.ts +16 -0
  420. package/shared/analyzer/docs.js +99 -93
  421. package/shared/analyzer/docs.js.map +1 -0
  422. package/shared/analyzer/explainLocal.d.ts +4 -0
  423. package/shared/analyzer/explainLocal.js +122 -142
  424. package/shared/analyzer/explainLocal.js.map +1 -0
  425. package/shared/analyzer/github.d.ts +37 -0
  426. package/shared/analyzer/github.js +41 -43
  427. package/shared/analyzer/github.js.map +1 -0
  428. package/shared/analyzer/graph.d.ts +41 -0
  429. package/shared/analyzer/graph.js +393 -400
  430. package/shared/analyzer/graph.js.map +1 -0
  431. package/shared/analyzer/health.d.ts +33 -0
  432. package/shared/analyzer/health.js +197 -193
  433. package/shared/analyzer/health.js.map +1 -0
  434. package/shared/analyzer/history.d.ts +29 -0
  435. package/shared/analyzer/history.js +110 -111
  436. package/shared/analyzer/history.js.map +1 -0
  437. package/shared/analyzer/jsonConfig.d.ts +2 -0
  438. package/shared/analyzer/jsonConfig.js +61 -0
  439. package/shared/analyzer/jsonConfig.js.map +1 -0
  440. package/shared/analyzer/languages/csharp.d.ts +26 -0
  441. package/shared/analyzer/languages/csharp.js +83 -88
  442. package/shared/analyzer/languages/csharp.js.map +1 -0
  443. package/shared/analyzer/languages/generic.d.ts +23 -0
  444. package/shared/analyzer/languages/generic.js +94 -105
  445. package/shared/analyzer/languages/generic.js.map +1 -0
  446. package/shared/analyzer/languages/go.d.ts +24 -0
  447. package/shared/analyzer/languages/go.js +45 -54
  448. package/shared/analyzer/languages/go.js.map +1 -0
  449. package/shared/analyzer/languages/index.d.ts +6 -0
  450. package/shared/analyzer/languages/index.js +19 -20
  451. package/shared/analyzer/languages/index.js.map +1 -0
  452. package/shared/analyzer/languages/java.d.ts +20 -0
  453. package/shared/analyzer/languages/java.js +73 -78
  454. package/shared/analyzer/languages/java.js.map +1 -0
  455. package/shared/analyzer/languages/javascript.d.ts +12 -0
  456. package/shared/analyzer/languages/javascript.js +258 -209
  457. package/shared/analyzer/languages/javascript.js.map +1 -0
  458. package/shared/analyzer/languages/python.d.ts +23 -0
  459. package/shared/analyzer/languages/python.js +92 -100
  460. package/shared/analyzer/languages/python.js.map +1 -0
  461. package/shared/analyzer/languages/rust.d.ts +17 -0
  462. package/shared/analyzer/languages/rust.js +96 -98
  463. package/shared/analyzer/languages/rust.js.map +1 -0
  464. package/shared/analyzer/languages/typescript.d.ts +11 -0
  465. package/shared/analyzer/languages/typescript.js +117 -116
  466. package/shared/analyzer/languages/typescript.js.map +1 -0
  467. package/shared/analyzer/licenses.d.ts +18 -0
  468. package/shared/analyzer/licenses.js +130 -133
  469. package/shared/analyzer/licenses.js.map +1 -0
  470. package/shared/analyzer/metrics.d.ts +20 -0
  471. package/shared/analyzer/metrics.js +112 -104
  472. package/shared/analyzer/metrics.js.map +1 -0
  473. package/shared/analyzer/pathUtil.d.ts +10 -0
  474. package/shared/analyzer/pathUtil.js +29 -35
  475. package/shared/analyzer/pathUtil.js.map +1 -0
  476. package/shared/analyzer/patterns.d.ts +11 -0
  477. package/shared/analyzer/patterns.js +231 -253
  478. package/shared/analyzer/patterns.js.map +1 -0
  479. package/shared/analyzer/scan.d.ts +62 -0
  480. package/shared/analyzer/scan.js +451 -442
  481. package/shared/analyzer/scan.js.map +1 -0
  482. package/shared/analyzer/scanControl.d.ts +11 -0
  483. package/shared/analyzer/scanControl.js +62 -0
  484. package/shared/analyzer/scanControl.js.map +1 -0
  485. package/shared/analyzer/security.d.ts +18 -0
  486. package/shared/analyzer/security.js +159 -159
  487. package/shared/analyzer/security.js.map +1 -0
  488. package/shared/analyzer/services.d.ts +5 -0
  489. package/shared/analyzer/services.js +141 -138
  490. package/shared/analyzer/services.js.map +1 -0
  491. package/shared/analyzer/stack.d.ts +6 -0
  492. package/shared/analyzer/stack.js +134 -141
  493. package/shared/analyzer/stack.js.map +1 -0
  494. package/shared/analyzer/tour.d.ts +2 -0
  495. package/shared/analyzer/tour.js +45 -53
  496. package/shared/analyzer/tour.js.map +1 -0
  497. package/shared/analyzer/util.d.ts +8 -0
  498. package/shared/analyzer/util.js +130 -76
  499. package/shared/analyzer/util.js.map +1 -0
  500. package/shared/analyzer/workflows.d.ts +8 -0
  501. package/shared/analyzer/workflows.js +139 -134
  502. package/shared/analyzer/workflows.js.map +1 -0
  503. package/shared/contracts.d.ts +250 -0
  504. package/shared/contracts.js +2 -0
  505. package/shared/contracts.js.map +1 -0
  506. package/shared/diagram/aiFacts.d.ts +35 -0
  507. package/shared/diagram/aiFacts.js +102 -105
  508. package/shared/diagram/aiFacts.js.map +1 -0
  509. package/shared/diagram/aiMermaid.d.ts +5 -0
  510. package/shared/diagram/aiMermaid.js +50 -46
  511. package/shared/diagram/aiMermaid.js.map +1 -0
  512. package/shared/diagram/atlas.d.ts +16 -0
  513. package/shared/diagram/atlas.js +51 -60
  514. package/shared/diagram/atlas.js.map +1 -0
  515. package/shared/diagram/mermaid.d.ts +43 -0
  516. package/shared/diagram/mermaid.js +348 -370
  517. package/shared/diagram/mermaid.js.map +1 -0
  518. package/shared/review/contracts.d.ts +95 -0
  519. package/shared/review/contracts.js +2 -0
  520. package/shared/review/contracts.js.map +1 -0
  521. package/shared/review/review.d.ts +10 -0
  522. package/shared/review/review.js +184 -0
  523. package/shared/review/review.js.map +1 -0
  524. package/shared/search/query.d.ts +32 -0
  525. package/shared/search/query.js +395 -388
  526. package/shared/search/query.js.map +1 -0
@@ -1,430 +1,423 @@
1
1
  // High-performance, interactive force-directed graph canvas engine.
2
2
  // Implements Barnes-Hut physics, neighbor subgraph highlighting,
3
3
  // theme-aware crisp text rendering, and rich interactive controls.
4
-
5
4
  export function initForceGraph(canvas, options) {
6
- const opts = options || {};
7
- const ctx = canvas.getContext('2d');
8
-
9
- let animationFrameId = null;
10
- let nodes = [];
11
- let edges = [];
12
- let communities = {};
13
-
14
- let transform = { x: 0, y: 0, k: 1 };
15
- let width = canvas.width;
16
- let height = canvas.height;
17
-
18
- let alpha = 1.0;
19
- const alphaDecay = 0.985;
20
- const alphaMin = 0.001;
21
-
22
- let draggedNode = null;
23
- let hoveredNode = null;
24
- let selectedNode = null;
25
- let highlightPath = null;
26
- let searchQuery = '';
27
-
28
- // Adjacency maps for fast interactive neighbor highlighting
29
- let inNeighbors = new Map(); // node -> Set of source nodes
30
- let outNeighbors = new Map(); // node -> Set of target nodes
31
- let nodeEdges = new Map(); // node -> Set of edges
32
-
33
- function resize() {
34
- const parent = canvas.parentElement;
35
- if (!parent) return;
36
- const rect = parent.getBoundingClientRect();
37
- if (rect.width === 0 || rect.height === 0) return;
38
-
39
- const dpr = window.devicePixelRatio || 1;
40
- canvas.width = rect.width * dpr;
41
- canvas.height = rect.height * dpr;
42
- canvas.style.width = rect.width + 'px';
43
- canvas.style.height = rect.height + 'px';
44
- ctx.scale(dpr, dpr);
45
-
46
- width = rect.width;
47
- height = rect.height;
48
-
49
- if (nodes.length > 0 && transform.k === 1 && transform.x === 0 && transform.y === 0) {
50
- fitToScreen();
51
- } else {
52
- render();
5
+ const opts = options || {};
6
+ const ctx = canvas.getContext('2d');
7
+ const interactions = new AbortController();
8
+ const listen = (target, type, handler, options = {}) => {
9
+ target.addEventListener(type, handler, { ...options, signal: interactions.signal });
10
+ };
11
+ let animationFrameId = null;
12
+ let viewActive = true;
13
+ let active = !document.hidden;
14
+ let destroyed = false;
15
+ let nodes = [];
16
+ let edges = [];
17
+ let communities = {};
18
+ let transform = { x: 0, y: 0, k: 1 };
19
+ let width = canvas.width;
20
+ let height = canvas.height;
21
+ let alpha = 1.0;
22
+ const alphaDecay = 0.985;
23
+ const alphaMin = 0.001;
24
+ let draggedNode = null;
25
+ let hoveredNode = null;
26
+ let selectedNode = null;
27
+ let highlightPath = null;
28
+ let searchQuery = '';
29
+ let compactLabels = new Set();
30
+ // Adjacency maps for fast interactive neighbor highlighting
31
+ let inNeighbors = new Map(); // node -> Set of source nodes
32
+ let outNeighbors = new Map(); // node -> Set of target nodes
33
+ let nodeEdges = new Map(); // node -> Set of edges
34
+ function resize() {
35
+ if (!active || destroyed)
36
+ return;
37
+ const parent = canvas.parentElement;
38
+ if (!parent)
39
+ return;
40
+ const rect = parent.getBoundingClientRect();
41
+ if (rect.width === 0 || rect.height === 0)
42
+ return;
43
+ const dpr = window.devicePixelRatio || 1;
44
+ canvas.width = rect.width * dpr;
45
+ canvas.height = rect.height * dpr;
46
+ canvas.style.width = rect.width + 'px';
47
+ canvas.style.height = rect.height + 'px';
48
+ ctx.scale(dpr, dpr);
49
+ width = rect.width;
50
+ height = rect.height;
51
+ if (nodes.length > 0 && transform.k === 1 && transform.x === 0 && transform.y === 0) {
52
+ fitToScreen();
53
+ }
54
+ else {
55
+ render();
56
+ }
53
57
  }
54
- }
55
-
56
- // Quadtree spatial index for Barnes-Hut O(N log N) repulsion
57
- class QuadTree {
58
- constructor(x, y, w, h) {
59
- this.x = x;
60
- this.y = y;
61
- this.w = w;
62
- this.h = h;
63
- this.nodes = [];
64
- this.mass = 0;
65
- this.cx = 0;
66
- this.cy = 0;
67
- this.children = null;
58
+ // Quadtree spatial index for Barnes-Hut O(N log N) repulsion
59
+ class QuadTree {
60
+ x;
61
+ y;
62
+ w;
63
+ h;
64
+ nodes;
65
+ mass;
66
+ cx;
67
+ cy;
68
+ children;
69
+ constructor(x, y, w, h) {
70
+ this.x = x;
71
+ this.y = y;
72
+ this.w = w;
73
+ this.h = h;
74
+ this.nodes = [];
75
+ this.mass = 0;
76
+ this.cx = 0;
77
+ this.cy = 0;
78
+ this.children = null;
79
+ }
80
+ insert(node) {
81
+ if (this.children) {
82
+ const i = (node.x > this.x + this.w / 2 ? 1 : 0) + (node.y > this.y + this.h / 2 ? 2 : 0);
83
+ this.children[i].insert(node);
84
+ }
85
+ else {
86
+ this.nodes.push(node);
87
+ if (this.nodes.length > 1 && this.w > 4) {
88
+ this.subdivide();
89
+ }
90
+ }
91
+ this.mass += 1;
92
+ this.cx = (this.cx * (this.mass - 1) + node.x) / this.mass;
93
+ this.cy = (this.cy * (this.mass - 1) + node.y) / this.mass;
94
+ }
95
+ subdivide() {
96
+ const hw = this.w / 2;
97
+ const hh = this.h / 2;
98
+ this.children = [
99
+ new QuadTree(this.x, this.y, hw, hh),
100
+ new QuadTree(this.x + hw, this.y, hw, hh),
101
+ new QuadTree(this.x, this.y + hh, hw, hh),
102
+ new QuadTree(this.x + hw, this.y + hh, hw, hh)
103
+ ];
104
+ const oldNodes = this.nodes;
105
+ this.nodes = [];
106
+ for (const n of oldNodes) {
107
+ const i = (n.x > this.x + hw ? 1 : 0) + (n.y > this.y + hh ? 2 : 0);
108
+ this.children[i].insert(n);
109
+ }
110
+ }
111
+ applyForce(node, theta = 0.85) {
112
+ if (this.mass === 0)
113
+ return;
114
+ const dx = this.cx - node.x;
115
+ const dy = this.cy - node.y;
116
+ const distSq = dx * dx + dy * dy;
117
+ if (this.w / Math.sqrt(distSq || 1) < theta || !this.children) {
118
+ if (distSq > 0.01) {
119
+ const dist = Math.sqrt(distSq);
120
+ // Scale repulsion force inversely proportional to distance
121
+ const f = -120 * this.mass / (distSq + 100);
122
+ node.vx += (dx / dist) * f;
123
+ node.vy += (dy / dist) * f;
124
+ }
125
+ }
126
+ else {
127
+ for (const child of this.children) {
128
+ child.applyForce(node, theta);
129
+ }
130
+ }
131
+ }
68
132
  }
69
-
70
- insert(node) {
71
- if (this.children) {
72
- const i = (node.x > this.x + this.w / 2 ? 1 : 0) + (node.y > this.y + this.h / 2 ? 2 : 0);
73
- this.children[i].insert(node);
74
- } else {
75
- this.nodes.push(node);
76
- if (this.nodes.length > 1 && this.w > 4) {
77
- this.subdivide();
133
+ function buildAdjacency() {
134
+ inNeighbors = new Map();
135
+ outNeighbors = new Map();
136
+ nodeEdges = new Map();
137
+ for (const n of nodes) {
138
+ inNeighbors.set(n, new Set());
139
+ outNeighbors.set(n, new Set());
140
+ nodeEdges.set(n, new Set());
141
+ }
142
+ for (const e of edges) {
143
+ if (e.source && e.target) {
144
+ outNeighbors.get(e.source)?.add(e.target);
145
+ inNeighbors.get(e.target)?.add(e.source);
146
+ nodeEdges.get(e.source)?.add(e);
147
+ nodeEdges.get(e.target)?.add(e);
148
+ }
78
149
  }
79
- }
80
- this.mass += 1;
81
- this.cx = (this.cx * (this.mass - 1) + node.x) / this.mass;
82
- this.cy = (this.cy * (this.mass - 1) + node.y) / this.mass;
83
150
  }
84
-
85
- subdivide() {
86
- const hw = this.w / 2;
87
- const hh = this.h / 2;
88
- this.children = [
89
- new QuadTree(this.x, this.y, hw, hh),
90
- new QuadTree(this.x + hw, this.y, hw, hh),
91
- new QuadTree(this.x, this.y + hh, hw, hh),
92
- new QuadTree(this.x + hw, this.y + hh, hw, hh)
93
- ];
94
- const oldNodes = this.nodes;
95
- this.nodes = [];
96
- for (const n of oldNodes) {
97
- const i = (n.x > this.x + hw ? 1 : 0) + (n.y > this.y + hh ? 2 : 0);
98
- this.children[i].insert(n);
99
- }
151
+ function update(newNodes, newEdges, newComms) {
152
+ communities = newComms || {};
153
+ const posMap = new Map(nodes.map(n => [n.path, { x: n.x, y: n.y, vx: n.vx, vy: n.vy }]));
154
+ nodes = newNodes.map((n, i) => {
155
+ const p = posMap.get(n.path);
156
+ const angle = (i / Math.max(1, newNodes.length)) * Math.PI * 2;
157
+ const dist = Math.min(width, height) * 0.35;
158
+ return {
159
+ ...n,
160
+ x: p ? p.x : Math.cos(angle) * dist + (Math.random() - 0.5) * 40,
161
+ y: p ? p.y : Math.sin(angle) * dist + (Math.random() - 0.5) * 40,
162
+ vx: p ? p.vx : 0,
163
+ vy: p ? p.vy : 0,
164
+ r: Math.max(5, Math.min(24, 4 + Math.sqrt(n.fanIn || 0) * 2.5)),
165
+ basename: n.path.split('/').pop() || n.path
166
+ };
167
+ });
168
+ compactLabels = new Set([...nodes]
169
+ .sort((a, b) => b.fanIn - a.fanIn)
170
+ .slice(0, 8)
171
+ .map((node) => node.path));
172
+ const nodeByPath = new Map(nodes.map(n => [n.path, n]));
173
+ edges = (newEdges || []).map(e => ({
174
+ source: nodeByPath.get(e.from),
175
+ target: nodeByPath.get(e.to),
176
+ from: e.from,
177
+ to: e.to
178
+ })).filter(e => e.source && e.target);
179
+ buildAdjacency();
180
+ alpha = 1.0;
181
+ if (active && !destroyed && !animationFrameId)
182
+ animationFrameId = requestAnimationFrame(tick);
100
183
  }
101
-
102
- applyForce(node, theta = 0.85) {
103
- if (this.mass === 0) return;
104
- const dx = this.cx - node.x;
105
- const dy = this.cy - node.y;
106
- const distSq = dx * dx + dy * dy;
107
-
108
- if (this.w / Math.sqrt(distSq || 1) < theta || !this.children) {
109
- if (distSq > 0.01) {
110
- const dist = Math.sqrt(distSq);
111
- // Scale repulsion force inversely proportional to distance
112
- const f = -120 * this.mass / (distSq + 100);
113
- node.vx += (dx / dist) * f;
114
- node.vy += (dy / dist) * f;
184
+ function tick() {
185
+ if (!active || destroyed) {
186
+ animationFrameId = null;
187
+ return;
188
+ }
189
+ if (alpha > alphaMin) {
190
+ alpha *= alphaDecay;
191
+ // Repulsion via Quadtree
192
+ if (nodes.length > 0) {
193
+ let minX = nodes[0].x, maxX = nodes[0].x;
194
+ let minY = nodes[0].y, maxY = nodes[0].y;
195
+ for (const n of nodes) {
196
+ if (n.x < minX)
197
+ minX = n.x;
198
+ if (n.x > maxX)
199
+ maxX = n.x;
200
+ if (n.y < minY)
201
+ minY = n.y;
202
+ if (n.y > maxY)
203
+ maxY = n.y;
204
+ }
205
+ const span = Math.max(maxX - minX, maxY - minY, 100);
206
+ const qt = new QuadTree(minX - 20, minY - 20, span + 40, span + 40);
207
+ for (const n of nodes)
208
+ qt.insert(n);
209
+ for (const n of nodes)
210
+ qt.applyForce(n);
211
+ }
212
+ // Hooke's Law Edge Springs
213
+ for (const e of edges) {
214
+ const dx = e.target.x - e.source.x;
215
+ const dy = e.target.y - e.source.y;
216
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
217
+ const targetDist = 45 + (e.source.r + e.target.r) * 1.5;
218
+ const f = (dist - targetDist) * 0.04 * alpha;
219
+ const fx = (dx / dist) * f;
220
+ const fy = (dy / dist) * f;
221
+ e.source.vx += fx;
222
+ e.source.vy += fy;
223
+ e.target.vx -= fx;
224
+ e.target.vy -= fy;
225
+ }
226
+ // Center Gravity
227
+ for (const n of nodes) {
228
+ n.vx -= n.x * 0.015 * alpha;
229
+ n.vy -= n.y * 0.015 * alpha;
230
+ }
231
+ // Velocity Verlet update
232
+ for (const n of nodes) {
233
+ if (n !== draggedNode) {
234
+ n.vx *= 0.65;
235
+ n.vy *= 0.65;
236
+ n.x += n.vx;
237
+ n.y += n.vy;
238
+ }
239
+ }
115
240
  }
116
- } else {
117
- for (const child of this.children) {
118
- child.applyForce(node, theta);
241
+ render();
242
+ if (alpha > alphaMin || draggedNode) {
243
+ animationFrameId = requestAnimationFrame(tick);
244
+ }
245
+ else {
246
+ animationFrameId = null;
119
247
  }
120
- }
121
248
  }
122
- }
123
-
124
- function buildAdjacency() {
125
- inNeighbors = new Map();
126
- outNeighbors = new Map();
127
- nodeEdges = new Map();
128
-
129
- for (const n of nodes) {
130
- inNeighbors.set(n, new Set());
131
- outNeighbors.set(n, new Set());
132
- nodeEdges.set(n, new Set());
249
+ function reheat() {
250
+ alpha = 0.5;
251
+ if (active && !destroyed && !animationFrameId)
252
+ animationFrameId = requestAnimationFrame(tick);
133
253
  }
134
-
135
- for (const e of edges) {
136
- if (e.source && e.target) {
137
- outNeighbors.get(e.source)?.add(e.target);
138
- inNeighbors.get(e.target)?.add(e.source);
139
- nodeEdges.get(e.source)?.add(e);
140
- nodeEdges.get(e.target)?.add(e);
141
- }
254
+ function isDarkTheme() {
255
+ return document.documentElement.dataset.theme === 'dark';
142
256
  }
143
- }
144
-
145
- function update(newNodes, newEdges, newComms) {
146
- communities = newComms || {};
147
-
148
- const posMap = new Map(nodes.map(n => [n.path, { x: n.x, y: n.y, vx: n.vx, vy: n.vy }]));
149
-
150
- nodes = newNodes.map((n, i) => {
151
- const p = posMap.get(n.path);
152
- const angle = (i / Math.max(1, newNodes.length)) * Math.PI * 2;
153
- const dist = Math.min(width, height) * 0.35;
154
- return {
155
- ...n,
156
- x: p ? p.x : Math.cos(angle) * dist + (Math.random() - 0.5) * 40,
157
- y: p ? p.y : Math.sin(angle) * dist + (Math.random() - 0.5) * 40,
158
- vx: p ? p.vx : 0,
159
- vy: p ? p.vy : 0,
160
- r: Math.max(5, Math.min(24, 4 + Math.sqrt(n.fanIn || 0) * 2.5)),
161
- basename: n.path.split('/').pop() || n.path
162
- };
163
- });
164
-
165
- const nodeByPath = new Map(nodes.map(n => [n.path, n]));
166
- edges = (newEdges || []).map(e => ({
167
- source: nodeByPath.get(e.from),
168
- target: nodeByPath.get(e.to),
169
- from: e.from,
170
- to: e.to
171
- })).filter(e => e.source && e.target);
172
-
173
- buildAdjacency();
174
-
175
- alpha = 1.0;
176
- if (!animationFrameId) tick();
177
- }
178
-
179
- function tick() {
180
- if (alpha > alphaMin) {
181
- alpha *= alphaDecay;
182
-
183
- // Repulsion via Quadtree
184
- if (nodes.length > 0) {
185
- let minX = nodes[0].x, maxX = nodes[0].x;
186
- let minY = nodes[0].y, maxY = nodes[0].y;
257
+ function render() {
258
+ if (!active || destroyed)
259
+ return;
260
+ ctx.save();
261
+ ctx.clearRect(0, 0, width, height);
262
+ ctx.translate(transform.x, transform.y);
263
+ ctx.scale(transform.k, transform.k);
264
+ const dark = isDarkTheme();
265
+ const activeNode = hoveredNode || selectedNode;
266
+ const hasFocus = Boolean(activeNode || highlightPath || searchQuery);
267
+ const inSet = activeNode ? inNeighbors.get(activeNode) : null;
268
+ const outSet = activeNode ? outNeighbors.get(activeNode) : null;
269
+ // Draw Edges
270
+ for (const e of edges) {
271
+ const isOut = activeNode && e.source === activeNode;
272
+ const isIn = activeNode && e.target === activeNode;
273
+ const isConnected = isOut || isIn;
274
+ ctx.beginPath();
275
+ ctx.moveTo(e.source.x, e.source.y);
276
+ ctx.lineTo(e.target.x, e.target.y);
277
+ if (isConnected) {
278
+ ctx.strokeStyle = isOut ? '#f43f5e' : '#38bdf8'; // Outbound (rose) vs Inbound (cyan)
279
+ ctx.lineWidth = 2.4 / transform.k;
280
+ ctx.globalAlpha = 0.95;
281
+ }
282
+ else {
283
+ ctx.strokeStyle = dark ? 'rgba(255, 255, 255, 0.25)' : 'rgba(30, 41, 59, 0.22)';
284
+ ctx.lineWidth = 1 / transform.k;
285
+ ctx.globalAlpha = hasFocus ? 0.04 : 0.45;
286
+ }
287
+ ctx.stroke();
288
+ }
289
+ // Draw Nodes
187
290
  for (const n of nodes) {
188
- if (n.x < minX) minX = n.x; if (n.x > maxX) maxX = n.x;
189
- if (n.y < minY) minY = n.y; if (n.y > maxY) maxY = n.y;
291
+ const isTarget = n === activeNode;
292
+ const isNeighbor = activeNode && (inSet?.has(n) || outSet?.has(n));
293
+ const isPathMatch = highlightPath && n.path === highlightPath;
294
+ const isSearchMatch = searchQuery && n.path.toLowerCase().includes(searchQuery.toLowerCase());
295
+ const isHighlighted = isTarget || isNeighbor || isPathMatch || isSearchMatch;
296
+ const nodeAlpha = hasFocus ? (isHighlighted ? 1.0 : 0.18) : 0.92;
297
+ ctx.globalAlpha = nodeAlpha;
298
+ // Node circle fill
299
+ ctx.beginPath();
300
+ ctx.arc(n.x, n.y, n.r, 0, 2 * Math.PI);
301
+ const hue = n.community ? (n.community * 137.5) % 360 : ((n.fanIn * 37) % 360);
302
+ ctx.fillStyle = `hsl(${hue}, 75%, ${dark ? '55%' : '45%'})`;
303
+ ctx.fill();
304
+ // Outer border / glow ring
305
+ if (isHighlighted) {
306
+ ctx.lineWidth = (isTarget ? 3.5 : 2) / transform.k;
307
+ ctx.strokeStyle = isTarget ? (dark ? '#ffffff' : '#0f172a') : (inSet?.has(n) ? '#38bdf8' : '#f43f5e');
308
+ ctx.stroke();
309
+ }
310
+ else {
311
+ ctx.lineWidth = 1 / transform.k;
312
+ ctx.strokeStyle = dark ? 'rgba(255,255,255,0.4)' : 'rgba(0,0,0,0.2)';
313
+ ctx.stroke();
314
+ }
190
315
  }
191
- const span = Math.max(maxX - minX, maxY - minY, 100);
192
- const qt = new QuadTree(minX - 20, minY - 20, span + 40, span + 40);
193
- for (const n of nodes) qt.insert(n);
194
- for (const n of nodes) qt.applyForce(n);
195
- }
196
-
197
- // Hooke's Law Edge Springs
198
- for (const e of edges) {
199
- const dx = e.target.x - e.source.x;
200
- const dy = e.target.y - e.source.y;
201
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
202
- const targetDist = 45 + (e.source.r + e.target.r) * 1.5;
203
- const f = (dist - targetDist) * 0.04 * alpha;
204
- const fx = (dx / dist) * f;
205
- const fy = (dy / dist) * f;
206
- e.source.vx += fx; e.source.vy += fy;
207
- e.target.vx -= fx; e.target.vy -= fy;
208
- }
209
-
210
- // Center Gravity
211
- for (const n of nodes) {
212
- n.vx -= n.x * 0.015 * alpha;
213
- n.vy -= n.y * 0.015 * alpha;
214
- }
215
-
216
- // Velocity Verlet update
217
- for (const n of nodes) {
218
- if (n !== draggedNode) {
219
- n.vx *= 0.65;
220
- n.vy *= 0.65;
221
- n.x += n.vx;
222
- n.y += n.vy;
316
+ // Draw Crisp Theme-Aware Text Labels (with dark/light halo outline)
317
+ ctx.textAlign = 'left';
318
+ ctx.textBaseline = 'middle';
319
+ for (const n of nodes) {
320
+ const isTarget = n === activeNode;
321
+ const isNeighbor = activeNode && (inSet?.has(n) || outSet?.has(n));
322
+ const isPathMatch = highlightPath && n.path === highlightPath;
323
+ const isSearchMatch = searchQuery && n.path.toLowerCase().includes(searchQuery.toLowerCase());
324
+ const shouldShowLabel = width < 600
325
+ ? isTarget || isNeighbor || isPathMatch || isSearchMatch ||
326
+ (compactLabels.has(n.path) && transform.k >= 0.4) || transform.k >= 1.8
327
+ : isTarget || isNeighbor || isPathMatch || isSearchMatch ||
328
+ (n.fanIn >= 2 && transform.k >= 0.4) || transform.k >= 1.1;
329
+ if (!shouldShowLabel)
330
+ continue;
331
+ const isProminent = isTarget || isNeighbor || isPathMatch || isSearchMatch;
332
+ ctx.globalAlpha = hasFocus ? (isProminent ? 1.0 : 0.15) : 0.95;
333
+ const fontSize = Math.max(10, Math.min(14, 11 / Math.sqrt(transform.k)));
334
+ ctx.font = `${isProminent ? '600' : '500'} ${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace`;
335
+ const textX = n.x + n.r + 4 / transform.k;
336
+ const textY = n.y;
337
+ const label = n.basename;
338
+ // Stroke halo background for contrast against any background
339
+ ctx.lineWidth = 3.5 / transform.k;
340
+ ctx.lineJoin = 'round';
341
+ ctx.strokeStyle = dark ? 'rgba(10, 14, 23, 0.92)' : 'rgba(255, 255, 255, 0.95)';
342
+ ctx.strokeText(label, textX, textY);
343
+ // Bright / High-contrast foreground text
344
+ if (isTarget) {
345
+ ctx.fillStyle = dark ? '#38bdf8' : '#0284c7';
346
+ }
347
+ else if (isProminent) {
348
+ ctx.fillStyle = dark ? '#f8fafc' : '#0f172a';
349
+ }
350
+ else {
351
+ ctx.fillStyle = dark ? '#cbd5e1' : '#334155';
352
+ }
353
+ ctx.fillText(label, textX, textY);
223
354
  }
224
- }
225
- }
226
-
227
- render();
228
-
229
- if (alpha > alphaMin || draggedNode) {
230
- animationFrameId = requestAnimationFrame(tick);
231
- } else {
232
- animationFrameId = null;
233
- }
234
- }
235
-
236
- function reheat() {
237
- alpha = 0.5;
238
- if (!animationFrameId) tick();
239
- }
240
-
241
- function isDarkTheme() {
242
- return document.documentElement.dataset.theme === 'dark';
243
- }
244
-
245
- function render() {
246
- ctx.save();
247
- ctx.clearRect(0, 0, width, height);
248
-
249
- ctx.translate(transform.x, transform.y);
250
- ctx.scale(transform.k, transform.k);
251
-
252
- const dark = isDarkTheme();
253
- const activeNode = hoveredNode || selectedNode;
254
- const hasFocus = Boolean(activeNode || highlightPath || searchQuery);
255
-
256
- const inSet = activeNode ? inNeighbors.get(activeNode) : null;
257
- const outSet = activeNode ? outNeighbors.get(activeNode) : null;
258
-
259
- // Draw Edges
260
- for (const e of edges) {
261
- const isOut = activeNode && e.source === activeNode;
262
- const isIn = activeNode && e.target === activeNode;
263
- const isConnected = isOut || isIn;
264
-
265
- ctx.beginPath();
266
- ctx.moveTo(e.source.x, e.source.y);
267
- ctx.lineTo(e.target.x, e.target.y);
268
-
269
- if (isConnected) {
270
- ctx.strokeStyle = isOut ? '#f43f5e' : '#38bdf8'; // Outbound (rose) vs Inbound (cyan)
271
- ctx.lineWidth = 2.4 / transform.k;
272
- ctx.globalAlpha = 0.95;
273
- } else {
274
- ctx.strokeStyle = dark ? 'rgba(255, 255, 255, 0.25)' : 'rgba(30, 41, 59, 0.22)';
275
- ctx.lineWidth = 1 / transform.k;
276
- ctx.globalAlpha = hasFocus ? 0.04 : 0.45;
277
- }
278
- ctx.stroke();
355
+ ctx.restore();
279
356
  }
280
-
281
- // Draw Nodes
282
- for (const n of nodes) {
283
- const isTarget = n === activeNode;
284
- const isNeighbor = activeNode && (inSet?.has(n) || outSet?.has(n));
285
- const isPathMatch = highlightPath && n.path === highlightPath;
286
- const isSearchMatch = searchQuery && n.path.toLowerCase().includes(searchQuery.toLowerCase());
287
- const isHighlighted = isTarget || isNeighbor || isPathMatch || isSearchMatch;
288
-
289
- const nodeAlpha = hasFocus ? (isHighlighted ? 1.0 : 0.18) : 0.92;
290
- ctx.globalAlpha = nodeAlpha;
291
-
292
- // Node circle fill
293
- ctx.beginPath();
294
- ctx.arc(n.x, n.y, n.r, 0, 2 * Math.PI);
295
-
296
- const hue = n.community ? (n.community * 137.5) % 360 : ((n.fanIn * 37) % 360);
297
- ctx.fillStyle = `hsl(${hue}, 75%, ${dark ? '55%' : '45%'})`;
298
- ctx.fill();
299
-
300
- // Outer border / glow ring
301
- if (isHighlighted) {
302
- ctx.lineWidth = (isTarget ? 3.5 : 2) / transform.k;
303
- ctx.strokeStyle = isTarget ? (dark ? '#ffffff' : '#0f172a') : (inSet?.has(n) ? '#38bdf8' : '#f43f5e');
304
- ctx.stroke();
305
- } else {
306
- ctx.lineWidth = 1 / transform.k;
307
- ctx.strokeStyle = dark ? 'rgba(255,255,255,0.4)' : 'rgba(0,0,0,0.2)';
308
- ctx.stroke();
309
- }
310
- }
311
-
312
- // Draw Crisp Theme-Aware Text Labels (with dark/light halo outline)
313
- ctx.textAlign = 'left';
314
- ctx.textBaseline = 'middle';
315
-
316
- for (const n of nodes) {
317
- const isTarget = n === activeNode;
318
- const isNeighbor = activeNode && (inSet?.has(n) || outSet?.has(n));
319
- const isPathMatch = highlightPath && n.path === highlightPath;
320
- const isSearchMatch = searchQuery && n.path.toLowerCase().includes(searchQuery.toLowerCase());
321
- const shouldShowLabel = isTarget || isNeighbor || isPathMatch || isSearchMatch || (n.fanIn >= 2 && transform.k >= 0.4) || transform.k >= 1.1;
322
-
323
- if (!shouldShowLabel) continue;
324
-
325
- const isProminent = isTarget || isNeighbor || isPathMatch || isSearchMatch;
326
- ctx.globalAlpha = hasFocus ? (isProminent ? 1.0 : 0.15) : 0.95;
327
-
328
- const fontSize = Math.max(10, Math.min(14, 11 / Math.sqrt(transform.k)));
329
- ctx.font = `${isProminent ? '600' : '500'} ${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace`;
330
-
331
- const textX = n.x + n.r + 4 / transform.k;
332
- const textY = n.y;
333
- const label = n.basename;
334
-
335
- // Stroke halo background for contrast against any background
336
- ctx.lineWidth = 3.5 / transform.k;
337
- ctx.lineJoin = 'round';
338
- ctx.strokeStyle = dark ? 'rgba(10, 14, 23, 0.92)' : 'rgba(255, 255, 255, 0.95)';
339
- ctx.strokeText(label, textX, textY);
340
-
341
- // Bright / High-contrast foreground text
342
- if (isTarget) {
343
- ctx.fillStyle = dark ? '#38bdf8' : '#0284c7';
344
- } else if (isProminent) {
345
- ctx.fillStyle = dark ? '#f8fafc' : '#0f172a';
346
- } else {
347
- ctx.fillStyle = dark ? '#cbd5e1' : '#334155';
348
- }
349
- ctx.fillText(label, textX, textY);
350
- }
351
-
352
- ctx.restore();
353
- }
354
-
355
- function fitToScreen() {
356
- if (nodes.length === 0) {
357
- transform = { x: width / 2, y: height / 2, k: 1 };
358
- render();
359
- return;
357
+ function fitToScreen() {
358
+ if (nodes.length === 0) {
359
+ transform = { x: width / 2, y: height / 2, k: 1 };
360
+ render();
361
+ return;
362
+ }
363
+ let minX = nodes[0].x, maxX = nodes[0].x;
364
+ let minY = nodes[0].y, maxY = nodes[0].y;
365
+ for (const n of nodes) {
366
+ if (n.x - n.r < minX)
367
+ minX = n.x - n.r;
368
+ if (n.x + n.r > maxX)
369
+ maxX = n.x + n.r;
370
+ if (n.y - n.r < minY)
371
+ minY = n.y - n.r;
372
+ if (n.y + n.r > maxY)
373
+ maxY = n.y + n.r;
374
+ }
375
+ const spanW = Math.max(40, maxX - minX);
376
+ const spanH = Math.max(40, maxY - minY);
377
+ const padding = 60;
378
+ const k = Math.min((width - padding * 2) / spanW, (height - padding * 2) / spanH, 2.0);
379
+ const cx = (minX + maxX) / 2;
380
+ const cy = (minY + maxY) / 2;
381
+ transform.k = Math.max(0.2, k);
382
+ transform.x = width / 2 - cx * transform.k;
383
+ transform.y = height / 2 - cy * transform.k;
384
+ render();
360
385
  }
361
-
362
- let minX = nodes[0].x, maxX = nodes[0].x;
363
- let minY = nodes[0].y, maxY = nodes[0].y;
364
- for (const n of nodes) {
365
- if (n.x - n.r < minX) minX = n.x - n.r;
366
- if (n.x + n.r > maxX) maxX = n.x + n.r;
367
- if (n.y - n.r < minY) minY = n.y - n.r;
368
- if (n.y + n.r > maxY) maxY = n.y + n.r;
386
+ function resetLayout() {
387
+ const dist = Math.min(width, height) * 0.35;
388
+ nodes.forEach((n, i) => {
389
+ const angle = (i / Math.max(1, nodes.length)) * Math.PI * 2;
390
+ n.x = Math.cos(angle) * dist + (Math.random() - 0.5) * 30;
391
+ n.y = Math.sin(angle) * dist + (Math.random() - 0.5) * 30;
392
+ n.vx = 0;
393
+ n.vy = 0;
394
+ });
395
+ fitToScreen();
396
+ alpha = 1.0;
397
+ if (active && !destroyed && !animationFrameId)
398
+ animationFrameId = requestAnimationFrame(tick);
369
399
  }
370
-
371
- const spanW = Math.max(40, maxX - minX);
372
- const spanH = Math.max(40, maxY - minY);
373
- const padding = 60;
374
-
375
- const k = Math.min(
376
- (width - padding * 2) / spanW,
377
- (height - padding * 2) / spanH,
378
- 2.0
379
- );
380
-
381
- const cx = (minX + maxX) / 2;
382
- const cy = (minY + maxY) / 2;
383
-
384
- transform.k = Math.max(0.2, k);
385
- transform.x = width / 2 - cx * transform.k;
386
- transform.y = height / 2 - cy * transform.k;
387
-
388
- render();
389
- }
390
-
391
- function resetLayout() {
392
- const dist = Math.min(width, height) * 0.35;
393
- nodes.forEach((n, i) => {
394
- const angle = (i / Math.max(1, nodes.length)) * Math.PI * 2;
395
- n.x = Math.cos(angle) * dist + (Math.random() - 0.5) * 30;
396
- n.y = Math.sin(angle) * dist + (Math.random() - 0.5) * 30;
397
- n.vx = 0;
398
- n.vy = 0;
399
- });
400
- fitToScreen();
401
- alpha = 1.0;
402
- if (!animationFrameId) tick();
403
- }
404
-
405
- function flyToNode(node) {
406
- if (!node) return;
407
- const targetK = Math.max(1.2, transform.k);
408
- transform.k = targetK;
409
- transform.x = width / 2 - node.x * targetK;
410
- transform.y = height / 2 - node.y * targetK;
411
- selectedNode = node;
412
- render();
413
- }
414
-
415
- function updateTooltip(node, clientX, clientY) {
416
- const tooltip = document.getElementById('graphTooltip');
417
- if (!tooltip) return;
418
-
419
- if (!node) {
420
- tooltip.hidden = true;
421
- return;
400
+ function flyToNode(node) {
401
+ if (!node)
402
+ return;
403
+ const targetK = Math.max(1.2, transform.k);
404
+ transform.k = targetK;
405
+ transform.x = width / 2 - node.x * targetK;
406
+ transform.y = height / 2 - node.y * targetK;
407
+ selectedNode = node;
408
+ render();
422
409
  }
423
-
424
- const inCount = inNeighbors.get(node)?.size || node.fanIn || 0;
425
- const outCount = outNeighbors.get(node)?.size || node.fanOut || 0;
426
-
427
- tooltip.innerHTML = `
410
+ function updateTooltip(node, clientX, clientY) {
411
+ const tooltip = document.getElementById('graphTooltip');
412
+ if (!tooltip)
413
+ return;
414
+ if (!node) {
415
+ tooltip.hidden = true;
416
+ return;
417
+ }
418
+ const inCount = inNeighbors.get(node)?.size || node.fanIn || 0;
419
+ const outCount = outNeighbors.get(node)?.size || node.fanOut || 0;
420
+ tooltip.innerHTML = `
428
421
  <div style="font-weight:600; font-size:13px; margin-bottom:4px; display:flex; align-items:center; gap:6px;">
429
422
  <span style="display:inline-block; width:8px; height:8px; border-radius:50%; background:hsl(${(node.community * 137.5) % 360}, 75%, 50%);"></span>
430
423
  ${node.basename}
@@ -437,156 +430,188 @@ export function initForceGraph(canvas, options) {
437
430
  </div>
438
431
  <div style="font-size:10px; color:var(--faint); margin-top:6px; font-style:italic;">Click to open · Double-click to center</div>
439
432
  `;
440
-
441
- tooltip.style.left = Math.min(clientX + 14, window.innerWidth - 260) + 'px';
442
- tooltip.style.top = Math.min(clientY + 14, window.innerHeight - 160) + 'px';
443
- tooltip.hidden = false;
444
- }
445
-
446
- // --- Interaction Event Handlers ---
447
- let isPanning = false;
448
- let startX = 0, startY = 0;
449
- let lastClickTime = 0;
450
-
451
- canvas.addEventListener('mousedown', e => {
452
- const rect = canvas.getBoundingClientRect();
453
- const mx = (e.clientX - rect.left - transform.x) / transform.k;
454
- const my = (e.clientY - rect.top - transform.y) / transform.k;
455
-
456
- draggedNode = nodes.find(n => {
457
- const dx = n.x - mx;
458
- const dy = n.y - my;
459
- return Math.sqrt(dx * dx + dy * dy) <= (n.r + 4);
460
- });
461
-
462
- if (draggedNode) {
463
- draggedNode.vx = 0;
464
- draggedNode.vy = 0;
465
- reheat();
466
- } else {
467
- isPanning = true;
468
- startX = e.clientX - transform.x;
469
- startY = e.clientY - transform.y;
470
- }
471
- });
472
-
473
- canvas.addEventListener('mousemove', e => {
474
- const rect = canvas.getBoundingClientRect();
475
-
476
- if (draggedNode) {
477
- draggedNode.x = (e.clientX - rect.left - transform.x) / transform.k;
478
- draggedNode.y = (e.clientY - rect.top - transform.y) / transform.k;
479
- draggedNode.vx = 0;
480
- draggedNode.vy = 0;
481
- updateTooltip(draggedNode, e.clientX, e.clientY);
482
- if (!animationFrameId) render();
483
- } else if (isPanning) {
484
- transform.x = e.clientX - startX;
485
- transform.y = e.clientY - startY;
486
- if (!animationFrameId) render();
487
- } else {
488
- const mx = (e.clientX - rect.left - transform.x) / transform.k;
489
- const my = (e.clientY - rect.top - transform.y) / transform.k;
490
-
491
- const newHover = nodes.find(n => {
492
- const dx = n.x - mx;
493
- const dy = n.y - my;
494
- return Math.sqrt(dx * dx + dy * dy) <= (n.r + 4);
495
- });
496
-
497
- if (newHover !== hoveredNode) {
498
- hoveredNode = newHover || null;
499
- canvas.style.cursor = hoveredNode ? 'pointer' : 'grab';
500
- updateTooltip(hoveredNode, e.clientX, e.clientY);
501
- if (!animationFrameId) render();
502
- } else if (hoveredNode) {
503
- updateTooltip(hoveredNode, e.clientX, e.clientY);
504
- }
433
+ tooltip.style.left = Math.min(clientX + 14, window.innerWidth - 260) + 'px';
434
+ tooltip.style.top = Math.min(clientY + 14, window.innerHeight - 160) + 'px';
435
+ tooltip.hidden = false;
505
436
  }
506
- });
507
-
508
- window.addEventListener('mouseup', () => {
509
- draggedNode = null;
510
- isPanning = false;
511
- });
512
-
513
- canvas.addEventListener('wheel', e => {
514
- e.preventDefault();
515
- const rect = canvas.getBoundingClientRect();
516
- const mx = e.clientX - rect.left;
517
- const my = e.clientY - rect.top;
518
-
519
- const zoomFactor = e.deltaY < 0 ? 1.15 : 0.87;
520
- const newK = Math.max(0.1, Math.min(5.0, transform.k * zoomFactor));
521
-
522
- transform.x = mx - (mx - transform.x) * (newK / transform.k);
523
- transform.y = my - (my - transform.y) * (newK / transform.k);
524
- transform.k = newK;
525
-
526
- if (!animationFrameId) render();
527
- }, { passive: false });
528
-
529
- canvas.addEventListener('click', e => {
530
- const now = Date.now();
531
- const isDouble = now - lastClickTime < 300;
532
- lastClickTime = now;
533
-
534
- if (hoveredNode) {
535
- if (isDouble) {
536
- flyToNode(hoveredNode);
537
- } else {
538
- selectedNode = hoveredNode;
539
- canvas.dispatchEvent(new CustomEvent('graph-node-click', {
540
- detail: { path: hoveredNode.path },
541
- bubbles: true
542
- }));
543
- }
544
- } else {
545
- selectedNode = null;
546
- if (!animationFrameId) render();
547
- }
548
- });
549
-
550
- canvas.addEventListener('mouseleave', () => {
551
- hoveredNode = null;
552
- updateTooltip(null);
553
- if (!animationFrameId) render();
554
- });
555
-
556
- // Wire Controls HUD
557
- const resetBtn = document.getElementById('graphReset');
558
- const zoomInBtn = document.getElementById('graphZoomIn');
559
- const zoomOutBtn = document.getElementById('graphZoomOut');
560
-
561
- if (resetBtn) resetBtn.onclick = () => resetLayout();
562
- if (zoomInBtn) zoomInBtn.onclick = () => {
563
- const newK = Math.min(5.0, transform.k * 1.25);
564
- transform.x = width / 2 - (width / 2 - transform.x) * (newK / transform.k);
565
- transform.y = height / 2 - (height / 2 - transform.y) * (newK / transform.k);
566
- transform.k = newK;
567
- if (!animationFrameId) render();
568
- };
569
- if (zoomOutBtn) zoomOutBtn.onclick = () => {
570
- const newK = Math.max(0.1, transform.k * 0.8);
571
- transform.x = width / 2 - (width / 2 - transform.x) * (newK / transform.k);
572
- transform.y = height / 2 - (height / 2 - transform.y) * (newK / transform.k);
573
- transform.k = newK;
574
- if (!animationFrameId) render();
575
- };
576
-
577
- window.addEventListener('resize', resize);
578
- setTimeout(resize, 50);
579
-
580
- return {
581
- update,
582
- resize,
583
- fit: fitToScreen,
584
- reset: resetLayout,
585
- search: q => { searchQuery = q; if (!animationFrameId) render(); },
586
- highlight: path => { highlightPath = path; if (!animationFrameId) render(); },
587
- destroy: () => {
588
- window.removeEventListener('resize', resize);
589
- if (animationFrameId) cancelAnimationFrame(animationFrameId);
437
+ // --- Interaction Event Handlers ---
438
+ let isPanning = false;
439
+ let startX = 0, startY = 0;
440
+ let lastClickTime = 0;
441
+ const nodeAt = e => {
442
+ const rect = canvas.getBoundingClientRect();
443
+ const mx = (e.clientX - rect.left - transform.x) / transform.k;
444
+ const my = (e.clientY - rect.top - transform.y) / transform.k;
445
+ return nodes.find(n => {
446
+ const dx = n.x - mx;
447
+ const dy = n.y - my;
448
+ return Math.sqrt(dx * dx + dy * dy) <= (n.r + 4);
449
+ });
450
+ };
451
+ listen(canvas, 'pointerdown', e => {
452
+ canvas.setPointerCapture(e.pointerId);
453
+ draggedNode = nodeAt(e);
454
+ if (draggedNode) {
455
+ draggedNode.vx = 0;
456
+ draggedNode.vy = 0;
457
+ reheat();
458
+ }
459
+ else {
460
+ isPanning = true;
461
+ startX = e.clientX - transform.x;
462
+ startY = e.clientY - transform.y;
463
+ }
464
+ });
465
+ listen(canvas, 'pointermove', e => {
466
+ const rect = canvas.getBoundingClientRect();
467
+ if (draggedNode) {
468
+ draggedNode.x = (e.clientX - rect.left - transform.x) / transform.k;
469
+ draggedNode.y = (e.clientY - rect.top - transform.y) / transform.k;
470
+ draggedNode.vx = 0;
471
+ draggedNode.vy = 0;
472
+ updateTooltip(draggedNode, e.clientX, e.clientY);
473
+ if (!animationFrameId)
474
+ render();
475
+ }
476
+ else if (isPanning) {
477
+ transform.x = e.clientX - startX;
478
+ transform.y = e.clientY - startY;
479
+ if (!animationFrameId)
480
+ render();
481
+ }
482
+ else {
483
+ const newHover = nodeAt(e);
484
+ if (newHover !== hoveredNode) {
485
+ hoveredNode = newHover || null;
486
+ canvas.style.cursor = hoveredNode ? 'pointer' : 'grab';
487
+ updateTooltip(hoveredNode, e.clientX, e.clientY);
488
+ if (!animationFrameId)
489
+ render();
490
+ }
491
+ else if (hoveredNode) {
492
+ updateTooltip(hoveredNode, e.clientX, e.clientY);
493
+ }
494
+ }
495
+ });
496
+ const stopPointer = e => {
497
+ draggedNode = null;
498
+ isPanning = false;
499
+ if (canvas.hasPointerCapture(e.pointerId))
500
+ canvas.releasePointerCapture(e.pointerId);
501
+ };
502
+ listen(canvas, 'pointerup', stopPointer);
503
+ listen(canvas, 'pointercancel', stopPointer);
504
+ listen(canvas, 'wheel', e => {
505
+ e.preventDefault();
506
+ const rect = canvas.getBoundingClientRect();
507
+ const mx = e.clientX - rect.left;
508
+ const my = e.clientY - rect.top;
509
+ const zoomFactor = e.deltaY < 0 ? 1.15 : 0.87;
510
+ const newK = Math.max(0.1, Math.min(5.0, transform.k * zoomFactor));
511
+ transform.x = mx - (mx - transform.x) * (newK / transform.k);
512
+ transform.y = my - (my - transform.y) * (newK / transform.k);
513
+ transform.k = newK;
514
+ if (!animationFrameId)
515
+ render();
516
+ }, { passive: false });
517
+ listen(canvas, 'click', e => {
518
+ const now = Date.now();
519
+ const isDouble = now - lastClickTime < 300;
520
+ lastClickTime = now;
521
+ const clickedNode = nodeAt(e);
522
+ if (clickedNode) {
523
+ if (isDouble) {
524
+ flyToNode(clickedNode);
525
+ }
526
+ else {
527
+ selectedNode = clickedNode;
528
+ canvas.dispatchEvent(new CustomEvent('graph-node-click', {
529
+ detail: { path: clickedNode.path },
530
+ bubbles: true
531
+ }));
532
+ }
533
+ }
534
+ else {
535
+ selectedNode = null;
536
+ if (!animationFrameId)
537
+ render();
538
+ }
539
+ });
540
+ listen(canvas, 'mouseleave', () => {
541
+ hoveredNode = null;
542
+ updateTooltip(null);
543
+ if (!animationFrameId)
544
+ render();
545
+ });
546
+ // Wire Controls HUD
547
+ const resetBtn = document.getElementById('graphReset');
548
+ const zoomInBtn = document.getElementById('graphZoomIn');
549
+ const zoomOutBtn = document.getElementById('graphZoomOut');
550
+ if (resetBtn)
551
+ listen(resetBtn, 'click', () => resetLayout());
552
+ if (zoomInBtn)
553
+ listen(zoomInBtn, 'click', () => {
554
+ const newK = Math.min(5.0, transform.k * 1.25);
555
+ transform.x = width / 2 - (width / 2 - transform.x) * (newK / transform.k);
556
+ transform.y = height / 2 - (height / 2 - transform.y) * (newK / transform.k);
557
+ transform.k = newK;
558
+ if (!animationFrameId)
559
+ render();
560
+ });
561
+ if (zoomOutBtn)
562
+ listen(zoomOutBtn, 'click', () => {
563
+ const newK = Math.max(0.1, transform.k * 0.8);
564
+ transform.x = width / 2 - (width / 2 - transform.x) * (newK / transform.k);
565
+ transform.y = height / 2 - (height / 2 - transform.y) * (newK / transform.k);
566
+ transform.k = newK;
567
+ if (!animationFrameId)
568
+ render();
569
+ });
570
+ function updateActivity() {
571
+ const next = viewActive && !document.hidden && !destroyed;
572
+ if (next === active)
573
+ return;
574
+ active = next;
575
+ if (!active) {
576
+ if (animationFrameId)
577
+ cancelAnimationFrame(animationFrameId);
578
+ animationFrameId = null;
579
+ draggedNode = null;
580
+ isPanning = false;
581
+ updateTooltip(null);
582
+ }
583
+ else {
584
+ resize();
585
+ if (alpha > alphaMin && !animationFrameId)
586
+ animationFrameId = requestAnimationFrame(tick);
587
+ }
590
588
  }
591
- };
589
+ function setActive(value) { viewActive = value; updateActivity(); }
590
+ window.addEventListener('resize', resize);
591
+ document.addEventListener('visibilitychange', updateActivity);
592
+ const initialResize = setTimeout(resize, 50);
593
+ return {
594
+ update,
595
+ resize,
596
+ setActive,
597
+ fit: fitToScreen,
598
+ reset: resetLayout,
599
+ search: q => { searchQuery = q; if (!animationFrameId)
600
+ render(); },
601
+ highlight: path => { highlightPath = path; if (!animationFrameId)
602
+ render(); },
603
+ destroy: () => {
604
+ destroyed = true;
605
+ active = false;
606
+ interactions.abort();
607
+ updateTooltip(null);
608
+ clearTimeout(initialResize);
609
+ document.removeEventListener('visibilitychange', updateActivity);
610
+ window.removeEventListener('resize', resize);
611
+ if (animationFrameId)
612
+ cancelAnimationFrame(animationFrameId);
613
+ animationFrameId = null;
614
+ }
615
+ };
592
616
  }
617
+ //# sourceMappingURL=forceGraph.js.map