codebase-onboarder 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.
Files changed (448) hide show
  1. package/README.md +174 -46
  2. package/bin/build-if-source.js +23 -0
  3. package/cli/commands.d.ts +26 -0
  4. package/cli/commands.js +836 -811
  5. package/cli/commands.js.map +1 -0
  6. package/cli/explorer/advanced.d.ts +14 -0
  7. package/cli/explorer/advanced.js +321 -329
  8. package/cli/explorer/advanced.js.map +1 -0
  9. package/cli/explorer/app.d.ts +1 -0
  10. package/cli/explorer/app.js +384 -313
  11. package/cli/explorer/app.js.map +1 -0
  12. package/cli/explorer/assistant.d.ts +9 -0
  13. package/cli/explorer/assistant.js +58 -0
  14. package/cli/explorer/assistant.js.map +1 -0
  15. package/cli/explorer/commands.d.ts +17 -0
  16. package/cli/explorer/commands.js +368 -302
  17. package/cli/explorer/commands.js.map +1 -0
  18. package/cli/explorer/featureViews.d.ts +124 -0
  19. package/cli/explorer/featureViews.js +179 -0
  20. package/cli/explorer/featureViews.js.map +1 -0
  21. package/cli/explorer/github.d.ts +21 -0
  22. package/cli/explorer/github.js +42 -44
  23. package/cli/explorer/github.js.map +1 -0
  24. package/cli/explorer/graphs.d.ts +7 -0
  25. package/cli/explorer/graphs.js +90 -95
  26. package/cli/explorer/graphs.js.map +1 -0
  27. package/cli/explorer/picker.d.ts +22 -0
  28. package/cli/explorer/picker.js +150 -0
  29. package/cli/explorer/picker.js.map +1 -0
  30. package/cli/explorer/report.d.ts +85 -0
  31. package/cli/explorer/report.js +60 -0
  32. package/cli/explorer/report.js.map +1 -0
  33. package/cli/explorer/session.d.ts +449 -0
  34. package/cli/explorer/session.js +143 -144
  35. package/cli/explorer/session.js.map +1 -0
  36. package/cli/explorer/views.d.ts +19 -0
  37. package/cli/explorer/views.js +465 -482
  38. package/cli/explorer/views.js.map +1 -0
  39. package/cli/explorer/wrap.d.ts +1 -0
  40. package/cli/explorer/wrap.js +28 -44
  41. package/cli/explorer/wrap.js.map +1 -0
  42. package/cli/main.d.ts +1 -0
  43. package/cli/main.js +214 -161
  44. package/cli/main.js.map +1 -0
  45. package/cli/prompt.d.ts +5 -0
  46. package/cli/prompt.js +84 -80
  47. package/cli/prompt.js.map +1 -0
  48. package/cli/scanOptions.d.ts +2 -0
  49. package/cli/scanOptions.js +11 -0
  50. package/cli/scanOptions.js.map +1 -0
  51. package/cli/ui.d.ts +33 -0
  52. package/cli/ui.js +35 -44
  53. package/cli/ui.js.map +1 -0
  54. package/cli/wizard.d.ts +187 -0
  55. package/cli/wizard.js +304 -284
  56. package/cli/wizard.js.map +1 -0
  57. package/package.json +15 -4
  58. package/public/app.d.ts +1 -0
  59. package/public/app.js +1057 -1009
  60. package/public/app.js.map +1 -0
  61. package/public/browser.d.ts +68 -0
  62. package/public/browser.js +2 -0
  63. package/public/browser.js.map +1 -0
  64. package/public/index.html +30 -13
  65. package/public/js/about.d.ts +6 -0
  66. package/public/js/about.js +152 -172
  67. package/public/js/about.js.map +1 -0
  68. package/public/js/aiDraft.d.ts +3 -0
  69. package/public/js/aiDraft.js +116 -124
  70. package/public/js/aiDraft.js.map +1 -0
  71. package/public/js/analysisPanel.d.ts +7 -0
  72. package/public/js/analysisPanel.js +166 -166
  73. package/public/js/analysisPanel.js.map +1 -0
  74. package/public/js/analysisReport.d.ts +38 -0
  75. package/public/js/analysisReport.js +105 -110
  76. package/public/js/analysisReport.js.map +1 -0
  77. package/public/js/api.d.ts +26 -0
  78. package/public/js/api.js +176 -167
  79. package/public/js/api.js.map +1 -0
  80. package/public/js/atlas.d.ts +6 -0
  81. package/public/js/atlas.js +50 -58
  82. package/public/js/atlas.js.map +1 -0
  83. package/public/js/blameView.d.ts +2 -0
  84. package/public/js/blameView.js +36 -37
  85. package/public/js/blameView.js.map +1 -0
  86. package/public/js/codeTab.d.ts +3 -0
  87. package/public/js/codeTab.js +175 -180
  88. package/public/js/codeTab.js.map +1 -0
  89. package/public/js/codeViewer.d.ts +5 -0
  90. package/public/js/codeViewer.js +162 -150
  91. package/public/js/codeViewer.js.map +1 -0
  92. package/public/js/components/FileChip.d.ts +9 -0
  93. package/public/js/components/FileChip.js +45 -49
  94. package/public/js/components/FileChip.js.map +1 -0
  95. package/public/js/components/MetricSparkline.d.ts +7 -0
  96. package/public/js/components/MetricSparkline.js +35 -44
  97. package/public/js/components/MetricSparkline.js.map +1 -0
  98. package/public/js/components/RiskBadge.d.ts +9 -0
  99. package/public/js/components/RiskBadge.js +55 -63
  100. package/public/js/components/RiskBadge.js.map +1 -0
  101. package/public/js/deepAnalysisView.d.ts +99 -0
  102. package/public/js/deepAnalysisView.js +282 -302
  103. package/public/js/deepAnalysisView.js.map +1 -0
  104. package/public/js/diagramPane.d.ts +8 -0
  105. package/public/js/diagramPane.js +133 -135
  106. package/public/js/diagramPane.js.map +1 -0
  107. package/public/js/diffView.d.ts +6 -0
  108. package/public/js/diffView.js +222 -209
  109. package/public/js/diffView.js.map +1 -0
  110. package/public/js/docsView.d.ts +2 -0
  111. package/public/js/docsView.js +441 -442
  112. package/public/js/docsView.js.map +1 -0
  113. package/public/js/dom.d.ts +189 -0
  114. package/public/js/dom.js +5 -0
  115. package/public/js/dom.js.map +1 -0
  116. package/public/js/fileSourceBrowser.d.ts +5 -0
  117. package/public/js/fileSourceBrowser.js +33 -41
  118. package/public/js/fileSourceBrowser.js.map +1 -0
  119. package/public/js/flameGraph.d.ts +5 -0
  120. package/public/js/flameGraph.js +62 -72
  121. package/public/js/flameGraph.js.map +1 -0
  122. package/public/js/forceGraph.d.ts +10 -0
  123. package/public/js/forceGraph.js +581 -556
  124. package/public/js/forceGraph.js.map +1 -0
  125. package/public/js/heatmap.d.ts +4 -0
  126. package/public/js/heatmap.js +142 -164
  127. package/public/js/heatmap.js.map +1 -0
  128. package/public/js/highlight.d.ts +2 -0
  129. package/public/js/highlight.js +116 -107
  130. package/public/js/highlight.js.map +1 -0
  131. package/public/js/html.d.ts +2 -0
  132. package/public/js/html.js +14 -44
  133. package/public/js/html.js.map +1 -0
  134. package/public/js/insightsView.d.ts +5 -0
  135. package/public/js/insightsView.js +93 -111
  136. package/public/js/insightsView.js.map +1 -0
  137. package/public/js/inspector.d.ts +37 -0
  138. package/public/js/inspector.js +416 -445
  139. package/public/js/inspector.js.map +1 -0
  140. package/public/js/llm.d.ts +113 -0
  141. package/public/js/llm.js +233 -272
  142. package/public/js/llm.js.map +1 -0
  143. package/public/js/mapView.d.ts +3 -0
  144. package/public/js/mapView.js +121 -120
  145. package/public/js/mapView.js.map +1 -0
  146. package/public/js/markdown.d.ts +3 -0
  147. package/public/js/markdown.js +84 -91
  148. package/public/js/markdown.js.map +1 -0
  149. package/public/js/mindmap.d.ts +32 -0
  150. package/public/js/mindmap.js +244 -259
  151. package/public/js/mindmap.js.map +1 -0
  152. package/public/js/mobilePanels.d.ts +5 -0
  153. package/public/js/mobilePanels.js +50 -0
  154. package/public/js/mobilePanels.js.map +1 -0
  155. package/public/js/repoFiles.d.ts +12 -0
  156. package/public/js/repoFiles.js +28 -27
  157. package/public/js/repoFiles.js.map +1 -0
  158. package/public/js/reviewView.d.ts +28 -0
  159. package/public/js/reviewView.js +256 -0
  160. package/public/js/reviewView.js.map +1 -0
  161. package/public/js/sbomView.d.ts +3 -0
  162. package/public/js/sbomView.js +59 -67
  163. package/public/js/sbomView.js.map +1 -0
  164. package/public/js/scanCache.d.ts +16 -0
  165. package/public/js/scanCache.js +67 -65
  166. package/public/js/scanCache.js.map +1 -0
  167. package/public/js/search.d.ts +7 -0
  168. package/public/js/search.js +868 -800
  169. package/public/js/search.js.map +1 -0
  170. package/public/js/searchLocal.d.ts +42 -0
  171. package/public/js/searchLocal.js +87 -0
  172. package/public/js/searchLocal.js.map +1 -0
  173. package/public/js/serverSettings.d.ts +5 -0
  174. package/public/js/serverSettings.js +153 -164
  175. package/public/js/serverSettings.js.map +1 -0
  176. package/public/js/state.d.ts +798 -0
  177. package/public/js/state.js +114 -122
  178. package/public/js/state.js.map +1 -0
  179. package/public/js/tour.d.ts +6 -0
  180. package/public/js/tour.js +5 -7
  181. package/public/js/tour.js.map +1 -0
  182. package/public/js/transitions.d.ts +2 -0
  183. package/public/js/transitions.js +9 -10
  184. package/public/js/transitions.js.map +1 -0
  185. package/public/js/tree.d.ts +12 -0
  186. package/public/js/tree.js +157 -154
  187. package/public/js/tree.js.map +1 -0
  188. package/public/js/workflowsView.d.ts +3 -0
  189. package/public/js/workflowsView.js +15 -22
  190. package/public/js/workflowsView.js.map +1 -0
  191. package/public/search.css +495 -0
  192. package/public/styles.css +163 -346
  193. package/public/workbench.css +166 -0
  194. package/server/apiAuth.d.ts +3 -0
  195. package/server/apiAuth.js +18 -20
  196. package/server/apiAuth.js.map +1 -0
  197. package/server/apiDiff.d.ts +2 -0
  198. package/server/apiDiff.js +20 -18
  199. package/server/apiDiff.js.map +1 -0
  200. package/server/apiDocs.d.ts +2 -0
  201. package/server/apiDocs.js +38 -40
  202. package/server/apiDocs.js.map +1 -0
  203. package/server/apiFile.d.ts +1 -0
  204. package/server/apiFile.js +14 -17
  205. package/server/apiFile.js.map +1 -0
  206. package/server/apiGitBlame.d.ts +2 -0
  207. package/server/apiGitBlame.js +50 -51
  208. package/server/apiGitBlame.js.map +1 -0
  209. package/server/apiMcp.d.ts +4 -0
  210. package/server/apiMcp.js +64 -66
  211. package/server/apiMcp.js.map +1 -0
  212. package/server/apiReview.d.ts +2 -0
  213. package/server/apiReview.js +45 -0
  214. package/server/apiReview.js.map +1 -0
  215. package/server/apiScan.d.ts +5 -0
  216. package/server/apiScan.js +90 -71
  217. package/server/apiScan.js.map +1 -0
  218. package/server/apiSearch.d.ts +33 -0
  219. package/server/apiSearch.js +184 -200
  220. package/server/apiSearch.js.map +1 -0
  221. package/server/apiSettings.d.ts +4 -0
  222. package/server/apiSettings.js +84 -85
  223. package/server/apiSettings.js.map +1 -0
  224. package/server/apiTools.d.ts +3 -0
  225. package/server/apiTools.js +55 -56
  226. package/server/apiTools.js.map +1 -0
  227. package/server/auth.d.ts +5 -0
  228. package/server/auth.js +54 -51
  229. package/server/auth.js.map +1 -0
  230. package/server/config.d.ts +42 -0
  231. package/server/config.js +189 -194
  232. package/server/config.js.map +1 -0
  233. package/server/daemon.d.ts +12 -0
  234. package/server/daemon.js +124 -114
  235. package/server/daemon.js.map +1 -0
  236. package/server/fileSourceNode.d.ts +2 -0
  237. package/server/fileSourceNode.js +34 -36
  238. package/server/fileSourceNode.js.map +1 -0
  239. package/server/gitClone.d.ts +9 -0
  240. package/server/gitClone.js +59 -61
  241. package/server/gitClone.js.map +1 -0
  242. package/server/gitDiff.d.ts +36 -0
  243. package/server/gitDiff.js +271 -169
  244. package/server/gitDiff.js.map +1 -0
  245. package/server/gitHistory.d.ts +12 -0
  246. package/server/gitHistory.js +80 -80
  247. package/server/gitHistory.js.map +1 -0
  248. package/server/gitRemote.d.ts +12 -0
  249. package/server/gitRemote.js +37 -38
  250. package/server/gitRemote.js.map +1 -0
  251. package/server/htmlText.d.ts +4 -0
  252. package/server/htmlText.js +16 -23
  253. package/server/htmlText.js.map +1 -0
  254. package/server/http.d.ts +5 -0
  255. package/server/http.js +27 -30
  256. package/server/http.js.map +1 -0
  257. package/server/httpGuards.d.ts +2 -0
  258. package/server/httpGuards.js +47 -43
  259. package/server/httpGuards.js.map +1 -0
  260. package/server/https.d.ts +34 -0
  261. package/server/https.js +121 -111
  262. package/server/https.js.map +1 -0
  263. package/server/index.d.ts +40 -0
  264. package/server/index.js +139 -147
  265. package/server/index.js.map +1 -0
  266. package/server/layout.d.ts +26 -0
  267. package/server/layout.js +59 -64
  268. package/server/layout.js.map +1 -0
  269. package/server/llmProxy.d.ts +3 -0
  270. package/server/llmProxy.js +137 -135
  271. package/server/llmProxy.js.map +1 -0
  272. package/server/logger.d.ts +38 -0
  273. package/server/logger.js +198 -211
  274. package/server/logger.js.map +1 -0
  275. package/server/mcp/analysis.d.ts +29 -0
  276. package/server/mcp/analysis.js +99 -120
  277. package/server/mcp/analysis.js.map +1 -0
  278. package/server/mcp/http.d.ts +9 -0
  279. package/server/mcp/http.js +128 -137
  280. package/server/mcp/http.js.map +1 -0
  281. package/server/mcp/runner.d.ts +68 -0
  282. package/server/mcp/runner.js +208 -218
  283. package/server/mcp/runner.js.map +1 -0
  284. package/server/mcp/server.d.ts +29 -0
  285. package/server/mcp/server.js +166 -179
  286. package/server/mcp/server.js.map +1 -0
  287. package/server/mcp/standalone.d.ts +1 -0
  288. package/server/mcp/standalone.js +8 -10
  289. package/server/mcp/standalone.js.map +1 -0
  290. package/server/mcp/tools.d.ts +2020 -0
  291. package/server/mcp/tools.js +523 -574
  292. package/server/mcp/tools.js.map +1 -0
  293. package/server/paths.d.ts +3 -0
  294. package/server/paths.js +17 -15
  295. package/server/paths.js.map +1 -0
  296. package/server/pidfile.d.ts +9 -0
  297. package/server/pidfile.js +43 -45
  298. package/server/pidfile.js.map +1 -0
  299. package/server/review.d.ts +5 -0
  300. package/server/review.js +35 -0
  301. package/server/review.js.map +1 -0
  302. package/server/router.d.ts +33 -0
  303. package/server/router.js +191 -197
  304. package/server/router.js.map +1 -0
  305. package/server/scanRequest.d.ts +3 -0
  306. package/server/scanRequest.js +36 -0
  307. package/server/scanRequest.js.map +1 -0
  308. package/server/searchIndex.d.ts +17 -0
  309. package/server/searchIndex.js +83 -80
  310. package/server/searchIndex.js.map +1 -0
  311. package/server/sessions.d.ts +14 -0
  312. package/server/sessions.js +62 -70
  313. package/server/sessions.js.map +1 -0
  314. package/server/startup.d.ts +118 -0
  315. package/server/startup.js +143 -148
  316. package/server/startup.js.map +1 -0
  317. package/server/static.d.ts +6 -0
  318. package/server/static.js +28 -31
  319. package/server/static.js.map +1 -0
  320. package/server/tools/install.d.ts +4 -0
  321. package/server/tools/install.js +176 -185
  322. package/server/tools/install.js.map +1 -0
  323. package/server/tools/parse.d.ts +8 -0
  324. package/server/tools/parse.js +112 -114
  325. package/server/tools/parse.js.map +1 -0
  326. package/server/tools/platform.d.ts +9 -0
  327. package/server/tools/platform.js +49 -47
  328. package/server/tools/platform.js.map +1 -0
  329. package/server/tools/registry.d.ts +137 -0
  330. package/server/tools/registry.js +173 -172
  331. package/server/tools/registry.js.map +1 -0
  332. package/server/tools/scan.d.ts +34 -0
  333. package/server/tools/scan.js +89 -98
  334. package/server/tools/scan.js.map +1 -0
  335. package/server/tools.d.ts +34 -0
  336. package/server/tools.js +94 -107
  337. package/server/tools.js.map +1 -0
  338. package/server/tunnel.d.ts +29 -0
  339. package/server/tunnel.js +61 -66
  340. package/server/tunnel.js.map +1 -0
  341. package/shared/analyzer/docs.d.ts +16 -0
  342. package/shared/analyzer/docs.js +99 -93
  343. package/shared/analyzer/docs.js.map +1 -0
  344. package/shared/analyzer/explainLocal.d.ts +4 -0
  345. package/shared/analyzer/explainLocal.js +122 -142
  346. package/shared/analyzer/explainLocal.js.map +1 -0
  347. package/shared/analyzer/github.d.ts +37 -0
  348. package/shared/analyzer/github.js +41 -43
  349. package/shared/analyzer/github.js.map +1 -0
  350. package/shared/analyzer/graph.d.ts +41 -0
  351. package/shared/analyzer/graph.js +393 -400
  352. package/shared/analyzer/graph.js.map +1 -0
  353. package/shared/analyzer/health.d.ts +33 -0
  354. package/shared/analyzer/health.js +197 -193
  355. package/shared/analyzer/health.js.map +1 -0
  356. package/shared/analyzer/history.d.ts +29 -0
  357. package/shared/analyzer/history.js +110 -111
  358. package/shared/analyzer/history.js.map +1 -0
  359. package/shared/analyzer/jsonConfig.d.ts +2 -0
  360. package/shared/analyzer/jsonConfig.js +61 -0
  361. package/shared/analyzer/jsonConfig.js.map +1 -0
  362. package/shared/analyzer/languages/csharp.d.ts +26 -0
  363. package/shared/analyzer/languages/csharp.js +83 -88
  364. package/shared/analyzer/languages/csharp.js.map +1 -0
  365. package/shared/analyzer/languages/generic.d.ts +23 -0
  366. package/shared/analyzer/languages/generic.js +94 -105
  367. package/shared/analyzer/languages/generic.js.map +1 -0
  368. package/shared/analyzer/languages/go.d.ts +24 -0
  369. package/shared/analyzer/languages/go.js +45 -54
  370. package/shared/analyzer/languages/go.js.map +1 -0
  371. package/shared/analyzer/languages/index.d.ts +6 -0
  372. package/shared/analyzer/languages/index.js +19 -20
  373. package/shared/analyzer/languages/index.js.map +1 -0
  374. package/shared/analyzer/languages/java.d.ts +20 -0
  375. package/shared/analyzer/languages/java.js +73 -78
  376. package/shared/analyzer/languages/java.js.map +1 -0
  377. package/shared/analyzer/languages/javascript.d.ts +12 -0
  378. package/shared/analyzer/languages/javascript.js +258 -209
  379. package/shared/analyzer/languages/javascript.js.map +1 -0
  380. package/shared/analyzer/languages/python.d.ts +23 -0
  381. package/shared/analyzer/languages/python.js +92 -100
  382. package/shared/analyzer/languages/python.js.map +1 -0
  383. package/shared/analyzer/languages/rust.d.ts +17 -0
  384. package/shared/analyzer/languages/rust.js +96 -98
  385. package/shared/analyzer/languages/rust.js.map +1 -0
  386. package/shared/analyzer/languages/typescript.d.ts +11 -0
  387. package/shared/analyzer/languages/typescript.js +117 -116
  388. package/shared/analyzer/languages/typescript.js.map +1 -0
  389. package/shared/analyzer/licenses.d.ts +18 -0
  390. package/shared/analyzer/licenses.js +130 -133
  391. package/shared/analyzer/licenses.js.map +1 -0
  392. package/shared/analyzer/metrics.d.ts +20 -0
  393. package/shared/analyzer/metrics.js +112 -104
  394. package/shared/analyzer/metrics.js.map +1 -0
  395. package/shared/analyzer/pathUtil.d.ts +10 -0
  396. package/shared/analyzer/pathUtil.js +29 -35
  397. package/shared/analyzer/pathUtil.js.map +1 -0
  398. package/shared/analyzer/patterns.d.ts +11 -0
  399. package/shared/analyzer/patterns.js +231 -253
  400. package/shared/analyzer/patterns.js.map +1 -0
  401. package/shared/analyzer/scan.d.ts +62 -0
  402. package/shared/analyzer/scan.js +451 -442
  403. package/shared/analyzer/scan.js.map +1 -0
  404. package/shared/analyzer/scanControl.d.ts +11 -0
  405. package/shared/analyzer/scanControl.js +62 -0
  406. package/shared/analyzer/scanControl.js.map +1 -0
  407. package/shared/analyzer/security.d.ts +18 -0
  408. package/shared/analyzer/security.js +159 -159
  409. package/shared/analyzer/security.js.map +1 -0
  410. package/shared/analyzer/services.d.ts +5 -0
  411. package/shared/analyzer/services.js +141 -138
  412. package/shared/analyzer/services.js.map +1 -0
  413. package/shared/analyzer/stack.d.ts +6 -0
  414. package/shared/analyzer/stack.js +134 -141
  415. package/shared/analyzer/stack.js.map +1 -0
  416. package/shared/analyzer/tour.d.ts +2 -0
  417. package/shared/analyzer/tour.js +45 -53
  418. package/shared/analyzer/tour.js.map +1 -0
  419. package/shared/analyzer/util.d.ts +8 -0
  420. package/shared/analyzer/util.js +130 -76
  421. package/shared/analyzer/util.js.map +1 -0
  422. package/shared/analyzer/workflows.d.ts +8 -0
  423. package/shared/analyzer/workflows.js +139 -134
  424. package/shared/analyzer/workflows.js.map +1 -0
  425. package/shared/contracts.d.ts +250 -0
  426. package/shared/contracts.js +2 -0
  427. package/shared/contracts.js.map +1 -0
  428. package/shared/diagram/aiFacts.d.ts +35 -0
  429. package/shared/diagram/aiFacts.js +102 -105
  430. package/shared/diagram/aiFacts.js.map +1 -0
  431. package/shared/diagram/aiMermaid.d.ts +5 -0
  432. package/shared/diagram/aiMermaid.js +46 -45
  433. package/shared/diagram/aiMermaid.js.map +1 -0
  434. package/shared/diagram/atlas.d.ts +16 -0
  435. package/shared/diagram/atlas.js +51 -60
  436. package/shared/diagram/atlas.js.map +1 -0
  437. package/shared/diagram/mermaid.d.ts +43 -0
  438. package/shared/diagram/mermaid.js +348 -370
  439. package/shared/diagram/mermaid.js.map +1 -0
  440. package/shared/review/contracts.d.ts +95 -0
  441. package/shared/review/contracts.js +2 -0
  442. package/shared/review/contracts.js.map +1 -0
  443. package/shared/review/review.d.ts +10 -0
  444. package/shared/review/review.js +184 -0
  445. package/shared/review/review.js.map +1 -0
  446. package/shared/search/query.d.ts +32 -0
  447. package/shared/search/query.js +395 -388
  448. package/shared/search/query.js.map +1 -0
@@ -1,7 +1,7 @@
1
+ import { eventElement } from '/js/dom.js';
1
2
  // The inspector: everything we know about the thing you clicked, written up
2
3
  // like margin notes. Explanations come from the static graph; the AI button
3
4
  // hands off to the LLM when one is configured.
4
-
5
5
  import { roleOf, scanIndex } from '/shared/analyzer/graph.js';
6
6
  import { explainFile, explainFolder, explainOverview } from '/shared/analyzer/explainLocal.js';
7
7
  import { baseName } from '/shared/analyzer/pathUtil.js';
@@ -9,527 +9,498 @@ import { languageLabel } from '/shared/analyzer/languages/index.js';
9
9
  import { escapeHtml } from './html.js';
10
10
  import { mdLite, mdInline } from './markdown.js';
11
11
  import { renderAnalysisPanel } from './analysisPanel.js';
12
-
13
12
  const el = {};
14
13
  ['inspectorEmpty', 'inspectorBody', 'inspTitle', 'inspRole', 'inspPath', 'inspStats', 'inspExplain', 'inspLists', 'aiExplainBtn', 'askInput', 'inspBack']
15
- .forEach((id) => (el[id] = document.getElementById(id)));
16
-
14
+ .forEach((id) => (el[id] = document.getElementById(id)));
17
15
  let askAIHandler = null;
18
16
  let clearFocusHandler = null;
19
17
  let inspHooks = {
20
- onOpenCode: () => {},
21
- onDeepDive: () => {},
22
- onToast: () => {},
18
+ onOpenCode: (..._args) => { },
19
+ onDeepDive: (..._args) => { },
20
+ onToast: (..._args) => { },
23
21
  };
24
-
25
22
  export function initInspector(options = {}) {
26
- const { onAskAI, onClearFocus, ...rest } = options;
27
- askAIHandler = onAskAI;
28
- clearFocusHandler = onClearFocus;
29
- inspHooks = { ...inspHooks, ...rest };
30
- el.aiExplainBtn.addEventListener('click', () => askAIHandler?.());
31
- el.inspBack.addEventListener('click', () => clearFocusHandler?.());
32
-
33
- el.inspectorBody.addEventListener('click', (e) => {
34
- const btn = e.target.closest('button[data-insp-action]');
35
- if (!btn) return;
36
- const action = btn.dataset.inspAction;
37
- const targetPath = btn.dataset.path || el.inspPath.textContent;
38
- if (action === 'code') {
39
- inspHooks.onOpenCode?.(targetPath);
40
- } else if (action === 'dive') {
41
- inspHooks.onDeepDive?.(targetPath);
42
- } else if (action === 'copy') {
43
- if (navigator.clipboard) {
44
- navigator.clipboard.writeText(targetPath)
45
- .then(() => inspHooks.onToast?.('Path copied to clipboard'))
46
- .catch(() => inspHooks.onToast?.('Failed to copy path'));
47
- }
48
- }
49
- });
23
+ const { onAskAI, onClearFocus, ...rest } = options;
24
+ askAIHandler = onAskAI;
25
+ clearFocusHandler = onClearFocus;
26
+ inspHooks = { ...inspHooks, ...rest };
27
+ el.aiExplainBtn.addEventListener('click', () => askAIHandler?.());
28
+ el.inspBack.addEventListener('click', () => clearFocusHandler?.());
29
+ el.inspectorBody.addEventListener('click', (e) => {
30
+ const btn = eventElement(e)?.closest('button[data-insp-action]');
31
+ if (!btn)
32
+ return;
33
+ const action = btn.dataset.inspAction;
34
+ const targetPath = btn.dataset.path || el.inspPath.textContent;
35
+ if (action === 'code') {
36
+ inspHooks.onOpenCode?.(targetPath);
37
+ }
38
+ else if (action === 'dive') {
39
+ inspHooks.onDeepDive?.(targetPath);
40
+ }
41
+ else if (action === 'copy') {
42
+ if (navigator.clipboard) {
43
+ navigator.clipboard.writeText(targetPath)
44
+ .then(() => inspHooks.onToast?.('Path copied to clipboard'))
45
+ .catch(() => inspHooks.onToast?.('Failed to copy path'));
46
+ }
47
+ }
48
+ });
50
49
  }
51
-
52
50
  // Show/hide the small "‹ back" control that returns to the view's own panel.
53
51
  function setBack(label) {
54
- if (label) {
55
- el.inspBack.hidden = false;
56
- el.inspBack.textContent = '‹ ' + label;
57
- } else {
58
- el.inspBack.hidden = true;
59
- }
52
+ if (label) {
53
+ el.inspBack.hidden = false;
54
+ el.inspBack.textContent = '‹ ' + label;
55
+ }
56
+ else {
57
+ el.inspBack.hidden = true;
58
+ }
60
59
  }
61
-
62
60
  export function clearInspector() {
63
- el.inspectorEmpty.hidden = false;
64
- el.inspectorBody.hidden = true;
61
+ el.inspectorEmpty.hidden = false;
62
+ el.inspectorBody.hidden = true;
65
63
  }
66
-
67
64
  export function showFile(path, state) {
68
- const { scan, facts, history, backLabel } = state;
69
- const file = scanIndex(scan).fileAt(path);
70
- const role = roleOf(path, facts);
71
- setBack(backLabel);
72
-
73
- el.inspTitle.textContent = baseName(path);
74
- el.inspRole.textContent = role;
75
- el.inspRole.className = 'role-chip is-' + role;
76
- el.inspPath.textContent = path;
77
- el.askInput.placeholder = `Ask about ${baseName(path)}…`;
78
-
79
- const fin = facts.fanIn[path] || 0;
80
- const fout = facts.fanOut[path] || 0;
81
- const hist = history?.byPath?.[path];
82
-
83
- const actionsHtml = `
65
+ const { scan, facts, history, backLabel } = state;
66
+ const file = scanIndex(scan).fileAt(path);
67
+ const role = roleOf(path, facts);
68
+ setBack(backLabel);
69
+ el.inspTitle.textContent = baseName(path);
70
+ el.inspRole.textContent = role;
71
+ el.inspRole.className = 'role-chip is-' + role;
72
+ el.inspPath.textContent = path;
73
+ el.askInput.placeholder = `Ask about ${baseName(path)}…`;
74
+ const fin = facts.fanIn[path] || 0;
75
+ const fout = facts.fanOut[path] || 0;
76
+ const hist = history?.byPath?.[path];
77
+ const actionsHtml = `
84
78
  <div class="insp-actions" style="display:flex; gap:6px; margin: 6px 0 10px;">
85
79
  <button class="btn btn-ghost btn-sm" data-insp-action="code" data-path="${escapeHtml(path)}" title="Open file in Code view">Code</button>
86
80
  <button class="btn btn-ghost btn-sm" data-insp-action="dive" data-path="${escapeHtml(path)}" title="Open Deep Dive flowchart">Deep Dive →</button>
87
81
  <button class="btn btn-ghost btn-sm" data-insp-action="copy" data-path="${escapeHtml(path)}" title="Copy relative path">Copy Path</button>
88
82
  </div>
89
83
  `;
90
-
91
- el.inspStats.innerHTML = [
92
- chip(`<b>${fin}</b> dependents`),
93
- chip(`<b>${fout}</b> imports`),
94
- hist ? chip(`<b>${hist.churn}</b> commit${hist.churn === 1 ? '' : 's'}`) : '',
95
- hist?.hotspot ? chip(`hotspot <b>${hist.hotspot}</b>`) : '',
96
- hist?.solo && hist.churn >= 2 ? chip(`<b>solo</b> author`) : '',
97
- file ? chip(`<b>${file.functions.length}</b> functions`) : '',
98
- file ? chip(`<b>${(file.size / 1024).toFixed(1)}</b> kb`) : '',
99
- file ? chip(langName(file.lang)) : '',
100
- file && file.cognitive !== undefined ? chip(`<b>${file.cognitive}</b> cognitive cx`) : '',
101
- file && file.maintainability !== undefined ? chip(`<b>${file.maintainability}</b> MI`) : '',
102
- facts.communities && facts.communities[path] ? chip(`community <b>${facts.communities[path]}</b>`) : '',
103
- ].join('');
104
-
105
- if (file && state && state.health) {
106
- const riskData = state.health.perFile?.find(x => x.path === path);
107
- if (riskData) {
108
- el.inspStats.innerHTML += ` <risk-badge score="${riskData.risk}" style="vertical-align: middle; margin-left: 8px; --risk-size: 32px;"></risk-badge>`;
84
+ el.inspStats.innerHTML = [
85
+ chip(`<b>${fin}</b> dependents`),
86
+ chip(`<b>${fout}</b> imports`),
87
+ hist ? chip(`<b>${hist.churn}</b> commit${hist.churn === 1 ? '' : 's'}`) : '',
88
+ hist?.hotspot ? chip(`hotspot <b>${hist.hotspot}</b>`) : '',
89
+ hist?.solo && hist.churn >= 2 ? chip(`<b>solo</b> author`) : '',
90
+ file ? chip(`<b>${file.functions.length}</b> functions`) : '',
91
+ file ? chip(`<b>${(file.size / 1024).toFixed(1)}</b> kb`) : '',
92
+ file ? chip(langName(file.lang)) : '',
93
+ file && file.cognitive !== undefined ? chip(`<b>${file.cognitive}</b> cognitive cx`) : '',
94
+ file && file.maintainability !== undefined ? chip(`<b>${file.maintainability}</b> MI`) : '',
95
+ facts.communities && facts.communities[path] ? chip(`community <b>${facts.communities[path]}</b>`) : '',
96
+ ].join('');
97
+ if (file && state && state.health) {
98
+ const riskData = state.health.perFile?.find(x => x.path === path);
99
+ if (riskData) {
100
+ el.inspStats.innerHTML += ` <risk-badge score="${riskData.risk}" style="vertical-align: middle; margin-left: 8px; --risk-size: 32px;"></risk-badge>`;
101
+ }
102
+ }
103
+ setExplain(actionsHtml + mdLite(explainFile(path, file, facts)), 'From the static analysis — no AI involved.');
104
+ el.inspLists.innerHTML = '';
105
+ addPathList('Imports', facts.importsOf[path] || [], scan);
106
+ addPathList('Imported by', facts.importers[path] || [], scan);
107
+ if (file?.exports.length) {
108
+ addList('Exports', file.exports.map((e) => ({ text: e.name, kind: e.kind })));
109
+ }
110
+ if (file?.functions.length) {
111
+ addList('Functions', file.functions.map((f) => ({ text: f.name + '()', kind: f.kind + ' · line ' + f.line })));
109
112
  }
110
- }
111
-
112
- setExplain(actionsHtml + mdLite(explainFile(path, file, facts)), 'From the static analysis — no AI involved.');
113
-
114
- el.inspLists.innerHTML = '';
115
- addPathList('Imports', facts.importsOf[path] || [], scan);
116
- addPathList('Imported by', facts.importers[path] || [], scan);
117
- if (file?.exports.length) {
118
- addList('Exports', file.exports.map((e) => ({ text: e.name, kind: e.kind })));
119
- }
120
- if (file?.functions.length) {
121
- addList('Functions', file.functions.map((f) => ({ text: f.name + '()', kind: f.kind + ' · line ' + f.line })));
122
- }
123
-
124
- el.inspectorEmpty.hidden = true;
125
- el.inspectorBody.hidden = false;
113
+ el.inspectorEmpty.hidden = true;
114
+ el.inspectorBody.hidden = false;
126
115
  }
127
-
128
116
  export function showFolder(folder, { scan, facts, backLabel }) {
129
- setBack(backLabel);
130
- el.inspTitle.textContent = (folder || '(repo root)') + '/';
131
- el.inspRole.textContent = 'folder';
132
- el.inspRole.className = 'role-chip is-test';
133
- el.inspPath.textContent = folder || '(the top of the repository)';
134
- el.askInput.placeholder = folder ? `Ask about ${folder}/…` : 'Ask anything about this repo…';
135
- const here = scanIndex(scan).filesIn(folder);
136
- const count = here.length;
137
- el.inspStats.innerHTML = chip(`<b>${count}</b> parsed files`);
138
- setExplain(mdLite(explainFolder(folder, scan, facts)), 'From the static analysis — no AI involved.');
139
- el.inspLists.innerHTML = '';
140
- addPathList('Files here', here.map((f) => f.path), scan);
141
- el.inspectorEmpty.hidden = true;
142
- el.inspectorBody.hidden = false;
117
+ setBack(backLabel);
118
+ el.inspTitle.textContent = (folder || '(repo root)') + '/';
119
+ el.inspRole.textContent = 'folder';
120
+ el.inspRole.className = 'role-chip is-test';
121
+ el.inspPath.textContent = folder || '(the top of the repository)';
122
+ el.askInput.placeholder = folder ? `Ask about ${folder}/…` : 'Ask anything about this repo…';
123
+ const here = scanIndex(scan).filesIn(folder);
124
+ const count = here.length;
125
+ el.inspStats.innerHTML = chip(`<b>${count}</b> parsed files`);
126
+ setExplain(mdLite(explainFolder(folder, scan, facts)), 'From the static analysis — no AI involved.');
127
+ el.inspLists.innerHTML = '';
128
+ addPathList('Files here', here.map((f) => f.path), scan);
129
+ el.inspectorEmpty.hidden = true;
130
+ el.inspectorBody.hidden = false;
143
131
  }
144
-
145
132
  export function showOverview({ scan, facts, manifest, history }) {
146
- setBack();
147
- el.inspTitle.textContent = scan.name;
148
- el.inspRole.textContent = 'repo';
149
- el.inspRole.className = 'role-chip is-entry';
150
- el.inspPath.textContent = scan.root;
151
- el.askInput.placeholder = 'Ask anything about this repo…';
152
- const conf = scan.stats.imports?.confidence;
153
- el.inspStats.innerHTML = [
154
- chip(`<b>${scan.stats.filesParsed}</b> files${scan.stats.truncated ? ' (partial)' : ''}`),
155
- chip(`<b>${scan.stats.edgeCount}</b> connections`),
156
- chip(`<b>${facts.entries.length}</b> entries`),
157
- chip(`<b>${facts.hubs.length}</b> hubs`),
158
- history?.available ? chip(`<b>${history.commitCount}</b> commits`) : '',
159
- // How much of the import graph we actually placed. Worth a permanent slot:
160
- // every other number on this page is downstream of it.
161
- typeof conf === 'number' ? chip(`<b>${conf}%</b> of imports placed`) : '',
162
- ].join('');
163
- setExplain(mdLite(explainOverview(scan, facts, manifest)), 'From the static analysis — no AI involved.');
164
- el.inspLists.innerHTML = '';
165
- addPathList('Entry points', facts.entries, scan);
166
- addList('Load-bearing files', facts.hubs.slice(0, 8).map((h) => ({ text: h.path, kind: h.fanIn + ' dependents', path: h.path })), scan);
167
- if (history?.available) {
168
- if (history.perFile?.length) {
169
- const topHotspots = history.perFile.filter((f) => f.hotspot > 10).slice(0, 6);
170
- if (topHotspots.length) {
171
- addList('Hotspots (churn × complexity)', topHotspots.map((f) => ({ text: f.path, kind: `hotspot ${f.hotspot} · ${f.churn} commits`, path: f.path })));
172
- }
133
+ setBack();
134
+ el.inspTitle.textContent = scan.name;
135
+ el.inspRole.textContent = 'repo';
136
+ el.inspRole.className = 'role-chip is-entry';
137
+ el.inspPath.textContent = scan.root;
138
+ el.askInput.placeholder = 'Ask anything about this repo…';
139
+ const conf = scan.stats.imports?.confidence;
140
+ el.inspStats.innerHTML = [
141
+ chip(`<b>${scan.stats.filesParsed}</b> files${scan.stats.truncated ? ' (partial)' : ''}`),
142
+ chip(`<b>${scan.stats.edgeCount}</b> connections`),
143
+ chip(`<b>${facts.entries.length}</b> entries`),
144
+ chip(`<b>${facts.hubs.length}</b> hubs`),
145
+ history?.available ? chip(`<b>${history.commitCount}</b> commits`) : '',
146
+ // How much of the import graph we actually placed. Worth a permanent slot:
147
+ // every other number on this page is downstream of it.
148
+ typeof conf === 'number' ? chip(`<b>${conf}%</b> of imports placed`) : '',
149
+ ].join('');
150
+ setExplain(mdLite(explainOverview(scan, facts, manifest)), 'From the static analysis — no AI involved.');
151
+ el.inspLists.innerHTML = '';
152
+ addPathList('Entry points', facts.entries, scan);
153
+ addList('Load-bearing files', facts.hubs.slice(0, 8).map((h) => ({ text: h.path, kind: h.fanIn + ' dependents', path: h.path })), scan);
154
+ if (history?.available) {
155
+ if (history.perFile?.length) {
156
+ const topHotspots = history.perFile.filter((f) => f.hotspot > 10).slice(0, 6);
157
+ if (topHotspots.length) {
158
+ addList('Hotspots (churn × complexity)', topHotspots.map((f) => ({ text: f.path, kind: `hotspot ${f.hotspot} · ${f.churn} commits`, path: f.path })));
159
+ }
160
+ }
161
+ if (history.coChanged?.length) {
162
+ addList('Co-changed pairs', history.coChanged.slice(0, 5).map((p) => ({ text: `${p.a} + ${p.b}`, kind: `${p.count} joint commits` })));
163
+ }
173
164
  }
174
- if (history.coChanged?.length) {
175
- addList('Co-changed pairs', history.coChanged.slice(0, 5).map((p) => ({ text: `${p.a} + ${p.b}`, kind: `${p.count} joint commits` })));
165
+ if (scan.externals.length) {
166
+ addList('Outside packages', scan.externals.slice(0, 10).map((x) => ({ text: x.name, kind: 'used by ' + x.usedBy.length })));
176
167
  }
177
- }
178
- if (scan.externals.length) {
179
- addList('Outside packages', scan.externals.slice(0, 10).map((x) => ({ text: x.name, kind: 'used by ' + x.usedBy.length })));
180
- }
181
- el.inspectorEmpty.hidden = true;
182
- el.inspectorBody.hidden = false;
168
+ el.inspectorEmpty.hidden = true;
169
+ el.inspectorBody.hidden = false;
183
170
  }
184
-
185
171
  // The Patterns view panel: written observations plus the folder-traffic grid.
186
172
  export function showPatterns({ scan, patterns }) {
187
- setBack();
188
- const { layersInfo, findings, coupling } = patterns;
189
- el.inspTitle.textContent = 'The shape of it';
190
- el.inspRole.textContent = 'patterns';
191
- el.inspRole.className = 'role-chip is-entry';
192
- el.inspPath.textContent = `${scan.name} — ${layersInfo.layers.length} layers deep from the door`;
193
- el.askInput.placeholder = 'Ask about the architecture…';
194
- el.inspStats.innerHTML = [
195
- chip(`<b>${layersInfo.layers.length}</b> layers`),
196
- chip(`<b>${layersInfo.unreachable.length}</b> off the path`),
197
- chip(`<b>${findings.length}</b> observations`),
198
- ].join('');
199
-
200
- el.inspExplain.innerHTML =
201
- findings.map((f) => `
173
+ setBack();
174
+ const { layersInfo, findings, coupling } = patterns;
175
+ el.inspTitle.textContent = 'The shape of it';
176
+ el.inspRole.textContent = 'patterns';
177
+ el.inspRole.className = 'role-chip is-entry';
178
+ el.inspPath.textContent = `${scan.name} — ${layersInfo.layers.length} layers deep from the door`;
179
+ el.askInput.placeholder = 'Ask about the architecture…';
180
+ el.inspStats.innerHTML = [
181
+ chip(`<b>${layersInfo.layers.length}</b> layers`),
182
+ chip(`<b>${layersInfo.unreachable.length}</b> off the path`),
183
+ chip(`<b>${findings.length}</b> observations`),
184
+ ].join('');
185
+ el.inspExplain.innerHTML =
186
+ findings.map((f) => `
202
187
  <div class="finding is-${f.tone}">
203
188
  <h4>${escapeHtml(f.title)}</h4>
204
189
  <p>${mdInline(f.detail)}</p>
205
190
  ${f.paths && f.paths.length ? `<div style="margin-top:6px;display:flex;flex-wrap:wrap;gap:4px;">${f.paths.slice(0, 4).map((p) => `<button class="role-chip" data-goto="${escapeHtml(p)}" style="cursor:pointer;font-size:11px;">${escapeHtml(p)}</button>`).join('')}${f.paths.length > 4 ? `<span style="font-size:11px;color:var(--faint);align-self:center;">+${f.paths.length - 4} more</span>` : ''}</div>` : ''}
206
191
  </div>`).join('') +
207
- '<p class="explain-src">Read straight from the import graph — no AI involved.</p>';
208
-
209
- el.inspLists.innerHTML = '';
210
- if (coupling.folders.length) {
211
- const h = document.createElement('h3');
212
- h.className = 'inspector-h';
213
- h.textContent = 'Folder traffic';
214
- el.inspLists.appendChild(h);
215
- el.inspLists.appendChild(heatGrid(coupling));
216
- const note = document.createElement('p');
217
- note.className = 'explain-src';
218
- note.textContent = 'Rows pull in columns; darker means more.';
219
- el.inspLists.appendChild(note);
220
- }
221
- el.inspectorEmpty.hidden = true;
222
- el.inspectorBody.hidden = false;
192
+ '<p class="explain-src">Read straight from the import graph — no AI involved.</p>';
193
+ el.inspLists.innerHTML = '';
194
+ if (coupling.folders.length) {
195
+ const h = document.createElement('h3');
196
+ h.className = 'inspector-h';
197
+ h.textContent = 'Folder traffic';
198
+ el.inspLists.appendChild(h);
199
+ el.inspLists.appendChild(heatGrid(coupling));
200
+ const note = document.createElement('p');
201
+ note.className = 'explain-src';
202
+ note.textContent = 'Rows pull in columns; darker means more.';
203
+ el.inspLists.appendChild(note);
204
+ }
205
+ el.inspectorEmpty.hidden = true;
206
+ el.inspectorBody.hidden = false;
223
207
  }
224
-
225
208
  // The Health view panel: the repo's grade and score with an itemized
226
209
  // breakdown, a legend for the metrics, and the riskiest files — clickable.
227
210
  export function showHealth({ scan, health }) {
228
- setBack();
229
- const t = health.totals;
230
- el.inspTitle.textContent = 'Health';
231
- el.inspRole.textContent = 'grade ' + health.grade;
232
- el.inspRole.className = 'role-chip is-' + (health.score >= 70 ? 'entry' : health.score >= 60 ? 'hub' : 'cycle');
233
- el.inspPath.textContent = `${scan.name} — ${health.score}/100`;
234
- el.askInput.placeholder = 'Ask about the riskiest files…';
235
- el.inspStats.innerHTML = [
236
- chip(`<b>${health.score}</b> / 100`),
237
- chip(`<b>${t.crit}</b> critical`),
238
- chip(`<b>${t.high}</b> high risk`),
239
- chip(`avg cx <b>${t.avgCx}</b>`),
240
- ].join('');
241
-
242
- const top = health.perFile[0];
243
- let note = `<b>${escapeHtml(scan.name)}</b> grades <b>${health.grade}</b> (${health.score}/100).`;
244
- if (health.score >= 90) note += ' Clean dependency shape — keep it that way.';
245
- if (t.crit) note += ` ${t.crit} file${t.crit === 1 ? ' sits' : 's sit'} in the critical band — change those with care.`;
246
- else if (t.high) note += ` ${t.high} file${t.high === 1 ? '' : 's'} in the high band.`;
247
- if (top) note += ` The single riskiest file is <b>${escapeHtml(top.name)}</b> (risk ${top.risk}${top.inCycle ? ', in a cycle' : ''}, ${top.fanIn} dependents, complexity ${top.complexity}).`;
248
-
249
- const breakdownHtml = health.breakdown.length
250
- ? `<div class="score-bd"><h4>Why ${health.score}/100</h4><ul>`
251
- + health.breakdown.map((b) => `<li><span>${escapeHtml(b.label)}</span><b>${b.points}</b></li>`).join('')
252
- + '</ul></div>'
253
- : '<p class="explain-src">A perfect run — nothing was deducted.</p>';
254
-
255
- const legendHtml = `<div class="score-bd"><h4>How the risk score works</h4><ul class="legend">`
256
- + `<li><b>centrality</b> — how much of the repo flows through a file (PageRank)</li>`
257
- + (health.blastExact === false
258
- ? `<li><b>blast radius</b> — direct dependents only: this repo is over 2,500 files, where the transitive walk is too slow to run</li>`
259
- : `<li><b>blast radius</b> — how many files break if it does</li>`)
260
- + `<li><b>complexity</b> — decision points; how hard it is to change safely</li>`
261
- + `<li><b>cycles</b> — circular dependencies, the sharpest edge</li>`
262
- + '</ul></div>';
263
-
264
- el.inspExplain.innerHTML = `<p>${note}</p>` + breakdownHtml + legendHtml
265
- + '<p class="explain-src">Scored from centrality, blast radius, complexity and cycles — no AI involved.</p>';
266
- el.inspLists.innerHTML = '';
267
- addList('Riskiest files', health.perFile.map((f) => ({ text: f.name, kind: `risk ${f.risk} · blast ${f.blast} · cx ${f.complexity}`, path: f.path })), scan);
268
- el.inspectorEmpty.hidden = true;
269
- el.inspectorBody.hidden = false;
211
+ setBack();
212
+ const t = health.totals;
213
+ el.inspTitle.textContent = 'Health';
214
+ el.inspRole.textContent = 'grade ' + health.grade;
215
+ el.inspRole.className = 'role-chip is-' + (health.score >= 70 ? 'entry' : health.score >= 60 ? 'hub' : 'cycle');
216
+ el.inspPath.textContent = `${scan.name} — ${health.score}/100`;
217
+ el.askInput.placeholder = 'Ask about the riskiest files…';
218
+ el.inspStats.innerHTML = [
219
+ chip(`<b>${health.score}</b> / 100`),
220
+ chip(`<b>${t.crit}</b> critical`),
221
+ chip(`<b>${t.high}</b> high risk`),
222
+ chip(`avg cx <b>${t.avgCx}</b>`),
223
+ ].join('');
224
+ const top = health.perFile[0];
225
+ let note = `<b>${escapeHtml(scan.name)}</b> grades <b>${health.grade}</b> (${health.score}/100).`;
226
+ if (health.score >= 90)
227
+ note += ' Clean dependency shape — keep it that way.';
228
+ if (t.crit)
229
+ note += ` ${t.crit} file${t.crit === 1 ? ' sits' : 's sit'} in the critical band — change those with care.`;
230
+ else if (t.high)
231
+ note += ` ${t.high} file${t.high === 1 ? '' : 's'} in the high band.`;
232
+ if (top)
233
+ note += ` The single riskiest file is <b>${escapeHtml(top.name)}</b> (risk ${top.risk}${top.inCycle ? ', in a cycle' : ''}, ${top.fanIn} dependents, complexity ${top.complexity}).`;
234
+ const breakdownHtml = health.breakdown.length
235
+ ? `<div class="score-bd"><h4>Why ${health.score}/100</h4><ul>`
236
+ + health.breakdown.map((b) => `<li><span>${escapeHtml(b.label)}</span><b>${b.points}</b></li>`).join('')
237
+ + '</ul></div>'
238
+ : '<p class="explain-src">A perfect run — nothing was deducted.</p>';
239
+ const legendHtml = `<div class="score-bd"><h4>How the risk score works</h4><ul class="legend">`
240
+ + `<li><b>centrality</b> — how much of the repo flows through a file (PageRank)</li>`
241
+ + (health.blastExact === false
242
+ ? `<li><b>blast radius</b> — direct dependents only: this repo is over 2,500 files, where the transitive walk is too slow to run</li>`
243
+ : `<li><b>blast radius</b> — how many files break if it does</li>`)
244
+ + `<li><b>complexity</b> — decision points; how hard it is to change safely</li>`
245
+ + `<li><b>cycles</b> — circular dependencies, the sharpest edge</li>`
246
+ + '</ul></div>';
247
+ el.inspExplain.innerHTML = `<p>${note}</p>` + breakdownHtml + legendHtml
248
+ + '<p class="explain-src">Scored from centrality, blast radius, complexity and cycles — no AI involved.</p>';
249
+ el.inspLists.innerHTML = '';
250
+ addList('Riskiest files', health.perFile.map((f) => ({ text: f.name, kind: `risk ${f.risk} · blast ${f.blast} · cx ${f.complexity}`, path: f.path })), scan);
251
+ el.inspectorEmpty.hidden = true;
252
+ el.inspectorBody.hidden = false;
270
253
  }
271
-
272
254
  // The Security view panel: severity counts, a written summary, and the
273
255
  // findings list — each one clickable into its file's deep-dive.
274
256
  export function showSecurity({ scan, security }) {
275
- setBack();
276
- const c = security.counts;
277
- el.inspTitle.textContent = 'Security';
278
- el.inspRole.textContent = 'grade ' + security.grade;
279
- el.inspRole.className = 'role-chip is-' + (security.score >= 75 ? 'entry' : security.score >= 60 ? 'hub' : 'cycle');
280
- el.inspPath.textContent = `${scan.name} — ${security.score}/100 · ${security.total} findings`;
281
- el.askInput.placeholder = 'Ask about the security findings…';
282
- el.inspStats.innerHTML = [
283
- chip(`<b>${c.critical}</b> critical`),
284
- chip(`<b>${c.high}</b> high`),
285
- chip(`<b>${c.medium}</b> medium`),
286
- chip(`<b>${c.low + c.info}</b> low/info`),
287
- ].join('');
288
-
289
- let note = `<b>${escapeHtml(scan.name)}</b> grades <b>${security.grade}</b> for security (${security.score}/100) — ${security.total} finding${security.total === 1 ? '' : 's'} across ${security.files.length} file${security.files.length === 1 ? '' : 's'}.`;
290
- if (!security.total) note += ' The rule engine found nothing to flag — a clean bill.';
291
- else {
292
- const worst = security.files[0];
293
- note += ` The loudest file is <b>${escapeHtml(worst.name)}</b> (${worst.count} finding${worst.count === 1 ? '' : 's'}, worst: ${worst.worst}).`;
294
- if (c.critical) note += ` ${c.critical} critical — fix those first.`;
295
- else if (c.high) note += ` ${c.high} high — worth a pass soon.`;
296
- }
297
-
298
- const sevLegend = `<div class="score-bd"><h4>What the severities mean</h4><ul class="legend">`
299
- + `<li><b class="sev-critical">critical</b> — secrets &amp; keys committed; fix now</li>`
300
- + `<li><b class="sev-high">high</b> — injection &amp; unsafe deserialization; sanitize, parameterize</li>`
301
- + `<li><b class="sev-medium">medium</b> — XSS sinks, weak crypto, open redirects, swallowed errors</li>`
302
- + `<li><b class="sev-low">low / info</b> — config, debug leftovers, style — tidy-up</li>`
303
- + '</ul></div>';
304
-
305
- const catEntries = Object.entries(security.byCat).sort((a, b) => b[1] - a[1]);
306
- const catHtml = catEntries.length
307
- ? `<div class="score-bd"><h4>By category</h4><ul>`
308
- + catEntries.map(([cat, n]) => `<li><span>${escapeHtml(cat)}</span><b>${n}</b></li>`).join('')
309
- + '</ul></div>'
310
- : '';
311
-
312
- // Attribution: the security story should say which engine told it. Built-in
313
- // findings carry no `source`; external ones carry the tool's name.
314
- const externalCount = security.files.reduce((n, f) => n + f.findings.filter((x) => x.source === 'external').length, 0);
315
- const sourceLine = externalCount
316
- ? `From the built-in rule engine plus ${externalCount} finding${externalCount === 1 ? '' : 's'} from external engines (see the list below).`
317
- : 'From the built-in rule engine — no external scanner, no AI.';
318
-
319
- el.inspExplain.innerHTML = `<p>${note}</p>` + catHtml + sevLegend
320
- + `<p class="explain-src">${sourceLine}</p>`;
321
-
322
- // The deep-analysis panel: which engines are installed, and the button that
323
- // runs them. Rendered on every pass so a finished run re-paints in place.
324
- renderAnalysisPanel();
325
-
326
- el.inspLists.innerHTML = '';
327
- const rows = security.files.flatMap((f) => f.findings.slice(0, 6).map((x) => ({ text: `${f.name}:${x.line} — ${x.message}`, kind: x.severity, path: f.path })));
328
- addList('Findings', rows, scan);
329
- el.inspectorEmpty.hidden = true;
330
- el.inspectorBody.hidden = false;
257
+ setBack();
258
+ const c = security.counts;
259
+ el.inspTitle.textContent = 'Security';
260
+ el.inspRole.textContent = 'grade ' + security.grade;
261
+ el.inspRole.className = 'role-chip is-' + (security.score >= 75 ? 'entry' : security.score >= 60 ? 'hub' : 'cycle');
262
+ el.inspPath.textContent = `${scan.name} — ${security.score}/100 · ${security.total} findings`;
263
+ el.askInput.placeholder = 'Ask about the security findings…';
264
+ el.inspStats.innerHTML = [
265
+ chip(`<b>${c.critical}</b> critical`),
266
+ chip(`<b>${c.high}</b> high`),
267
+ chip(`<b>${c.medium}</b> medium`),
268
+ chip(`<b>${c.low + c.info}</b> low/info`),
269
+ ].join('');
270
+ let note = `<b>${escapeHtml(scan.name)}</b> grades <b>${security.grade}</b> for security (${security.score}/100) — ${security.total} finding${security.total === 1 ? '' : 's'} across ${security.files.length} file${security.files.length === 1 ? '' : 's'}.`;
271
+ if (!security.total)
272
+ note += ' The rule engine found nothing to flag — a clean bill.';
273
+ else {
274
+ const worst = security.files[0];
275
+ note += ` The loudest file is <b>${escapeHtml(worst.name)}</b> (${worst.count} finding${worst.count === 1 ? '' : 's'}, worst: ${worst.worst}).`;
276
+ if (c.critical)
277
+ note += ` ${c.critical} critical — fix those first.`;
278
+ else if (c.high)
279
+ note += ` ${c.high} high — worth a pass soon.`;
280
+ }
281
+ const sevLegend = `<div class="score-bd"><h4>What the severities mean</h4><ul class="legend">`
282
+ + `<li><b class="sev-critical">critical</b> — secrets &amp; keys committed; fix now</li>`
283
+ + `<li><b class="sev-high">high</b> — injection &amp; unsafe deserialization; sanitize, parameterize</li>`
284
+ + `<li><b class="sev-medium">medium</b> — XSS sinks, weak crypto, open redirects, swallowed errors</li>`
285
+ + `<li><b class="sev-low">low / info</b> — config, debug leftovers, style — tidy-up</li>`
286
+ + '</ul></div>';
287
+ const catEntries = Object.entries(security.byCat).sort((a, b) => Number(b[1]) - Number(a[1]));
288
+ const catHtml = catEntries.length
289
+ ? `<div class="score-bd"><h4>By category</h4><ul>`
290
+ + catEntries.map(([cat, n]) => `<li><span>${escapeHtml(cat)}</span><b>${n}</b></li>`).join('')
291
+ + '</ul></div>'
292
+ : '';
293
+ // Attribution: the security story should say which engine told it. Built-in
294
+ // findings carry no `source`; external ones carry the tool's name.
295
+ const externalCount = security.files.reduce((n, f) => n + f.findings.filter((x) => x.source === 'external').length, 0);
296
+ const sourceLine = externalCount
297
+ ? `From the built-in rule engine plus ${externalCount} finding${externalCount === 1 ? '' : 's'} from external engines (see the list below).`
298
+ : 'From the built-in rule engine — no external scanner, no AI.';
299
+ el.inspExplain.innerHTML = `<p>${note}</p>` + catHtml + sevLegend
300
+ + `<p class="explain-src">${sourceLine}</p>`;
301
+ // The deep-analysis panel: which engines are installed, and the button that
302
+ // runs them. Rendered on every pass so a finished run re-paints in place.
303
+ renderAnalysisPanel();
304
+ el.inspLists.innerHTML = '';
305
+ const rows = security.files.flatMap((f) => f.findings.slice(0, 6).map((x) => ({ text: `${f.name}:${x.line} — ${x.message}`, kind: x.severity, path: f.path })));
306
+ addList('Findings', rows, scan);
307
+ el.inspectorEmpty.hidden = true;
308
+ el.inspectorBody.hidden = false;
331
309
  }
332
-
333
310
  // The History view panel: what the git log says — churn, authorship, the
334
311
  // hotspot cross-tab — with the top of each list clickable into the file's
335
312
  // deep-dive. When there is no history it says why rather than showing zeros.
336
313
  export function showHistory({ scan, history }) {
337
- setBack();
338
- el.inspTitle.textContent = 'History';
339
- if (!history?.available) {
340
- el.inspRole.textContent = 'unavailable';
341
- el.inspRole.className = 'role-chip is-test';
342
- el.inspPath.textContent = scan.name;
343
- el.askInput.placeholder = 'Ask anything about this repo…';
344
- el.inspStats.innerHTML = '';
345
- el.inspExplain.innerHTML =
346
- `<p>${escapeHtml(history?.reason || 'No history available.')}</p>`
347
- + '<p class="explain-src">History needs a checkout with commits — a git URL clone has one; a folder picked in the browser or a plain directory may not.</p>';
314
+ setBack();
315
+ el.inspTitle.textContent = 'History';
316
+ if (!history?.available) {
317
+ el.inspRole.textContent = 'unavailable';
318
+ el.inspRole.className = 'role-chip is-test';
319
+ el.inspPath.textContent = scan.name;
320
+ el.askInput.placeholder = 'Ask anything about this repo…';
321
+ el.inspStats.innerHTML = '';
322
+ el.inspExplain.innerHTML =
323
+ `<p>${escapeHtml(history?.reason || 'No history available.')}</p>`
324
+ + '<p class="explain-src">History needs a checkout with commits — a git URL clone has one; a folder picked in the browser or a plain directory may not.</p>';
325
+ el.inspLists.innerHTML = '';
326
+ el.inspectorEmpty.hidden = true;
327
+ el.inspectorBody.hidden = false;
328
+ return;
329
+ }
330
+ const hotspots = history.perFile.filter((f) => f.hotspot > 0);
331
+ const top = hotspots[0];
332
+ el.inspRole.textContent = history.truncated
333
+ ? `${history.commitCount} of ${history.totalCommits} commits`
334
+ : `${history.commitCount} commits`;
335
+ el.inspRole.className = 'role-chip is-entry';
336
+ el.inspPath.textContent = `${scan.name} — ${span(history.firstCommitAt, history.lastCommitAt)}`;
337
+ el.askInput.placeholder = 'Ask about the hotspots…';
338
+ el.inspStats.innerHTML = [
339
+ chip(`<b>${history.authors.length}</b> ${history.authors.length === 1 ? 'author' : 'authors'}`),
340
+ chip(`<b>${hotspots.length}</b> ${hotspots.length === 1 ? 'hotspot' : 'hotspots'}`),
341
+ history.soloFiles ? chip(`<b>${history.soloFiles}</b> solo-owned`) : '',
342
+ history.pathsGone ? chip(`<b>${history.pathsGone}</b> files gone`) : '',
343
+ ].join('');
344
+ let note = `<b>${escapeHtml(scan.name)}</b> shows ${history.commitCount} commits by ${history.authors.length} ${history.authors.length === 1 ? 'person' : 'people'}, ${span(history.firstCommitAt, history.lastCommitAt)}.`;
345
+ if (top) {
346
+ note += ` The sharpest hotspot is <b>${escapeHtml(top.name)}</b> — touched ${top.churn} ${top.churn === 1 ? 'time' : 'times'} at complexity ${top.complexity}`;
347
+ note += top.solo ? ', always by the same person.' : ` by ${top.authors} ${top.authors === 1 ? 'person' : 'people'}.`;
348
+ }
349
+ else {
350
+ note += ' Nothing stands out — no file is both complex and frequently changed.';
351
+ }
352
+ if (history.truncated)
353
+ note += ` The window was capped at ${history.commitCount} of ${history.totalCommits} total commits.`;
354
+ const legendHtml = `<div class="score-bd"><h4>How the hotspot score works</h4><ul class="legend">`
355
+ + `<li><b>churn</b> — how many commits have touched the file</li>`
356
+ + `<li><b>complexity</b> — decision points, the same number Health uses</li>`
357
+ + `<li><b>hotspot</b> — both at once, each against the repo's own worst: where change and difficulty meet</li>`
358
+ + `<li><b>solo</b> — only one person has ever touched it; knowledge leaves with them</li>`
359
+ + '</ul></div>';
360
+ el.inspExplain.innerHTML = `<p>${note}</p>` + legendHtml
361
+ + '<p class="explain-src">From the git log — authors keyed by email, no AI involved.</p>';
348
362
  el.inspLists.innerHTML = '';
363
+ addList('Hotspots', hotspots.map((f) => ({
364
+ text: f.name,
365
+ kind: `hotspot ${f.hotspot} · ${f.churn} commits · cx ${f.complexity}${f.solo ? ' · solo' : ''}`,
366
+ path: f.path,
367
+ })), scan);
368
+ if (history.coChanged?.length) {
369
+ addList('Change together, unconnected', history.coChanged.map((p) => ({
370
+ text: p.a.split('/').pop() + ' + ' + p.b.split('/').pop(),
371
+ kind: p.count + '× together',
372
+ path: p.a,
373
+ title: p.a + ' ↔ ' + p.b,
374
+ })), scan);
375
+ }
349
376
  el.inspectorEmpty.hidden = true;
350
377
  el.inspectorBody.hidden = false;
351
- return;
352
- }
353
-
354
- const hotspots = history.perFile.filter((f) => f.hotspot > 0);
355
- const top = hotspots[0];
356
- el.inspRole.textContent = history.truncated
357
- ? `${history.commitCount} of ${history.totalCommits} commits`
358
- : `${history.commitCount} commits`;
359
- el.inspRole.className = 'role-chip is-entry';
360
- el.inspPath.textContent = `${scan.name} — ${span(history.firstCommitAt, history.lastCommitAt)}`;
361
- el.askInput.placeholder = 'Ask about the hotspots…';
362
- el.inspStats.innerHTML = [
363
- chip(`<b>${history.authors.length}</b> ${history.authors.length === 1 ? 'author' : 'authors'}`),
364
- chip(`<b>${hotspots.length}</b> ${hotspots.length === 1 ? 'hotspot' : 'hotspots'}`),
365
- history.soloFiles ? chip(`<b>${history.soloFiles}</b> solo-owned`) : '',
366
- history.pathsGone ? chip(`<b>${history.pathsGone}</b> files gone`) : '',
367
- ].join('');
368
-
369
- let note = `<b>${escapeHtml(scan.name)}</b> shows ${history.commitCount} commits by ${history.authors.length} ${history.authors.length === 1 ? 'person' : 'people'}, ${span(history.firstCommitAt, history.lastCommitAt)}.`;
370
- if (top) {
371
- note += ` The sharpest hotspot is <b>${escapeHtml(top.name)}</b> — touched ${top.churn} ${top.churn === 1 ? 'time' : 'times'} at complexity ${top.complexity}`;
372
- note += top.solo ? ', always by the same person.' : ` by ${top.authors} ${top.authors === 1 ? 'person' : 'people'}.`;
373
- } else {
374
- note += ' Nothing stands out — no file is both complex and frequently changed.';
375
- }
376
- if (history.truncated) note += ` The window was capped at ${history.commitCount} of ${history.totalCommits} total commits.`;
377
-
378
- const legendHtml = `<div class="score-bd"><h4>How the hotspot score works</h4><ul class="legend">`
379
- + `<li><b>churn</b> — how many commits have touched the file</li>`
380
- + `<li><b>complexity</b> — decision points, the same number Health uses</li>`
381
- + `<li><b>hotspot</b> — both at once, each against the repo's own worst: where change and difficulty meet</li>`
382
- + `<li><b>solo</b> — only one person has ever touched it; knowledge leaves with them</li>`
383
- + '</ul></div>';
384
-
385
- el.inspExplain.innerHTML = `<p>${note}</p>` + legendHtml
386
- + '<p class="explain-src">From the git log — authors keyed by email, no AI involved.</p>';
387
- el.inspLists.innerHTML = '';
388
- addList('Hotspots', hotspots.map((f) => ({
389
- text: f.name,
390
- kind: `hotspot ${f.hotspot} · ${f.churn} commits · cx ${f.complexity}${f.solo ? ' · solo' : ''}`,
391
- path: f.path,
392
- })), scan);
393
- if (history.coChanged?.length) {
394
- addList('Change together, unconnected', history.coChanged.map((p) => ({
395
- text: p.a.split('/').pop() + ' + ' + p.b.split('/').pop(),
396
- kind: p.count + '× together',
397
- path: p.a,
398
- title: p.a + ' ↔ ' + p.b,
399
- })), scan);
400
- }
401
- el.inspectorEmpty.hidden = true;
402
- el.inspectorBody.hidden = false;
403
378
  }
404
-
405
379
  function span(firstIso, lastIso) {
406
- const fmt = (iso) => {
407
- if (!iso) return '?';
408
- const d = new Date(iso);
409
- if (Number.isNaN(d.getTime())) return iso.slice(0, 10);
410
- return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
411
- };
412
- return fmt(firstIso) + ' → ' + fmt(lastIso);
380
+ const fmt = (iso) => {
381
+ if (!iso)
382
+ return '?';
383
+ const d = new Date(iso);
384
+ if (Number.isNaN(d.getTime()))
385
+ return iso.slice(0, 10);
386
+ return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
387
+ };
388
+ return fmt(firstIso) + ' → ' + fmt(lastIso);
413
389
  }
414
-
415
390
  function heatGrid(coupling) {
416
- const { folders, counts, max } = coupling;
417
- const grid = document.createElement('div');
418
- grid.className = 'heat';
419
- grid.style.gridTemplateColumns = `64px repeat(${folders.length}, 1fr)`;
420
-
421
- grid.appendChild(document.createElement('span'));
422
- for (const c of folders) {
423
- const label = document.createElement('span');
424
- label.className = 'heat-col-label';
425
- label.textContent = shortFolder(c);
426
- label.title = c;
427
- grid.appendChild(label);
428
- }
429
- for (const r of folders) {
430
- const label = document.createElement('span');
431
- label.className = 'heat-label';
432
- label.textContent = shortFolder(r);
433
- label.title = r;
434
- grid.appendChild(label);
391
+ const { folders, counts, max } = coupling;
392
+ const grid = document.createElement('div');
393
+ grid.className = 'heat';
394
+ grid.style.gridTemplateColumns = `64px repeat(${folders.length}, 1fr)`;
395
+ grid.appendChild(document.createElement('span'));
435
396
  for (const c of folders) {
436
- const cell = document.createElement('span');
437
- cell.className = 'heat-cell';
438
- const n = counts.get(r + '->' + c) || 0;
439
- if (n > 0) {
440
- const pct = Math.max(14, Math.round((n / max) * 86));
441
- cell.style.background = `color-mix(in srgb, var(--accent) ${pct}%, transparent)`;
442
- cell.textContent = n;
443
- cell.title = `${r} → ${c}: ${n} ${n === 1 ? 'import' : 'imports'}`;
444
- }
445
- grid.appendChild(cell);
397
+ const label = document.createElement('span');
398
+ label.className = 'heat-col-label';
399
+ label.textContent = shortFolder(c);
400
+ label.title = c;
401
+ grid.appendChild(label);
402
+ }
403
+ for (const r of folders) {
404
+ const label = document.createElement('span');
405
+ label.className = 'heat-label';
406
+ label.textContent = shortFolder(r);
407
+ label.title = r;
408
+ grid.appendChild(label);
409
+ for (const c of folders) {
410
+ const cell = document.createElement('span');
411
+ cell.className = 'heat-cell';
412
+ const n = counts.get(r + '->' + c) || 0;
413
+ if (n > 0) {
414
+ const pct = Math.max(14, Math.round((n / max) * 86));
415
+ cell.style.background = `color-mix(in srgb, var(--accent) ${pct}%, transparent)`;
416
+ cell.textContent = n;
417
+ cell.title = `${r} → ${c}: ${n} ${n === 1 ? 'import' : 'imports'}`;
418
+ }
419
+ grid.appendChild(cell);
420
+ }
446
421
  }
447
- }
448
- return grid;
422
+ return grid;
449
423
  }
450
-
451
424
  function shortFolder(name) {
452
- const clean = name === '(root)' ? 'root' : name;
453
- return clean.length > 9 ? clean.slice(0, 8) + '…' : clean;
425
+ const clean = name === '(root)' ? 'root' : name;
426
+ return clean.length > 9 ? clean.slice(0, 8) + '…' : clean;
454
427
  }
455
-
456
428
  // The AI flow reuses the same box: streaming text lands here. `headerHtml`
457
429
  // pins a line above the answer — the question being answered, for instance.
458
430
  export function setExplain(html, sourceNote) {
459
- el.inspExplain.innerHTML = html + (sourceNote ? `<p class="explain-src">${escapeHtml(sourceNote)}</p>` : '');
431
+ el.inspExplain.innerHTML = html + (sourceNote ? `<p class="explain-src">${escapeHtml(sourceNote)}</p>` : '');
460
432
  }
461
-
462
433
  export function explainStreamWriter(headerHtml = '') {
463
- let text = '';
464
- el.inspExplain.innerHTML = headerHtml + '<p><span class="caret"></span></p>';
465
- return {
466
- push(delta) {
467
- text += delta;
468
- el.inspExplain.innerHTML = headerHtml + mdLite(text) + '<span class="caret"></span>';
469
- },
470
- finish(note) {
471
- el.inspExplain.innerHTML = headerHtml + mdLite(text) + (note ? `<p class="explain-src">${escapeHtml(note)}</p>` : '');
472
- },
473
- fail(message) {
474
- el.inspExplain.innerHTML = headerHtml + `<p style="color:var(--warn)">${escapeHtml(message)}</p>`;
475
- },
476
- };
434
+ let text = '';
435
+ el.inspExplain.innerHTML = headerHtml + '<p><span class="caret"></span></p>';
436
+ return {
437
+ push(delta) {
438
+ text += delta;
439
+ el.inspExplain.innerHTML = headerHtml + mdLite(text) + '<span class="caret"></span>';
440
+ },
441
+ finish(note) {
442
+ el.inspExplain.innerHTML = headerHtml + mdLite(text) + (note ? `<p class="explain-src">${escapeHtml(note)}</p>` : '');
443
+ },
444
+ fail(message) {
445
+ el.inspExplain.innerHTML = headerHtml + `<p style="color:var(--warn)">${escapeHtml(message)}</p>`;
446
+ },
447
+ };
477
448
  }
478
-
479
449
  // ---- helpers ---------------------------------------------------------------
480
-
481
450
  function addPathList(title, paths, scan) {
482
- if (!paths.length) return;
483
- addList(title, paths.map((p) => ({ text: p, path: p })), scan);
451
+ if (!paths.length)
452
+ return;
453
+ addList(title, paths.map((p) => ({ text: p, path: p })), scan);
484
454
  }
485
-
486
455
  function addList(title, items, scan) {
487
- if (!items || !items.length) return;
488
- const h = document.createElement('h3');
489
- h.className = 'inspector-h';
490
- h.textContent = title;
491
- const ul = document.createElement('ul');
492
- ul.className = 'link-list';
493
- for (const item of items.slice(0, 14)) {
494
- const li = document.createElement('li');
495
- if (item.path) {
496
- const b = document.createElement('button');
497
- b.textContent = item.text;
498
- b.dataset.goto = item.path;
499
- if (item.title) b.title = item.title;
500
- li.appendChild(b);
501
- } else {
502
- li.textContent = item.text;
456
+ if (!items || !items.length)
457
+ return;
458
+ const h = document.createElement('h3');
459
+ h.className = 'inspector-h';
460
+ h.textContent = title;
461
+ const ul = document.createElement('ul');
462
+ ul.className = 'link-list';
463
+ for (const item of items.slice(0, 14)) {
464
+ const li = document.createElement('li');
465
+ if (item.path) {
466
+ const b = document.createElement('button');
467
+ b.textContent = item.text;
468
+ b.dataset.goto = item.path;
469
+ if (item.title)
470
+ b.title = item.title;
471
+ li.appendChild(b);
472
+ }
473
+ else {
474
+ li.textContent = item.text;
475
+ }
476
+ if (item.kind) {
477
+ const k = document.createElement('span');
478
+ k.className = 'fn-kind';
479
+ k.textContent = item.kind;
480
+ li.appendChild(k);
481
+ }
482
+ ul.appendChild(li);
503
483
  }
504
- if (item.kind) {
505
- const k = document.createElement('span');
506
- k.className = 'fn-kind';
507
- k.textContent = item.kind;
508
- li.appendChild(k);
484
+ if (items.length > 14) {
485
+ const more = document.createElement('li');
486
+ more.textContent = `…and ${items.length - 14} more`;
487
+ more.style.color = 'var(--faint)';
488
+ ul.appendChild(more);
509
489
  }
510
- ul.appendChild(li);
511
- }
512
- if (items.length > 14) {
513
- const more = document.createElement('li');
514
- more.textContent = `…and ${items.length - 14} more`;
515
- more.style.color = 'var(--faint)';
516
- ul.appendChild(more);
517
- }
518
- el.inspLists.append(h, ul);
490
+ el.inspLists.append(h, ul);
519
491
  }
520
-
521
492
  // Clicking a path inside the inspector navigates to it (wired in app.js).
522
493
  export function onInspectorNavigate(handler) {
523
- el.inspLists.addEventListener('click', (event) => {
524
- const b = event.target.closest('button[data-goto]');
525
- if (b) handler(b.dataset.goto);
526
- });
494
+ el.inspLists.addEventListener('click', (event) => {
495
+ const b = eventElement(event)?.closest('button[data-goto]');
496
+ if (b)
497
+ handler(b.dataset.goto);
498
+ });
527
499
  }
528
-
529
500
  function chip(html) {
530
- return `<span class="stat-chip">${html}</span>`;
501
+ return `<span class="stat-chip">${html}</span>`;
531
502
  }
532
-
533
503
  function langName(id) {
534
- return languageLabel(id, { short: true });
504
+ return languageLabel(id, { short: true });
535
505
  }
506
+ //# sourceMappingURL=inspector.js.map