@n-dx/web 0.1.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 (671) hide show
  1. package/LICENSE +96 -0
  2. package/README.md +5 -0
  3. package/build.js +243 -0
  4. package/dist/cli/index.d.ts +8 -0
  5. package/dist/cli/index.js +50 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/landing/index.html +325 -0
  8. package/dist/landing/landing.d.ts +38 -0
  9. package/dist/landing/landing.js +299 -0
  10. package/dist/landing/landing.js.map +1 -0
  11. package/dist/public.d.ts +55 -0
  12. package/dist/public.js +51 -0
  13. package/dist/public.js.map +1 -0
  14. package/dist/schema/features.d.ts +28 -0
  15. package/dist/schema/features.js +9 -0
  16. package/dist/schema/features.js.map +1 -0
  17. package/dist/schema/v1.d.ts +317 -0
  18. package/dist/schema/v1.js +3 -0
  19. package/dist/schema/v1.js.map +1 -0
  20. package/dist/server/aggregation-cache.d.ts +105 -0
  21. package/dist/server/aggregation-cache.js +163 -0
  22. package/dist/server/aggregation-cache.js.map +1 -0
  23. package/dist/server/concurrent-execution-metrics.d.ts +103 -0
  24. package/dist/server/concurrent-execution-metrics.js +253 -0
  25. package/dist/server/concurrent-execution-metrics.js.map +1 -0
  26. package/dist/server/domain-gateway.d.ts +18 -0
  27. package/dist/server/domain-gateway.js +19 -0
  28. package/dist/server/domain-gateway.js.map +1 -0
  29. package/dist/server/incremental-task-usage.d.ts +92 -0
  30. package/dist/server/incremental-task-usage.js +251 -0
  31. package/dist/server/incremental-task-usage.js.map +1 -0
  32. package/dist/server/index.d.ts +39 -0
  33. package/dist/server/index.js +36 -0
  34. package/dist/server/index.js.map +1 -0
  35. package/dist/server/port.d.ts +96 -0
  36. package/dist/server/port.js +134 -0
  37. package/dist/server/port.js.map +1 -0
  38. package/dist/server/pr-markdown-refresh-diagnostics.d.ts +59 -0
  39. package/dist/server/pr-markdown-refresh-diagnostics.js +429 -0
  40. package/dist/server/pr-markdown-refresh-diagnostics.js.map +1 -0
  41. package/dist/server/prd-io.d.ts +40 -0
  42. package/dist/server/prd-io.js +66 -0
  43. package/dist/server/prd-io.js.map +1 -0
  44. package/dist/server/process-memory-tracker.d.ts +79 -0
  45. package/dist/server/process-memory-tracker.js +194 -0
  46. package/dist/server/process-memory-tracker.js.map +1 -0
  47. package/dist/server/register-scheduler.d.ts +53 -0
  48. package/dist/server/register-scheduler.js +36 -0
  49. package/dist/server/register-scheduler.js.map +1 -0
  50. package/dist/server/rex-gateway.d.ts +39 -0
  51. package/dist/server/rex-gateway.js +47 -0
  52. package/dist/server/rex-gateway.js.map +1 -0
  53. package/dist/server/routes-adaptive.d.ts +21 -0
  54. package/dist/server/routes-adaptive.js +659 -0
  55. package/dist/server/routes-adaptive.js.map +1 -0
  56. package/dist/server/routes-config.d.ts +47 -0
  57. package/dist/server/routes-config.js +222 -0
  58. package/dist/server/routes-config.js.map +1 -0
  59. package/dist/server/routes-data.d.ts +14 -0
  60. package/dist/server/routes-data.js +129 -0
  61. package/dist/server/routes-data.js.map +1 -0
  62. package/dist/server/routes-features.d.ts +14 -0
  63. package/dist/server/routes-features.js +245 -0
  64. package/dist/server/routes-features.js.map +1 -0
  65. package/dist/server/routes-hench.d.ts +116 -0
  66. package/dist/server/routes-hench.js +2016 -0
  67. package/dist/server/routes-hench.js.map +1 -0
  68. package/dist/server/routes-integrations.d.ts +23 -0
  69. package/dist/server/routes-integrations.js +277 -0
  70. package/dist/server/routes-integrations.js.map +1 -0
  71. package/dist/server/routes-mcp.d.ts +31 -0
  72. package/dist/server/routes-mcp.js +175 -0
  73. package/dist/server/routes-mcp.js.map +1 -0
  74. package/dist/server/routes-notion.d.ts +23 -0
  75. package/dist/server/routes-notion.js +723 -0
  76. package/dist/server/routes-notion.js.map +1 -0
  77. package/dist/server/routes-project.d.ts +47 -0
  78. package/dist/server/routes-project.js +128 -0
  79. package/dist/server/routes-project.js.map +1 -0
  80. package/dist/server/routes-rex/analysis.d.ts +11 -0
  81. package/dist/server/routes-rex/analysis.js +583 -0
  82. package/dist/server/routes-rex/analysis.js.map +1 -0
  83. package/dist/server/routes-rex/execution.d.ts +37 -0
  84. package/dist/server/routes-rex/execution.js +355 -0
  85. package/dist/server/routes-rex/execution.js.map +1 -0
  86. package/dist/server/routes-rex/health.d.ts +8 -0
  87. package/dist/server/routes-rex/health.js +136 -0
  88. package/dist/server/routes-rex/health.js.map +1 -0
  89. package/dist/server/routes-rex/index.d.ts +35 -0
  90. package/dist/server/routes-rex/index.js +72 -0
  91. package/dist/server/routes-rex/index.js.map +1 -0
  92. package/dist/server/routes-rex/items.d.ts +8 -0
  93. package/dist/server/routes-rex/items.js +359 -0
  94. package/dist/server/routes-rex/items.js.map +1 -0
  95. package/dist/server/routes-rex/prune.d.ts +11 -0
  96. package/dist/server/routes-rex/prune.js +379 -0
  97. package/dist/server/routes-rex/prune.js.map +1 -0
  98. package/dist/server/routes-rex/reads.d.ts +9 -0
  99. package/dist/server/routes-rex/reads.js +119 -0
  100. package/dist/server/routes-rex/reads.js.map +1 -0
  101. package/dist/server/routes-rex/requirements.d.ts +10 -0
  102. package/dist/server/routes-rex/requirements.js +408 -0
  103. package/dist/server/routes-rex/requirements.js.map +1 -0
  104. package/dist/server/routes-rex/shared.d.ts +37 -0
  105. package/dist/server/routes-rex/shared.js +73 -0
  106. package/dist/server/routes-rex/shared.js.map +1 -0
  107. package/dist/server/routes-search.d.ts +26 -0
  108. package/dist/server/routes-search.js +82 -0
  109. package/dist/server/routes-search.js.map +1 -0
  110. package/dist/server/routes-sourcevision.d.ts +18 -0
  111. package/dist/server/routes-sourcevision.js +444 -0
  112. package/dist/server/routes-sourcevision.js.map +1 -0
  113. package/dist/server/routes-static.d.ts +20 -0
  114. package/dist/server/routes-static.js +168 -0
  115. package/dist/server/routes-static.js.map +1 -0
  116. package/dist/server/routes-status.d.ts +58 -0
  117. package/dist/server/routes-status.js +191 -0
  118. package/dist/server/routes-status.js.map +1 -0
  119. package/dist/server/routes-token-usage.d.ts +63 -0
  120. package/dist/server/routes-token-usage.js +720 -0
  121. package/dist/server/routes-token-usage.js.map +1 -0
  122. package/dist/server/routes-validation.d.ts +10 -0
  123. package/dist/server/routes-validation.js +365 -0
  124. package/dist/server/routes-validation.js.map +1 -0
  125. package/dist/server/routes-workflow.d.ts +15 -0
  126. package/dist/server/routes-workflow.js +498 -0
  127. package/dist/server/routes-workflow.js.map +1 -0
  128. package/dist/server/search-index.d.ts +111 -0
  129. package/dist/server/search-index.js +348 -0
  130. package/dist/server/search-index.js.map +1 -0
  131. package/dist/server/shared-types.d.ts +65 -0
  132. package/dist/server/shared-types.js +31 -0
  133. package/dist/server/shared-types.js.map +1 -0
  134. package/dist/server/start.d.ts +68 -0
  135. package/dist/server/start.js +568 -0
  136. package/dist/server/start.js.map +1 -0
  137. package/dist/server/task-usage.d.ts +17 -0
  138. package/dist/server/task-usage.js +20 -0
  139. package/dist/server/task-usage.js.map +1 -0
  140. package/dist/server/types.d.ts +27 -0
  141. package/dist/server/types.js +26 -0
  142. package/dist/server/types.js.map +1 -0
  143. package/dist/server/usage-cleanup-scheduler.d.ts +107 -0
  144. package/dist/server/usage-cleanup-scheduler.js +232 -0
  145. package/dist/server/usage-cleanup-scheduler.js.map +1 -0
  146. package/dist/server/websocket.d.ts +131 -0
  147. package/dist/server/websocket.js +512 -0
  148. package/dist/server/websocket.js.map +1 -0
  149. package/dist/shared/data-files.d.ts +17 -0
  150. package/dist/shared/data-files.js +18 -0
  151. package/dist/shared/data-files.js.map +1 -0
  152. package/dist/shared/index.d.ts +9 -0
  153. package/dist/shared/index.js +9 -0
  154. package/dist/shared/index.js.map +1 -0
  155. package/dist/shared/view-id.d.ts +8 -0
  156. package/dist/shared/view-id.js +9 -0
  157. package/dist/shared/view-id.js.map +1 -0
  158. package/dist/viewer/Hench-F.png +0 -0
  159. package/dist/viewer/Rex-F.png +0 -0
  160. package/dist/viewer/SourceVision-F.png +0 -0
  161. package/dist/viewer/SourceVision.png +0 -0
  162. package/dist/viewer/api.d.ts +19 -0
  163. package/dist/viewer/api.js +17 -0
  164. package/dist/viewer/api.js.map +1 -0
  165. package/dist/viewer/bootstrap.d.ts +13 -0
  166. package/dist/viewer/bootstrap.js +40 -0
  167. package/dist/viewer/bootstrap.js.map +1 -0
  168. package/dist/viewer/components/active-tasks-panel.d.ts +36 -0
  169. package/dist/viewer/components/active-tasks-panel.js +183 -0
  170. package/dist/viewer/components/active-tasks-panel.js.map +1 -0
  171. package/dist/viewer/components/breadcrumb.d.ts +21 -0
  172. package/dist/viewer/components/breadcrumb.js +117 -0
  173. package/dist/viewer/components/breadcrumb.js.map +1 -0
  174. package/dist/viewer/components/concurrency-panel.d.ts +18 -0
  175. package/dist/viewer/components/concurrency-panel.js +175 -0
  176. package/dist/viewer/components/concurrency-panel.js.map +1 -0
  177. package/dist/viewer/components/config-footer.d.ts +11 -0
  178. package/dist/viewer/components/config-footer.js +132 -0
  179. package/dist/viewer/components/config-footer.js.map +1 -0
  180. package/dist/viewer/components/constants.d.ts +10 -0
  181. package/dist/viewer/components/constants.js +11 -0
  182. package/dist/viewer/components/constants.js.map +1 -0
  183. package/dist/viewer/components/copy-link-button.d.ts +30 -0
  184. package/dist/viewer/components/copy-link-button.js +74 -0
  185. package/dist/viewer/components/copy-link-button.js.map +1 -0
  186. package/dist/viewer/components/crash-recovery-banner.d.ts +28 -0
  187. package/dist/viewer/components/crash-recovery-banner.js +47 -0
  188. package/dist/viewer/components/crash-recovery-banner.js.map +1 -0
  189. package/dist/viewer/components/data-display/collapsible-section.d.ts +14 -0
  190. package/dist/viewer/components/data-display/collapsible-section.js +66 -0
  191. package/dist/viewer/components/data-display/collapsible-section.js.map +1 -0
  192. package/dist/viewer/components/data-display/findings-list.d.ts +13 -0
  193. package/dist/viewer/components/data-display/findings-list.js +136 -0
  194. package/dist/viewer/components/data-display/findings-list.js.map +1 -0
  195. package/dist/viewer/components/data-display/health-gauge.d.ts +37 -0
  196. package/dist/viewer/components/data-display/health-gauge.js +62 -0
  197. package/dist/viewer/components/data-display/health-gauge.js.map +1 -0
  198. package/dist/viewer/components/data-display/mini-charts.d.ts +36 -0
  199. package/dist/viewer/components/data-display/mini-charts.js +124 -0
  200. package/dist/viewer/components/data-display/mini-charts.js.map +1 -0
  201. package/dist/viewer/components/data-display/tree-view.d.ts +18 -0
  202. package/dist/viewer/components/data-display/tree-view.js +107 -0
  203. package/dist/viewer/components/data-display/tree-view.js.map +1 -0
  204. package/dist/viewer/components/data-display/zone-map.d.ts +28 -0
  205. package/dist/viewer/components/data-display/zone-map.js +197 -0
  206. package/dist/viewer/components/data-display/zone-map.js.map +1 -0
  207. package/dist/viewer/components/degradation-banner.d.ts +28 -0
  208. package/dist/viewer/components/degradation-banner.js +47 -0
  209. package/dist/viewer/components/degradation-banner.js.map +1 -0
  210. package/dist/viewer/components/detail-panel.d.ts +12 -0
  211. package/dist/viewer/components/detail-panel.js +166 -0
  212. package/dist/viewer/components/detail-panel.js.map +1 -0
  213. package/dist/viewer/components/elapsed-time.d.ts +38 -0
  214. package/dist/viewer/components/elapsed-time.js +35 -0
  215. package/dist/viewer/components/elapsed-time.js.map +1 -0
  216. package/dist/viewer/components/faq.d.ts +12 -0
  217. package/dist/viewer/components/faq.js +237 -0
  218. package/dist/viewer/components/faq.js.map +1 -0
  219. package/dist/viewer/components/favicon.d.ts +30 -0
  220. package/dist/viewer/components/favicon.js +90 -0
  221. package/dist/viewer/components/favicon.js.map +1 -0
  222. package/dist/viewer/components/guide.d.ts +5 -0
  223. package/dist/viewer/components/guide.js +116 -0
  224. package/dist/viewer/components/guide.js.map +1 -0
  225. package/dist/viewer/components/index.d.ts +37 -0
  226. package/dist/viewer/components/index.js +46 -0
  227. package/dist/viewer/components/index.js.map +1 -0
  228. package/dist/viewer/components/logos.d.ts +42 -0
  229. package/dist/viewer/components/logos.js +46 -0
  230. package/dist/viewer/components/logos.js.map +1 -0
  231. package/dist/viewer/components/memory-panel.d.ts +19 -0
  232. package/dist/viewer/components/memory-panel.js +181 -0
  233. package/dist/viewer/components/memory-panel.js.map +1 -0
  234. package/dist/viewer/components/memory-warning.d.ts +23 -0
  235. package/dist/viewer/components/memory-warning.js +42 -0
  236. package/dist/viewer/components/memory-warning.js.map +1 -0
  237. package/dist/viewer/components/notion-schema-wizard.d.ts +16 -0
  238. package/dist/viewer/components/notion-schema-wizard.js +263 -0
  239. package/dist/viewer/components/notion-schema-wizard.js.map +1 -0
  240. package/dist/viewer/components/polling-suspension-indicator.d.ts +24 -0
  241. package/dist/viewer/components/polling-suspension-indicator.js +31 -0
  242. package/dist/viewer/components/polling-suspension-indicator.js.map +1 -0
  243. package/dist/viewer/components/prd-tree/add-item-form.d.ts +31 -0
  244. package/dist/viewer/components/prd-tree/add-item-form.js +231 -0
  245. package/dist/viewer/components/prd-tree/add-item-form.js.map +1 -0
  246. package/dist/viewer/components/prd-tree/analyze-panel.d.ts +13 -0
  247. package/dist/viewer/components/prd-tree/analyze-panel.js +245 -0
  248. package/dist/viewer/components/prd-tree/analyze-panel.js.map +1 -0
  249. package/dist/viewer/components/prd-tree/batch-import-panel.d.ts +34 -0
  250. package/dist/viewer/components/prd-tree/batch-import-panel.js +415 -0
  251. package/dist/viewer/components/prd-tree/batch-import-panel.js.map +1 -0
  252. package/dist/viewer/components/prd-tree/bulk-actions.d.ts +19 -0
  253. package/dist/viewer/components/prd-tree/bulk-actions.js +90 -0
  254. package/dist/viewer/components/prd-tree/bulk-actions.js.map +1 -0
  255. package/dist/viewer/components/prd-tree/compute.d.ts +32 -0
  256. package/dist/viewer/components/prd-tree/compute.js +129 -0
  257. package/dist/viewer/components/prd-tree/compute.js.map +1 -0
  258. package/dist/viewer/components/prd-tree/delete-confirmation.d.ts +23 -0
  259. package/dist/viewer/components/prd-tree/delete-confirmation.js +83 -0
  260. package/dist/viewer/components/prd-tree/delete-confirmation.js.map +1 -0
  261. package/dist/viewer/components/prd-tree/execution-panel.d.ts +14 -0
  262. package/dist/viewer/components/prd-tree/execution-panel.js +291 -0
  263. package/dist/viewer/components/prd-tree/execution-panel.js.map +1 -0
  264. package/dist/viewer/components/prd-tree/facet-filter.d.ts +40 -0
  265. package/dist/viewer/components/prd-tree/facet-filter.js +188 -0
  266. package/dist/viewer/components/prd-tree/facet-filter.js.map +1 -0
  267. package/dist/viewer/components/prd-tree/index.d.ts +24 -0
  268. package/dist/viewer/components/prd-tree/index.js +16 -0
  269. package/dist/viewer/components/prd-tree/index.js.map +1 -0
  270. package/dist/viewer/components/prd-tree/inline-add-form.d.ts +32 -0
  271. package/dist/viewer/components/prd-tree/inline-add-form.js +175 -0
  272. package/dist/viewer/components/prd-tree/inline-add-form.js.map +1 -0
  273. package/dist/viewer/components/prd-tree/inline-status-picker.d.ts +31 -0
  274. package/dist/viewer/components/prd-tree/inline-status-picker.js +110 -0
  275. package/dist/viewer/components/prd-tree/inline-status-picker.js.map +1 -0
  276. package/dist/viewer/components/prd-tree/lazy-children.d.ts +40 -0
  277. package/dist/viewer/components/prd-tree/lazy-children.js +73 -0
  278. package/dist/viewer/components/prd-tree/lazy-children.js.map +1 -0
  279. package/dist/viewer/components/prd-tree/levels.d.ts +31 -0
  280. package/dist/viewer/components/prd-tree/levels.js +72 -0
  281. package/dist/viewer/components/prd-tree/levels.js.map +1 -0
  282. package/dist/viewer/components/prd-tree/listener-lifecycle.d.ts +105 -0
  283. package/dist/viewer/components/prd-tree/listener-lifecycle.js +173 -0
  284. package/dist/viewer/components/prd-tree/listener-lifecycle.js.map +1 -0
  285. package/dist/viewer/components/prd-tree/merge-preview.d.ts +25 -0
  286. package/dist/viewer/components/prd-tree/merge-preview.js +181 -0
  287. package/dist/viewer/components/prd-tree/merge-preview.js.map +1 -0
  288. package/dist/viewer/components/prd-tree/prd-tree.d.ts +91 -0
  289. package/dist/viewer/components/prd-tree/prd-tree.js +565 -0
  290. package/dist/viewer/components/prd-tree/prd-tree.js.map +1 -0
  291. package/dist/viewer/components/prd-tree/proposal-editor.d.ts +44 -0
  292. package/dist/viewer/components/prd-tree/proposal-editor.js +438 -0
  293. package/dist/viewer/components/prd-tree/proposal-editor.js.map +1 -0
  294. package/dist/viewer/components/prd-tree/prune-confirmation.d.ts +25 -0
  295. package/dist/viewer/components/prd-tree/prune-confirmation.js +336 -0
  296. package/dist/viewer/components/prd-tree/prune-confirmation.js.map +1 -0
  297. package/dist/viewer/components/prd-tree/prune-diff-tree.d.ts +39 -0
  298. package/dist/viewer/components/prd-tree/prune-diff-tree.js +319 -0
  299. package/dist/viewer/components/prd-tree/prune-diff-tree.js.map +1 -0
  300. package/dist/viewer/components/prd-tree/reorganize-panel.d.ts +16 -0
  301. package/dist/viewer/components/prd-tree/reorganize-panel.js +213 -0
  302. package/dist/viewer/components/prd-tree/reorganize-panel.js.map +1 -0
  303. package/dist/viewer/components/prd-tree/smart-add-input.d.ts +19 -0
  304. package/dist/viewer/components/prd-tree/smart-add-input.js +383 -0
  305. package/dist/viewer/components/prd-tree/smart-add-input.js.map +1 -0
  306. package/dist/viewer/components/prd-tree/status-filter.d.ts +40 -0
  307. package/dist/viewer/components/prd-tree/status-filter.js +131 -0
  308. package/dist/viewer/components/prd-tree/status-filter.js.map +1 -0
  309. package/dist/viewer/components/prd-tree/task-detail.d.ts +41 -0
  310. package/dist/viewer/components/prd-tree/task-detail.js +1205 -0
  311. package/dist/viewer/components/prd-tree/task-detail.js.map +1 -0
  312. package/dist/viewer/components/prd-tree/task-utilization.d.ts +7 -0
  313. package/dist/viewer/components/prd-tree/task-utilization.js +25 -0
  314. package/dist/viewer/components/prd-tree/task-utilization.js.map +1 -0
  315. package/dist/viewer/components/prd-tree/tree-differ.d.ts +59 -0
  316. package/dist/viewer/components/prd-tree/tree-differ.js +200 -0
  317. package/dist/viewer/components/prd-tree/tree-differ.js.map +1 -0
  318. package/dist/viewer/components/prd-tree/tree-event-delegate.d.ts +70 -0
  319. package/dist/viewer/components/prd-tree/tree-event-delegate.js +176 -0
  320. package/dist/viewer/components/prd-tree/tree-event-delegate.js.map +1 -0
  321. package/dist/viewer/components/prd-tree/tree-search.d.ts +65 -0
  322. package/dist/viewer/components/prd-tree/tree-search.js +178 -0
  323. package/dist/viewer/components/prd-tree/tree-search.js.map +1 -0
  324. package/dist/viewer/components/prd-tree/tree-utils.d.ts +38 -0
  325. package/dist/viewer/components/prd-tree/tree-utils.js +107 -0
  326. package/dist/viewer/components/prd-tree/tree-utils.js.map +1 -0
  327. package/dist/viewer/components/prd-tree/types.d.ts +93 -0
  328. package/dist/viewer/components/prd-tree/types.js +16 -0
  329. package/dist/viewer/components/prd-tree/types.js.map +1 -0
  330. package/dist/viewer/components/prd-tree/virtual-scroll.d.ts +119 -0
  331. package/dist/viewer/components/prd-tree/virtual-scroll.js +169 -0
  332. package/dist/viewer/components/prd-tree/virtual-scroll.js.map +1 -0
  333. package/dist/viewer/components/progressive-loader.d.ts +114 -0
  334. package/dist/viewer/components/progressive-loader.js +225 -0
  335. package/dist/viewer/components/progressive-loader.js.map +1 -0
  336. package/dist/viewer/components/refresh-queue-status.d.ts +20 -0
  337. package/dist/viewer/components/refresh-queue-status.js +65 -0
  338. package/dist/viewer/components/refresh-queue-status.js.map +1 -0
  339. package/dist/viewer/components/rex-task-link.d.ts +50 -0
  340. package/dist/viewer/components/rex-task-link.js +218 -0
  341. package/dist/viewer/components/rex-task-link.js.map +1 -0
  342. package/dist/viewer/components/search-filter.d.ts +20 -0
  343. package/dist/viewer/components/search-filter.js +28 -0
  344. package/dist/viewer/components/search-filter.js.map +1 -0
  345. package/dist/viewer/components/search-overlay.d.ts +31 -0
  346. package/dist/viewer/components/search-overlay.js +472 -0
  347. package/dist/viewer/components/search-overlay.js.map +1 -0
  348. package/dist/viewer/components/sidebar.d.ts +18 -0
  349. package/dist/viewer/components/sidebar.js +357 -0
  350. package/dist/viewer/components/sidebar.js.map +1 -0
  351. package/dist/viewer/components/status-indicators.d.ts +63 -0
  352. package/dist/viewer/components/status-indicators.js +136 -0
  353. package/dist/viewer/components/status-indicators.js.map +1 -0
  354. package/dist/viewer/components/theme-toggle.d.ts +8 -0
  355. package/dist/viewer/components/theme-toggle.js +28 -0
  356. package/dist/viewer/components/theme-toggle.js.map +1 -0
  357. package/dist/viewer/components/throttle-controls.d.ts +18 -0
  358. package/dist/viewer/components/throttle-controls.js +304 -0
  359. package/dist/viewer/components/throttle-controls.js.map +1 -0
  360. package/dist/viewer/components/ws-health-panel.d.ts +18 -0
  361. package/dist/viewer/components/ws-health-panel.js +250 -0
  362. package/dist/viewer/components/ws-health-panel.js.map +1 -0
  363. package/dist/viewer/components/zone-slideout.d.ts +17 -0
  364. package/dist/viewer/components/zone-slideout.js +162 -0
  365. package/dist/viewer/components/zone-slideout.js.map +1 -0
  366. package/dist/viewer/crash/crash-detector.d.ts +69 -0
  367. package/dist/viewer/crash/crash-detector.js +239 -0
  368. package/dist/viewer/crash/crash-detector.js.map +1 -0
  369. package/dist/viewer/crash/index.d.ts +7 -0
  370. package/dist/viewer/crash/index.js +8 -0
  371. package/dist/viewer/crash/index.js.map +1 -0
  372. package/dist/viewer/deployed-mode.d.ts +37 -0
  373. package/dist/viewer/deployed-mode.js +94 -0
  374. package/dist/viewer/deployed-mode.js.map +1 -0
  375. package/dist/viewer/external.d.ts +17 -0
  376. package/dist/viewer/external.js +17 -0
  377. package/dist/viewer/external.js.map +1 -0
  378. package/dist/viewer/graph/index.d.ts +9 -0
  379. package/dist/viewer/graph/index.js +12 -0
  380. package/dist/viewer/graph/index.js.map +1 -0
  381. package/dist/viewer/graph/physics.d.ts +96 -0
  382. package/dist/viewer/graph/physics.js +366 -0
  383. package/dist/viewer/graph/physics.js.map +1 -0
  384. package/dist/viewer/graph/renderer.d.ts +184 -0
  385. package/dist/viewer/graph/renderer.js +1438 -0
  386. package/dist/viewer/graph/renderer.js.map +1 -0
  387. package/dist/viewer/hooks/index.d.ts +27 -0
  388. package/dist/viewer/hooks/index.js +28 -0
  389. package/dist/viewer/hooks/index.js.map +1 -0
  390. package/dist/viewer/hooks/use-app-data.d.ts +31 -0
  391. package/dist/viewer/hooks/use-app-data.js +152 -0
  392. package/dist/viewer/hooks/use-app-data.js.map +1 -0
  393. package/dist/viewer/hooks/use-crash-recovery.d.ts +50 -0
  394. package/dist/viewer/hooks/use-crash-recovery.js +76 -0
  395. package/dist/viewer/hooks/use-crash-recovery.js.map +1 -0
  396. package/dist/viewer/hooks/use-delete-actions.d.ts +48 -0
  397. package/dist/viewer/hooks/use-delete-actions.js +103 -0
  398. package/dist/viewer/hooks/use-delete-actions.js.map +1 -0
  399. package/dist/viewer/hooks/use-dom-performance-monitor.d.ts +68 -0
  400. package/dist/viewer/hooks/use-dom-performance-monitor.js +71 -0
  401. package/dist/viewer/hooks/use-dom-performance-monitor.js.map +1 -0
  402. package/dist/viewer/hooks/use-facet-state.d.ts +32 -0
  403. package/dist/viewer/hooks/use-facet-state.js +119 -0
  404. package/dist/viewer/hooks/use-facet-state.js.map +1 -0
  405. package/dist/viewer/hooks/use-feature-toggle.d.ts +18 -0
  406. package/dist/viewer/hooks/use-feature-toggle.js +57 -0
  407. package/dist/viewer/hooks/use-feature-toggle.js.map +1 -0
  408. package/dist/viewer/hooks/use-file-edges.d.ts +23 -0
  409. package/dist/viewer/hooks/use-file-edges.js +221 -0
  410. package/dist/viewer/hooks/use-file-edges.js.map +1 -0
  411. package/dist/viewer/hooks/use-graceful-degradation.d.ts +30 -0
  412. package/dist/viewer/hooks/use-graceful-degradation.js +45 -0
  413. package/dist/viewer/hooks/use-graceful-degradation.js.map +1 -0
  414. package/dist/viewer/hooks/use-item-selection.d.ts +49 -0
  415. package/dist/viewer/hooks/use-item-selection.js +117 -0
  416. package/dist/viewer/hooks/use-item-selection.js.map +1 -0
  417. package/dist/viewer/hooks/use-memory-monitor.d.ts +39 -0
  418. package/dist/viewer/hooks/use-memory-monitor.js +73 -0
  419. package/dist/viewer/hooks/use-memory-monitor.js.map +1 -0
  420. package/dist/viewer/hooks/use-pan-zoom.d.ts +33 -0
  421. package/dist/viewer/hooks/use-pan-zoom.js +110 -0
  422. package/dist/viewer/hooks/use-pan-zoom.js.map +1 -0
  423. package/dist/viewer/hooks/use-persistent-filter.d.ts +24 -0
  424. package/dist/viewer/hooks/use-persistent-filter.js +37 -0
  425. package/dist/viewer/hooks/use-persistent-filter.js.map +1 -0
  426. package/dist/viewer/hooks/use-polling-suspension.d.ts +32 -0
  427. package/dist/viewer/hooks/use-polling-suspension.js +41 -0
  428. package/dist/viewer/hooks/use-polling-suspension.js.map +1 -0
  429. package/dist/viewer/hooks/use-polling.d.ts +39 -0
  430. package/dist/viewer/hooks/use-polling.js +55 -0
  431. package/dist/viewer/hooks/use-polling.js.map +1 -0
  432. package/dist/viewer/hooks/use-prd-actions.d.ts +126 -0
  433. package/dist/viewer/hooks/use-prd-actions.js +250 -0
  434. package/dist/viewer/hooks/use-prd-actions.js.map +1 -0
  435. package/dist/viewer/hooks/use-prd-data.d.ts +45 -0
  436. package/dist/viewer/hooks/use-prd-data.js +159 -0
  437. package/dist/viewer/hooks/use-prd-data.js.map +1 -0
  438. package/dist/viewer/hooks/use-prd-deep-link.d.ts +45 -0
  439. package/dist/viewer/hooks/use-prd-deep-link.js +60 -0
  440. package/dist/viewer/hooks/use-prd-deep-link.js.map +1 -0
  441. package/dist/viewer/hooks/use-prd-websocket.d.ts +47 -0
  442. package/dist/viewer/hooks/use-prd-websocket.js +139 -0
  443. package/dist/viewer/hooks/use-prd-websocket.js.map +1 -0
  444. package/dist/viewer/hooks/use-project-metadata.d.ts +25 -0
  445. package/dist/viewer/hooks/use-project-metadata.js +55 -0
  446. package/dist/viewer/hooks/use-project-metadata.js.map +1 -0
  447. package/dist/viewer/hooks/use-project-status.d.ts +60 -0
  448. package/dist/viewer/hooks/use-project-status.js +133 -0
  449. package/dist/viewer/hooks/use-project-status.js.map +1 -0
  450. package/dist/viewer/hooks/use-refresh-throttle.d.ts +45 -0
  451. package/dist/viewer/hooks/use-refresh-throttle.js +52 -0
  452. package/dist/viewer/hooks/use-refresh-throttle.js.map +1 -0
  453. package/dist/viewer/hooks/use-route-state.d.ts +18 -0
  454. package/dist/viewer/hooks/use-route-state.js +115 -0
  455. package/dist/viewer/hooks/use-route-state.js.map +1 -0
  456. package/dist/viewer/hooks/use-subzone-edges.d.ts +21 -0
  457. package/dist/viewer/hooks/use-subzone-edges.js +147 -0
  458. package/dist/viewer/hooks/use-subzone-edges.js.map +1 -0
  459. package/dist/viewer/hooks/use-tab-visibility.d.ts +31 -0
  460. package/dist/viewer/hooks/use-tab-visibility.js +43 -0
  461. package/dist/viewer/hooks/use-tab-visibility.js.map +1 -0
  462. package/dist/viewer/hooks/use-tick.d.ts +43 -0
  463. package/dist/viewer/hooks/use-tick.js +76 -0
  464. package/dist/viewer/hooks/use-tick.js.map +1 -0
  465. package/dist/viewer/hooks/use-toast.d.ts +24 -0
  466. package/dist/viewer/hooks/use-toast.js +26 -0
  467. package/dist/viewer/hooks/use-toast.js.map +1 -0
  468. package/dist/viewer/hooks/use-zone-drag.d.ts +30 -0
  469. package/dist/viewer/hooks/use-zone-drag.js +60 -0
  470. package/dist/viewer/hooks/use-zone-drag.js.map +1 -0
  471. package/dist/viewer/index.html +36 -0
  472. package/dist/viewer/loader.d.ts +33 -0
  473. package/dist/viewer/loader.js +195 -0
  474. package/dist/viewer/loader.js.map +1 -0
  475. package/dist/viewer/main.d.ts +1 -0
  476. package/dist/viewer/main.js +121 -0
  477. package/dist/viewer/main.js.map +1 -0
  478. package/dist/viewer/messaging/call-rate-limiter.d.ts +50 -0
  479. package/dist/viewer/messaging/call-rate-limiter.js +103 -0
  480. package/dist/viewer/messaging/call-rate-limiter.js.map +1 -0
  481. package/dist/viewer/messaging/fetch-pipeline.d.ts +58 -0
  482. package/dist/viewer/messaging/fetch-pipeline.js +58 -0
  483. package/dist/viewer/messaging/fetch-pipeline.js.map +1 -0
  484. package/dist/viewer/messaging/index.d.ts +43 -0
  485. package/dist/viewer/messaging/index.js +46 -0
  486. package/dist/viewer/messaging/index.js.map +1 -0
  487. package/dist/viewer/messaging/message-coalescer.d.ts +96 -0
  488. package/dist/viewer/messaging/message-coalescer.js +121 -0
  489. package/dist/viewer/messaging/message-coalescer.js.map +1 -0
  490. package/dist/viewer/messaging/message-throttle.d.ts +95 -0
  491. package/dist/viewer/messaging/message-throttle.js +147 -0
  492. package/dist/viewer/messaging/message-throttle.js.map +1 -0
  493. package/dist/viewer/messaging/request-dedup.d.ts +43 -0
  494. package/dist/viewer/messaging/request-dedup.js +55 -0
  495. package/dist/viewer/messaging/request-dedup.js.map +1 -0
  496. package/dist/viewer/messaging/ws-pipeline.d.ts +85 -0
  497. package/dist/viewer/messaging/ws-pipeline.js +68 -0
  498. package/dist/viewer/messaging/ws-pipeline.js.map +1 -0
  499. package/dist/viewer/n-dx.png +0 -0
  500. package/dist/viewer/performance/dom-performance-monitor.d.ts +157 -0
  501. package/dist/viewer/performance/dom-performance-monitor.js +341 -0
  502. package/dist/viewer/performance/dom-performance-monitor.js.map +1 -0
  503. package/dist/viewer/performance/dom-update-gate.d.ts +122 -0
  504. package/dist/viewer/performance/dom-update-gate.js +229 -0
  505. package/dist/viewer/performance/dom-update-gate.js.map +1 -0
  506. package/dist/viewer/performance/graceful-degradation.d.ts +73 -0
  507. package/dist/viewer/performance/graceful-degradation.js +152 -0
  508. package/dist/viewer/performance/graceful-degradation.js.map +1 -0
  509. package/dist/viewer/performance/index.d.ts +14 -0
  510. package/dist/viewer/performance/index.js +20 -0
  511. package/dist/viewer/performance/index.js.map +1 -0
  512. package/dist/viewer/performance/memory-monitor.d.ts +78 -0
  513. package/dist/viewer/performance/memory-monitor.js +218 -0
  514. package/dist/viewer/performance/memory-monitor.js.map +1 -0
  515. package/dist/viewer/performance/refresh-throttle.d.ts +90 -0
  516. package/dist/viewer/performance/refresh-throttle.js +266 -0
  517. package/dist/viewer/performance/refresh-throttle.js.map +1 -0
  518. package/dist/viewer/performance/response-buffer-gate.d.ts +108 -0
  519. package/dist/viewer/performance/response-buffer-gate.js +170 -0
  520. package/dist/viewer/performance/response-buffer-gate.js.map +1 -0
  521. package/dist/viewer/performance/update-batcher.d.ts +79 -0
  522. package/dist/viewer/performance/update-batcher.js +119 -0
  523. package/dist/viewer/performance/update-batcher.js.map +1 -0
  524. package/dist/viewer/polling/batched-tick-dispatcher.d.ts +83 -0
  525. package/dist/viewer/polling/batched-tick-dispatcher.js +183 -0
  526. package/dist/viewer/polling/batched-tick-dispatcher.js.map +1 -0
  527. package/dist/viewer/polling/index.d.ts +13 -0
  528. package/dist/viewer/polling/index.js +16 -0
  529. package/dist/viewer/polling/index.js.map +1 -0
  530. package/dist/viewer/polling/polling-manager.d.ts +82 -0
  531. package/dist/viewer/polling/polling-manager.js +254 -0
  532. package/dist/viewer/polling/polling-manager.js.map +1 -0
  533. package/dist/viewer/polling/polling-restart.d.ts +45 -0
  534. package/dist/viewer/polling/polling-restart.js +98 -0
  535. package/dist/viewer/polling/polling-restart.js.map +1 -0
  536. package/dist/viewer/polling/polling-state.d.ts +182 -0
  537. package/dist/viewer/polling/polling-state.js +306 -0
  538. package/dist/viewer/polling/polling-state.js.map +1 -0
  539. package/dist/viewer/polling/tab-visibility.d.ts +112 -0
  540. package/dist/viewer/polling/tab-visibility.js +276 -0
  541. package/dist/viewer/polling/tab-visibility.js.map +1 -0
  542. package/dist/viewer/polling/tick-timer.d.ts +70 -0
  543. package/dist/viewer/polling/tick-timer.js +168 -0
  544. package/dist/viewer/polling/tick-timer.js.map +1 -0
  545. package/dist/viewer/polling/tick-visibility-gate.d.ts +92 -0
  546. package/dist/viewer/polling/tick-visibility-gate.js +146 -0
  547. package/dist/viewer/polling/tick-visibility-gate.js.map +1 -0
  548. package/dist/viewer/route-state.d.ts +8 -0
  549. package/dist/viewer/route-state.js +77 -0
  550. package/dist/viewer/route-state.js.map +1 -0
  551. package/dist/viewer/schema-compat.d.ts +17 -0
  552. package/dist/viewer/schema-compat.js +49 -0
  553. package/dist/viewer/schema-compat.js.map +1 -0
  554. package/dist/viewer/types.d.ts +66 -0
  555. package/dist/viewer/types.js +2 -0
  556. package/dist/viewer/types.js.map +1 -0
  557. package/dist/viewer/usage/constants.d.ts +14 -0
  558. package/dist/viewer/usage/constants.js +14 -0
  559. package/dist/viewer/usage/constants.js.map +1 -0
  560. package/dist/viewer/usage/index.d.ts +7 -0
  561. package/dist/viewer/usage/index.js +8 -0
  562. package/dist/viewer/usage/index.js.map +1 -0
  563. package/dist/viewer/utils.d.ts +25 -0
  564. package/dist/viewer/utils.js +48 -0
  565. package/dist/viewer/utils.js.map +1 -0
  566. package/dist/viewer/validate.d.ts +23 -0
  567. package/dist/viewer/validate.js +275 -0
  568. package/dist/viewer/validate.js.map +1 -0
  569. package/dist/viewer/views/analysis.d.ts +10 -0
  570. package/dist/viewer/views/analysis.js +109 -0
  571. package/dist/viewer/views/analysis.js.map +1 -0
  572. package/dist/viewer/views/architecture.d.ts +10 -0
  573. package/dist/viewer/views/architecture.js +44 -0
  574. package/dist/viewer/views/architecture.js.map +1 -0
  575. package/dist/viewer/views/domain-hench.d.ts +13 -0
  576. package/dist/viewer/views/domain-hench.js +14 -0
  577. package/dist/viewer/views/domain-hench.js.map +1 -0
  578. package/dist/viewer/views/domain-rex.d.ts +17 -0
  579. package/dist/viewer/views/domain-rex.js +18 -0
  580. package/dist/viewer/views/domain-rex.js.map +1 -0
  581. package/dist/viewer/views/domain-settings.d.ts +12 -0
  582. package/dist/viewer/views/domain-settings.js +13 -0
  583. package/dist/viewer/views/domain-settings.js.map +1 -0
  584. package/dist/viewer/views/domain-sourcevision.d.ts +20 -0
  585. package/dist/viewer/views/domain-sourcevision.js +21 -0
  586. package/dist/viewer/views/domain-sourcevision.js.map +1 -0
  587. package/dist/viewer/views/enrichment-thresholds.d.ts +11 -0
  588. package/dist/viewer/views/enrichment-thresholds.js +12 -0
  589. package/dist/viewer/views/enrichment-thresholds.js.map +1 -0
  590. package/dist/viewer/views/feature-toggles.d.ts +13 -0
  591. package/dist/viewer/views/feature-toggles.js +185 -0
  592. package/dist/viewer/views/feature-toggles.js.map +1 -0
  593. package/dist/viewer/views/files.d.ts +13 -0
  594. package/dist/viewer/views/files.js +174 -0
  595. package/dist/viewer/views/files.js.map +1 -0
  596. package/dist/viewer/views/graph.d.ts +12 -0
  597. package/dist/viewer/views/graph.js +316 -0
  598. package/dist/viewer/views/graph.js.map +1 -0
  599. package/dist/viewer/views/hench-config.d.ts +39 -0
  600. package/dist/viewer/views/hench-config.js +473 -0
  601. package/dist/viewer/views/hench-config.js.map +1 -0
  602. package/dist/viewer/views/hench-runs.d.ts +19 -0
  603. package/dist/viewer/views/hench-runs.js +460 -0
  604. package/dist/viewer/views/hench-runs.js.map +1 -0
  605. package/dist/viewer/views/hench-templates.d.ts +17 -0
  606. package/dist/viewer/views/hench-templates.js +262 -0
  607. package/dist/viewer/views/hench-templates.js.map +1 -0
  608. package/dist/viewer/views/integration-config.d.ts +73 -0
  609. package/dist/viewer/views/integration-config.js +524 -0
  610. package/dist/viewer/views/integration-config.js.map +1 -0
  611. package/dist/viewer/views/notion-config.d.ts +16 -0
  612. package/dist/viewer/views/notion-config.js +357 -0
  613. package/dist/viewer/views/notion-config.js.map +1 -0
  614. package/dist/viewer/views/overview.d.ts +10 -0
  615. package/dist/viewer/views/overview.js +187 -0
  616. package/dist/viewer/views/overview.js.map +1 -0
  617. package/dist/viewer/views/pr-markdown.d.ts +3 -0
  618. package/dist/viewer/views/pr-markdown.js +350 -0
  619. package/dist/viewer/views/pr-markdown.js.map +1 -0
  620. package/dist/viewer/views/prd.d.ts +34 -0
  621. package/dist/viewer/views/prd.js +257 -0
  622. package/dist/viewer/views/prd.js.map +1 -0
  623. package/dist/viewer/views/problems.d.ts +8 -0
  624. package/dist/viewer/views/problems.js +50 -0
  625. package/dist/viewer/views/problems.js.map +1 -0
  626. package/dist/viewer/views/rex-dashboard.d.ts +14 -0
  627. package/dist/viewer/views/rex-dashboard.js +334 -0
  628. package/dist/viewer/views/rex-dashboard.js.map +1 -0
  629. package/dist/viewer/views/routes.d.ts +8 -0
  630. package/dist/viewer/views/routes.js +216 -0
  631. package/dist/viewer/views/routes.js.map +1 -0
  632. package/dist/viewer/views/sourcevision-tabs.d.ts +18 -0
  633. package/dist/viewer/views/sourcevision-tabs.js +14 -0
  634. package/dist/viewer/views/sourcevision-tabs.js.map +1 -0
  635. package/dist/viewer/views/suggestions.d.ts +8 -0
  636. package/dist/viewer/views/suggestions.js +36 -0
  637. package/dist/viewer/views/suggestions.js.map +1 -0
  638. package/dist/viewer/views/task-audit.d.ts +18 -0
  639. package/dist/viewer/views/task-audit.js +413 -0
  640. package/dist/viewer/views/task-audit.js.map +1 -0
  641. package/dist/viewer/views/token-usage.d.ts +10 -0
  642. package/dist/viewer/views/token-usage.js +410 -0
  643. package/dist/viewer/views/token-usage.js.map +1 -0
  644. package/dist/viewer/views/validation.d.ts +11 -0
  645. package/dist/viewer/views/validation.js +475 -0
  646. package/dist/viewer/views/validation.js.map +1 -0
  647. package/dist/viewer/views/view-registry.d.ts +27 -0
  648. package/dist/viewer/views/view-registry.js +70 -0
  649. package/dist/viewer/views/view-registry.js.map +1 -0
  650. package/dist/viewer/views/workflow-optimization.d.ts +12 -0
  651. package/dist/viewer/views/workflow-optimization.js +311 -0
  652. package/dist/viewer/views/workflow-optimization.js.map +1 -0
  653. package/dist/viewer/views/zone-types.d.ts +69 -0
  654. package/dist/viewer/views/zone-types.js +5 -0
  655. package/dist/viewer/views/zone-types.js.map +1 -0
  656. package/dist/viewer/views/zones.d.ts +50 -0
  657. package/dist/viewer/views/zones.js +1438 -0
  658. package/dist/viewer/views/zones.js.map +1 -0
  659. package/dist/viewer/visualization/colors.d.ts +16 -0
  660. package/dist/viewer/visualization/colors.js +31 -0
  661. package/dist/viewer/visualization/colors.js.map +1 -0
  662. package/dist/viewer/visualization/flow.d.ts +54 -0
  663. package/dist/viewer/visualization/flow.js +123 -0
  664. package/dist/viewer/visualization/flow.js.map +1 -0
  665. package/dist/viewer/visualization/index.d.ts +34 -0
  666. package/dist/viewer/visualization/index.js +40 -0
  667. package/dist/viewer/visualization/index.js.map +1 -0
  668. package/dist/viewer/visualization/metrics.d.ts +8 -0
  669. package/dist/viewer/visualization/metrics.js +16 -0
  670. package/dist/viewer/visualization/metrics.js.map +1 -0
  671. package/package.json +54 -0
@@ -0,0 +1,1438 @@
1
+ /**
2
+ * Zones — SVG box-and-line zone diagram with slideout details.
3
+ *
4
+ * Zones rendered as rectangular boxes on a topology-aware grid,
5
+ * connected by Bézier edges weighted by call traffic.
6
+ * Zones expand on click to reveal file rows inside.
7
+ * When expanded, file-level edges show which files bridge zones.
8
+ * Clicking a zone opens a slideout panel with details.
9
+ */
10
+ import { h } from "preact";
11
+ import { useState, useMemo, useCallback, useEffect } from "preact/hooks";
12
+ import { CollapsibleSection, buildFileToZoneMap, buildFlowEdges, buildCallFlowEdges, buildExternalImportEdges, getZoneColorByIndex, } from "../visualization/index.js";
13
+ import { basename } from "../utils.js";
14
+ import { SearchFilter } from "../components/search-filter.js";
15
+ import { BrandedHeader } from "../components/logos.js";
16
+ import { ZoneSlideout } from "../components/zone-slideout.js";
17
+ import { usePanZoom, useZoneDrag, useFileEdges, useSubZoneEdges } from "../hooks/index.js";
18
+ // ── Constants ────────────────────────────────────────────────────────
19
+ const BOX_W = 200;
20
+ const BOX_H_COLLAPSED = 80;
21
+ const FILE_ROW_H = 22;
22
+ const FILE_ROWS_MAX = 15;
23
+ const GAP_X = 80;
24
+ const GAP_Y = 60;
25
+ const PADDING = 40;
26
+ const SUBZONE_ROW_H = 28;
27
+ const SUBZONE_ROWS_MAX = 10;
28
+ const SUBZONE_FILE_INDENT = 12;
29
+ // ── Data transformation ──────────────────────────────────────────────
30
+ /**
31
+ * Convert raw Zone sub-zones into ZoneData for drill-down display.
32
+ * Uses zone metadata only (file counts, descriptions) since full call
33
+ * graph enrichment is scoped to the top-level analysis.
34
+ *
35
+ * @internal Exported for testing.
36
+ */
37
+ export function convertSubZones(subZones) {
38
+ return subZones.map((sz, i) => {
39
+ const subData = {
40
+ id: sz.id,
41
+ name: sz.name,
42
+ color: getZoneColorByIndex(i),
43
+ description: sz.description,
44
+ cohesion: sz.cohesion,
45
+ coupling: sz.coupling,
46
+ files: [],
47
+ totalFiles: sz.files.length,
48
+ totalFunctions: 0,
49
+ internalCalls: 0,
50
+ crossZoneCalls: 0,
51
+ };
52
+ // Recurse if deeper levels exist
53
+ if (sz.subZones && sz.subZones.length > 0) {
54
+ subData.subZones = convertSubZones(sz.subZones);
55
+ subData.subCrossings = convertCrossings(sz.subCrossings);
56
+ subData.hasDrillDown = true;
57
+ }
58
+ return subData;
59
+ });
60
+ }
61
+ /**
62
+ * Convert ZoneCrossing[] to FlowEdge[] (aggregate by zone pair).
63
+ *
64
+ * @internal Exported for testing.
65
+ */
66
+ export function convertCrossings(crossings) {
67
+ if (!crossings || crossings.length === 0)
68
+ return [];
69
+ const pairCounts = new Map();
70
+ for (const c of crossings) {
71
+ const key = `${c.fromZone}->${c.toZone}`;
72
+ pairCounts.set(key, (pairCounts.get(key) ?? 0) + 1);
73
+ }
74
+ return [...pairCounts.entries()].map(([key, weight]) => {
75
+ const [from, to] = key.split("->");
76
+ return { from, to, weight };
77
+ });
78
+ }
79
+ /**
80
+ * Distribute a parent zone's enriched FileInfo[] to each subzone by matching
81
+ * file paths from the raw Zone.files string array.
82
+ */
83
+ function enrichSubZoneFiles(subZoneData, parentFiles, rawSubZones) {
84
+ const fileMap = new Map(parentFiles.map((f) => [f.path, f]));
85
+ for (let i = 0; i < subZoneData.length; i++) {
86
+ const rawSz = rawSubZones[i];
87
+ if (!rawSz)
88
+ continue;
89
+ const matched = [];
90
+ for (const path of rawSz.files) {
91
+ const info = fileMap.get(path);
92
+ if (info)
93
+ matched.push(info);
94
+ }
95
+ matched.sort((a, b) => b.crossZoneCalls - a.crossZoneCalls || a.path.localeCompare(b.path));
96
+ subZoneData[i].files = matched;
97
+ // Recurse if deeper subzones exist
98
+ if (subZoneData[i].subZones && rawSz.subZones) {
99
+ enrichSubZoneFiles(subZoneData[i].subZones, matched, rawSz.subZones);
100
+ }
101
+ }
102
+ }
103
+ function buildExplorerData(callGraph, fileToZoneMap, zones) {
104
+ const funcsByFile = new Map();
105
+ for (const fn of callGraph.functions) {
106
+ let list = funcsByFile.get(fn.file);
107
+ if (!list) {
108
+ list = [];
109
+ funcsByFile.set(fn.file, list);
110
+ }
111
+ list.push(fn);
112
+ }
113
+ const funcInfoMap = new Map();
114
+ for (const fn of callGraph.functions) {
115
+ funcInfoMap.set(`${fn.file}:${fn.qualifiedName}`, {
116
+ fn,
117
+ outgoing: [],
118
+ incoming: [],
119
+ });
120
+ }
121
+ const fileInternalCalls = new Map();
122
+ const fileCrossZoneCalls = new Map();
123
+ for (const edge of callGraph.edges) {
124
+ if (!edge.calleeFile)
125
+ continue;
126
+ const callerKey = `${edge.callerFile}:${edge.caller}`;
127
+ const calleeKey = `${edge.calleeFile}:${edge.callee}`;
128
+ const callerZone = fileToZoneMap.get(edge.callerFile);
129
+ const calleeZone = fileToZoneMap.get(edge.calleeFile);
130
+ const crossZone = !!(callerZone && calleeZone && callerZone.id !== calleeZone.id);
131
+ const callerInfo = funcInfoMap.get(callerKey);
132
+ if (callerInfo) {
133
+ callerInfo.outgoing.push({ funcName: edge.callee, file: edge.calleeFile, crossZone });
134
+ }
135
+ const calleeInfo = funcInfoMap.get(calleeKey);
136
+ if (calleeInfo) {
137
+ calleeInfo.incoming.push({ funcName: edge.caller, file: edge.callerFile, crossZone });
138
+ }
139
+ if (crossZone) {
140
+ fileCrossZoneCalls.set(edge.callerFile, (fileCrossZoneCalls.get(edge.callerFile) ?? 0) + 1);
141
+ fileCrossZoneCalls.set(edge.calleeFile, (fileCrossZoneCalls.get(edge.calleeFile) ?? 0) + 1);
142
+ }
143
+ else {
144
+ fileInternalCalls.set(edge.callerFile, (fileInternalCalls.get(edge.callerFile) ?? 0) + 1);
145
+ }
146
+ }
147
+ const zoneFilesMap = new Map();
148
+ const unzonedFiles = [];
149
+ for (const [filePath, fns] of funcsByFile) {
150
+ const funcInfos = fns.map((fn) => funcInfoMap.get(`${filePath}:${fn.qualifiedName}`)).filter(Boolean);
151
+ const fileInfo = {
152
+ path: filePath,
153
+ functions: funcInfos,
154
+ internalCalls: fileInternalCalls.get(filePath) ?? 0,
155
+ crossZoneCalls: fileCrossZoneCalls.get(filePath) ?? 0,
156
+ };
157
+ const zone = fileToZoneMap.get(filePath);
158
+ if (zone) {
159
+ let list = zoneFilesMap.get(zone.id);
160
+ if (!list) {
161
+ list = [];
162
+ zoneFilesMap.set(zone.id, list);
163
+ }
164
+ list.push(fileInfo);
165
+ }
166
+ else {
167
+ unzonedFiles.push(fileInfo);
168
+ }
169
+ }
170
+ const zoneDataList = [];
171
+ if (zones) {
172
+ for (let i = 0; i < zones.zones.length; i++) {
173
+ const z = zones.zones[i];
174
+ const files = zoneFilesMap.get(z.id) ?? [];
175
+ files.sort((a, b) => b.crossZoneCalls - a.crossZoneCalls || a.path.localeCompare(b.path));
176
+ const totalFunctions = files.reduce((sum, f) => sum + f.functions.length, 0);
177
+ const internalCalls = files.reduce((sum, f) => sum + f.internalCalls, 0);
178
+ const crossZoneCalls = files.reduce((sum, f) => sum + f.crossZoneCalls, 0);
179
+ const zd = {
180
+ id: z.id,
181
+ name: z.name,
182
+ color: getZoneColorByIndex(i),
183
+ description: z.description,
184
+ cohesion: z.cohesion,
185
+ coupling: z.coupling,
186
+ files,
187
+ totalFiles: z.files.length,
188
+ totalFunctions,
189
+ internalCalls,
190
+ crossZoneCalls,
191
+ };
192
+ // Attach sub-zone data for drill-down when available
193
+ if (z.subZones && z.subZones.length > 0) {
194
+ zd.subZones = convertSubZones(z.subZones);
195
+ enrichSubZoneFiles(zd.subZones, files, z.subZones);
196
+ zd.subCrossings = convertCrossings(z.subCrossings);
197
+ zd.hasDrillDown = true;
198
+ }
199
+ zoneDataList.push(zd);
200
+ }
201
+ }
202
+ unzonedFiles.sort((a, b) => a.path.localeCompare(b.path));
203
+ return { zoneDataList, unzonedFiles };
204
+ }
205
+ /**
206
+ * Build per-file cross-zone connection map.
207
+ * For each file, tracks which other zones it connects to (and weight).
208
+ */
209
+ function buildFileConnectionMap(callGraph, externalImports, fileToZoneMap, zones) {
210
+ // file → targetZoneId → weight
211
+ const raw = new Map();
212
+ const addConn = (file, targetZone) => {
213
+ let targets = raw.get(file);
214
+ if (!targets) {
215
+ targets = new Map();
216
+ raw.set(file, targets);
217
+ }
218
+ targets.set(targetZone, (targets.get(targetZone) ?? 0) + 1);
219
+ };
220
+ // From call graph edges
221
+ for (const e of callGraph.edges) {
222
+ if (!e.calleeFile)
223
+ continue;
224
+ const fromZone = fileToZoneMap.get(e.callerFile);
225
+ const toZone = fileToZoneMap.get(e.calleeFile);
226
+ if (!fromZone || !toZone || fromZone.id === toZone.id)
227
+ continue;
228
+ addConn(e.callerFile, toZone.id);
229
+ addConn(e.calleeFile, fromZone.id);
230
+ }
231
+ // From external imports: map package names to zones
232
+ if (zones) {
233
+ const dirToZone = new Map();
234
+ for (const z of zones.zones) {
235
+ for (const f of z.files) {
236
+ const parts = f.split("/");
237
+ if (parts.length >= 2 && parts[0] === "packages") {
238
+ const dir = `packages/${parts[1]}`;
239
+ const isSrc = f.includes("/src/");
240
+ const existing = dirToZone.get(dir);
241
+ if (!existing || (isSrc && !existing.hasSrc)) {
242
+ dirToZone.set(dir, { zoneId: z.id, hasSrc: isSrc });
243
+ }
244
+ }
245
+ }
246
+ }
247
+ const pkgToZone = new Map();
248
+ for (const ext of externalImports) {
249
+ const pkg = ext.package;
250
+ if (pkgToZone.has(pkg))
251
+ continue;
252
+ if (pkg.startsWith("@n-dx/")) {
253
+ const entry = dirToZone.get(`packages/${pkg.slice(6)}`);
254
+ if (entry) {
255
+ pkgToZone.set(pkg, entry.zoneId);
256
+ continue;
257
+ }
258
+ }
259
+ const entry = dirToZone.get(`packages/${pkg}`);
260
+ if (entry) {
261
+ pkgToZone.set(pkg, entry.zoneId);
262
+ }
263
+ }
264
+ for (const ext of externalImports) {
265
+ const targetZone = pkgToZone.get(ext.package);
266
+ if (!targetZone)
267
+ continue;
268
+ for (const file of ext.importedBy) {
269
+ const fromZone = fileToZoneMap.get(file);
270
+ if (!fromZone || fromZone.id === targetZone)
271
+ continue;
272
+ addConn(file, targetZone);
273
+ }
274
+ }
275
+ }
276
+ // Convert to result format
277
+ const result = new Map();
278
+ for (const [file, targets] of raw) {
279
+ result.set(file, [...targets.entries()].map(([zoneId, weight]) => ({
280
+ targetZoneId: zoneId,
281
+ weight,
282
+ })));
283
+ }
284
+ return result;
285
+ }
286
+ /**
287
+ * Build file-to-file cross-zone connection map from call graph edges.
288
+ * Used when both source and target zones are expanded to draw file-level edges.
289
+ */
290
+ function buildFileToFileMap(callGraph, fileToZoneMap) {
291
+ const result = new Map();
292
+ const addEdge = (from, to) => {
293
+ let targets = result.get(from);
294
+ if (!targets) {
295
+ targets = new Map();
296
+ result.set(from, targets);
297
+ }
298
+ targets.set(to, (targets.get(to) ?? 0) + 1);
299
+ };
300
+ for (const e of callGraph.edges) {
301
+ if (!e.calleeFile)
302
+ continue;
303
+ const fromZone = fileToZoneMap.get(e.callerFile);
304
+ const toZone = fileToZoneMap.get(e.calleeFile);
305
+ if (!fromZone || !toZone || fromZone.id === toZone.id)
306
+ continue;
307
+ addEdge(e.callerFile, e.calleeFile);
308
+ }
309
+ return result;
310
+ }
311
+ // ── Layout pure functions ────────────────────────────────────────────
312
+ function boxHeight(zone, expanded, expandedSubZoneIds) {
313
+ if (!expanded)
314
+ return BOX_H_COLLAPSED;
315
+ // Zone has subzones → show subzone rows instead of files
316
+ if (zone.subZones && zone.subZones.length > 0) {
317
+ const visibleCount = Math.min(zone.subZones.length, SUBZONE_ROWS_MAX);
318
+ let h = BOX_H_COLLAPSED + visibleCount * SUBZONE_ROW_H + 16;
319
+ // Expanded subzones show nested files
320
+ if (expandedSubZoneIds) {
321
+ for (const sz of zone.subZones.slice(0, SUBZONE_ROWS_MAX)) {
322
+ if (expandedSubZoneIds.has(sz.id)) {
323
+ const fileRows = Math.min(sz.files.length, FILE_ROWS_MAX);
324
+ h += fileRows * FILE_ROW_H + (sz.files.length > FILE_ROWS_MAX ? 20 : 0);
325
+ }
326
+ }
327
+ }
328
+ if (zone.subZones.length > SUBZONE_ROWS_MAX)
329
+ h += 20;
330
+ return h;
331
+ }
332
+ // No subzones → show files directly
333
+ const rows = Math.min(zone.files.length, FILE_ROWS_MAX);
334
+ return BOX_H_COLLAPSED + rows * FILE_ROW_H + 16 + (zone.files.length > FILE_ROWS_MAX ? 20 : 0);
335
+ }
336
+ function boxEdgeAnchor(box, tx, ty) {
337
+ const cx = box.x + box.w / 2;
338
+ const cy = box.y + box.h / 2;
339
+ const dx = tx - cx;
340
+ const dy = ty - cy;
341
+ if (dx === 0 && dy === 0)
342
+ return { x: cx, y: cy };
343
+ const hw = box.w / 2;
344
+ const hh = box.h / 2;
345
+ const sx = dx !== 0 ? hw / Math.abs(dx) : Infinity;
346
+ const sy = dy !== 0 ? hh / Math.abs(dy) : Infinity;
347
+ const s = Math.min(sx, sy);
348
+ return { x: cx + dx * s, y: cy + dy * s };
349
+ }
350
+ function computeZoneLayout(zones, edges, expandedZones, expandedSubZones) {
351
+ if (zones.length === 0)
352
+ return { boxes: new Map(), totalW: 0, totalH: 0 };
353
+ // Build adjacency weight map
354
+ const adj = new Map();
355
+ for (const z of zones)
356
+ adj.set(z.id, new Map());
357
+ for (const e of edges) {
358
+ if (!adj.has(e.from) || !adj.has(e.to))
359
+ continue;
360
+ const m1 = adj.get(e.from);
361
+ m1.set(e.to, (m1.get(e.to) ?? 0) + e.weight);
362
+ const m2 = adj.get(e.to);
363
+ m2.set(e.from, (m2.get(e.from) ?? 0) + e.weight);
364
+ }
365
+ const totalWeight = new Map();
366
+ for (const [id, neighbors] of adj) {
367
+ let sum = 0;
368
+ for (const w of neighbors.values())
369
+ sum += w;
370
+ totalWeight.set(id, sum);
371
+ }
372
+ const sorted = [...zones].sort((a, b) => (totalWeight.get(b.id) ?? 0) - (totalWeight.get(a.id) ?? 0));
373
+ const placed = new Map();
374
+ const occupied = new Set();
375
+ const gridKey = (c, r) => `${c},${r}`;
376
+ placed.set(sorted[0].id, { col: 0, row: 0 });
377
+ occupied.add(gridKey(0, 0));
378
+ const queue = [sorted[0].id];
379
+ const visited = new Set([sorted[0].id]);
380
+ const getNeighborsSorted = (id) => {
381
+ const neighbors = adj.get(id);
382
+ if (!neighbors)
383
+ return [];
384
+ return [...neighbors.entries()]
385
+ .filter(([nid]) => !visited.has(nid))
386
+ .sort((a, b) => b[1] - a[1])
387
+ .map(([nid]) => nid);
388
+ };
389
+ while (queue.length > 0) {
390
+ const current = queue.shift();
391
+ const currentPos = placed.get(current);
392
+ const neighbors = getNeighborsSorted(current);
393
+ for (const nid of neighbors) {
394
+ if (visited.has(nid))
395
+ continue;
396
+ visited.add(nid);
397
+ let bestCol = currentPos.col + 1;
398
+ let bestRow = currentPos.row;
399
+ let found = false;
400
+ for (let dist = 1; dist <= 10 && !found; dist++) {
401
+ for (let dc = -dist; dc <= dist && !found; dc++) {
402
+ for (let dr = -dist; dr <= dist && !found; dr++) {
403
+ if (Math.abs(dc) !== dist && Math.abs(dr) !== dist)
404
+ continue;
405
+ const c = currentPos.col + dc;
406
+ const r = currentPos.row + dr;
407
+ if (!occupied.has(gridKey(c, r))) {
408
+ bestCol = c;
409
+ bestRow = r;
410
+ found = true;
411
+ }
412
+ }
413
+ }
414
+ }
415
+ placed.set(nid, { col: bestCol, row: bestRow });
416
+ occupied.add(gridKey(bestCol, bestRow));
417
+ queue.push(nid);
418
+ }
419
+ }
420
+ for (const z of zones) {
421
+ if (placed.has(z.id))
422
+ continue;
423
+ for (let r = 0;; r++) {
424
+ for (let c = 0; c < 4; c++) {
425
+ if (!occupied.has(gridKey(c, r))) {
426
+ placed.set(z.id, { col: c, row: r });
427
+ occupied.add(gridKey(c, r));
428
+ break;
429
+ }
430
+ }
431
+ if (placed.has(z.id))
432
+ break;
433
+ }
434
+ }
435
+ let minCol = Infinity, minRow = Infinity;
436
+ for (const pos of placed.values()) {
437
+ minCol = Math.min(minCol, pos.col);
438
+ minRow = Math.min(minRow, pos.row);
439
+ }
440
+ for (const pos of placed.values()) {
441
+ pos.col -= minCol;
442
+ pos.row -= minRow;
443
+ }
444
+ let maxCol = 0;
445
+ let maxRow = 0;
446
+ for (const pos of placed.values()) {
447
+ maxCol = Math.max(maxCol, pos.col);
448
+ maxRow = Math.max(maxRow, pos.row);
449
+ }
450
+ const rowHeights = new Array(maxRow + 1).fill(BOX_H_COLLAPSED);
451
+ const zoneMap = new Map(zones.map((z) => [z.id, z]));
452
+ for (const [id, pos] of placed) {
453
+ const zone = zoneMap.get(id);
454
+ if (!zone)
455
+ continue;
456
+ const bh = boxHeight(zone, expandedZones.has(id), expandedSubZones?.get(id));
457
+ rowHeights[pos.row] = Math.max(rowHeights[pos.row], bh);
458
+ }
459
+ const boxes = new Map();
460
+ for (const [id, pos] of placed) {
461
+ const zone = zoneMap.get(id);
462
+ if (!zone)
463
+ continue;
464
+ const x = PADDING + pos.col * (BOX_W + GAP_X);
465
+ let y = PADDING;
466
+ for (let r = 0; r < pos.row; r++) {
467
+ y += rowHeights[r] + GAP_Y;
468
+ }
469
+ boxes.set(id, {
470
+ x,
471
+ y,
472
+ w: BOX_W,
473
+ h: boxHeight(zone, expandedZones.has(id), expandedSubZones?.get(id)),
474
+ gridCol: pos.col,
475
+ gridRow: pos.row,
476
+ });
477
+ }
478
+ const totalW = PADDING * 2 + (maxCol + 1) * BOX_W + maxCol * GAP_X;
479
+ let totalH = PADDING * 2;
480
+ for (const rh of rowHeights)
481
+ totalH += rh;
482
+ totalH += maxRow * GAP_Y;
483
+ return { boxes, totalW, totalH };
484
+ }
485
+ function computeEdgePath(fromBox, toBox, edgeIndex, totalEdgesBetweenPair) {
486
+ const fromCx = fromBox.x + fromBox.w / 2;
487
+ const fromCy = fromBox.y + fromBox.h / 2;
488
+ const toCx = toBox.x + toBox.w / 2;
489
+ const toCy = toBox.y + toBox.h / 2;
490
+ const from = boxEdgeAnchor(fromBox, toCx, toCy);
491
+ const to = boxEdgeAnchor(toBox, fromCx, fromCy);
492
+ const mx = (from.x + to.x) / 2;
493
+ const my = (from.y + to.y) / 2;
494
+ const dx = to.x - from.x;
495
+ const dy = to.y - from.y;
496
+ const len = Math.sqrt(dx * dx + dy * dy) || 1;
497
+ const px = -dy / len;
498
+ const py = dx / len;
499
+ const offset = totalEdgesBetweenPair > 1
500
+ ? (edgeIndex - (totalEdgesBetweenPair - 1) / 2) * 25
501
+ : (len > 200 ? 20 : 12);
502
+ const cpx = mx + px * offset;
503
+ const cpy = my + py * offset;
504
+ return `M ${from.x} ${from.y} Q ${cpx} ${cpy} ${to.x} ${to.y}`;
505
+ }
506
+ // ── Sub-components ───────────────────────────────────────────────────
507
+ function StatCard({ value, label, color }) {
508
+ return h("div", {
509
+ class: "stat-card",
510
+ style: "padding: 8px 16px; background: var(--bg-surface); border: 1px solid var(--border); border-radius: 8px; font-size: 12px;",
511
+ }, h("div", { style: `font-weight: 600; color: ${color ?? "var(--text)"};` }, value), h("div", { style: "color: var(--text-dim);" }, label));
512
+ }
513
+ /** Chevron separator for zone breadcrumb trail. */
514
+ function ZoneBreadcrumbSep() {
515
+ return h("svg", {
516
+ class: "zone-breadcrumb-sep",
517
+ width: 12,
518
+ height: 12,
519
+ viewBox: "0 0 12 12",
520
+ fill: "none",
521
+ stroke: "currentColor",
522
+ "stroke-width": "1.5",
523
+ "stroke-linecap": "round",
524
+ "aria-hidden": "true",
525
+ }, h("path", { d: "M4.5 2.5l3 3.5-3 3.5" }));
526
+ }
527
+ /**
528
+ * Drill-down breadcrumb trail rendered above the zone diagram.
529
+ *
530
+ * Hidden at root level (drillPath has only the root entry).
531
+ * Clicking a crumb navigates back to that level by truncating the drill path.
532
+ *
533
+ * @internal Exported for testing.
534
+ */
535
+ export function ZoneBreadcrumbNav({ drillPath, onNavigate, }) {
536
+ // Hidden at root — no unnecessary UI
537
+ if (drillPath.length <= 1)
538
+ return null;
539
+ return h("nav", {
540
+ class: "zone-breadcrumb",
541
+ "aria-label": "Zone navigation",
542
+ }, h("ol", { class: "zone-breadcrumb-list" }, ...drillPath.map((crumb, i) => {
543
+ const isLast = i === drillPath.length - 1;
544
+ return h("li", {
545
+ key: crumb.zoneId ?? "root",
546
+ class: `zone-breadcrumb-item${isLast ? " zone-breadcrumb-current" : ""}`,
547
+ }, isLast
548
+ // Current level — plain text, not clickable
549
+ ? h("span", { "aria-current": "location" }, crumb.label)
550
+ // Ancestor level — clickable to pop back
551
+ : [
552
+ h("button", {
553
+ class: "zone-breadcrumb-link",
554
+ type: "button",
555
+ onClick: () => onNavigate(i),
556
+ }, crumb.label),
557
+ ZoneBreadcrumbSep(),
558
+ ]);
559
+ })));
560
+ }
561
+ function FileRow({ file, y, boxX, boxW, searchMatch, hasCrossZone, onClick, onDblClick, }) {
562
+ const totalIn = file.functions.reduce((s, fi) => s + fi.incoming.length, 0);
563
+ const totalOut = file.functions.reduce((s, fi) => s + fi.outgoing.length, 0);
564
+ const name = basename(file.path);
565
+ const stats = `${file.functions.length}fn`;
566
+ const arrows = `${totalIn > 0 ? "\u2190" + totalIn : ""}${totalOut > 0 ? "\u2192" + totalOut : ""}`;
567
+ return h("g", {
568
+ class: `cg-file-row${searchMatch ? " search-match" : ""}${hasCrossZone ? " cross-zone" : ""}`,
569
+ onClick: (e) => { e.stopPropagation(); onClick(); },
570
+ onDblClick: (e) => { e.stopPropagation(); onDblClick(); },
571
+ }, h("rect", {
572
+ class: "cg-file-bg",
573
+ x: boxX + 8,
574
+ y,
575
+ width: boxW - 16,
576
+ height: FILE_ROW_H - 2,
577
+ rx: 3,
578
+ }),
579
+ // Cross-zone indicator bar
580
+ hasCrossZone
581
+ ? h("rect", {
582
+ class: "cg-file-xzone-bar",
583
+ x: boxX + 8,
584
+ y,
585
+ width: 2,
586
+ height: FILE_ROW_H - 2,
587
+ rx: 1,
588
+ })
589
+ : null, h("text", {
590
+ class: "cg-file-name",
591
+ x: boxX + (hasCrossZone ? 16 : 14),
592
+ y: y + 14,
593
+ }, name.length > 20 ? name.slice(0, 18) + "\u2026" : name), h("text", {
594
+ class: "cg-file-stats",
595
+ x: boxX + boxW - 14,
596
+ y: y + 14,
597
+ "text-anchor": "end",
598
+ }, `${stats} ${arrows}`));
599
+ }
600
+ function ZoneEdge({ edge, fromBox, toBox, maxWeight, edgeIndex, totalEdgesBetweenPair, hovered, onHover, onLeave, }) {
601
+ const d = computeEdgePath(fromBox, toBox, edgeIndex, totalEdgesBetweenPair);
602
+ const isHeavy = edge.weight > maxWeight * 0.5;
603
+ const strokeW = Math.max(1.5, Math.min(6, (edge.weight / maxWeight) * 5 + 1));
604
+ return h("g", null, h("path", {
605
+ d,
606
+ fill: "none",
607
+ stroke: "transparent",
608
+ "stroke-width": Math.max(12, strokeW + 8),
609
+ onMouseEnter: onHover,
610
+ onMouseLeave: onLeave,
611
+ style: "cursor: pointer;",
612
+ }), h("path", {
613
+ class: `cg-edge${isHeavy ? " heavy" : ""}${hovered ? " hovered" : ""}`,
614
+ d,
615
+ fill: "none",
616
+ "stroke-width": hovered ? strokeW + 1 : strokeW,
617
+ "marker-end": "url(#cg-arrow)",
618
+ style: "pointer-events: none;",
619
+ }));
620
+ }
621
+ function EdgeLabel({ edge, fromBox, toBox, hovered, }) {
622
+ const fromCx = fromBox.x + fromBox.w / 2;
623
+ const fromCy = fromBox.y + fromBox.h / 2;
624
+ const toCx = toBox.x + toBox.w / 2;
625
+ const toCy = toBox.y + toBox.h / 2;
626
+ const from = boxEdgeAnchor(fromBox, toCx, toCy);
627
+ const to = boxEdgeAnchor(toBox, fromCx, fromCy);
628
+ const mx = (from.x + to.x) / 2;
629
+ const my = (from.y + to.y) / 2;
630
+ return h("text", {
631
+ class: `cg-edge-label${hovered ? " visible" : ""}`,
632
+ x: mx,
633
+ y: my - 8,
634
+ "text-anchor": "middle",
635
+ }, `${edge.weight} call${edge.weight !== 1 ? "s" : ""}`);
636
+ }
637
+ function SubZoneRow({ subZone, y, boxX, boxW, expanded, onToggle, }) {
638
+ const name = subZone.name.length > 14
639
+ ? subZone.name.slice(0, 12) + "\u2026"
640
+ : subZone.name;
641
+ return h("g", {
642
+ class: `cg-subzone-row${expanded ? " expanded" : ""}`,
643
+ style: `--zone-color: ${subZone.color}`,
644
+ onClick: (e) => { e.stopPropagation(); onToggle(); },
645
+ }, h("rect", {
646
+ class: "cg-subzone-bg",
647
+ x: boxX + 6,
648
+ y,
649
+ width: boxW - 12,
650
+ height: SUBZONE_ROW_H - 2,
651
+ rx: 4,
652
+ }),
653
+ // Color bar
654
+ h("rect", {
655
+ x: boxX + 6,
656
+ y,
657
+ width: 3,
658
+ height: SUBZONE_ROW_H - 2,
659
+ rx: 1.5,
660
+ fill: subZone.color,
661
+ style: "pointer-events: none;",
662
+ }),
663
+ // Name
664
+ h("text", {
665
+ class: "cg-subzone-name",
666
+ x: boxX + 14,
667
+ y: y + 18,
668
+ }, name),
669
+ // File count
670
+ h("text", {
671
+ class: "cg-subzone-stats",
672
+ x: boxX + boxW - 30,
673
+ y: y + 18,
674
+ "text-anchor": "end",
675
+ }, `${subZone.totalFiles}f`),
676
+ // Expand chevron
677
+ h("text", {
678
+ class: "cg-subzone-expand-icon",
679
+ x: boxX + boxW - 16,
680
+ y: y + 18,
681
+ "text-anchor": "end",
682
+ }, expanded ? "\u25B4" : "\u25BE"));
683
+ }
684
+ function ZoneBox({ zone, box, expanded, selected, dimmed, searchQ, matchingFiles, fileConnections, expandedSubZoneIds, onToggle, onSelectZone, onSelectFile, onDblClickFile, onDrillDown, onToggleSubZone, }) {
685
+ const fileCount = zone.totalFiles;
686
+ const hasSubZones = !!(zone.subZones && zone.subZones.length > 0);
687
+ // Build expanded content elements
688
+ const renderFileContent = () => {
689
+ const visibleFiles = zone.files.slice(0, FILE_ROWS_MAX);
690
+ const overflow = zone.files.length - FILE_ROWS_MAX;
691
+ return [
692
+ ...visibleFiles.map((file, i) => {
693
+ const fy = box.y + BOX_H_COLLAPSED - 4 + i * FILE_ROW_H;
694
+ const isMatch = !searchQ || matchingFiles.has(file.path);
695
+ const hasCrossZone = fileConnections.has(file.path);
696
+ return h(FileRow, {
697
+ key: file.path,
698
+ file,
699
+ y: fy,
700
+ boxX: box.x,
701
+ boxW: box.w,
702
+ searchMatch: searchQ ? isMatch : false,
703
+ hasCrossZone,
704
+ onClick: () => onSelectFile(file.path),
705
+ onDblClick: () => onDblClickFile(file.path),
706
+ });
707
+ }),
708
+ overflow > 0
709
+ ? h("text", {
710
+ key: "overflow",
711
+ class: "cg-file-overflow",
712
+ x: box.x + 14,
713
+ y: box.y + BOX_H_COLLAPSED - 4 + FILE_ROWS_MAX * FILE_ROW_H + 14,
714
+ }, `+${overflow} more`)
715
+ : null,
716
+ ];
717
+ };
718
+ const renderSubZoneContent = () => {
719
+ if (!zone.subZones)
720
+ return [];
721
+ const visible = zone.subZones.slice(0, SUBZONE_ROWS_MAX);
722
+ const overflow = zone.subZones.length - SUBZONE_ROWS_MAX;
723
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
724
+ const elements = [];
725
+ let curY = box.y + BOX_H_COLLAPSED - 4;
726
+ for (const sz of visible) {
727
+ const szExpanded = expandedSubZoneIds?.has(sz.id) ?? false;
728
+ elements.push(h(SubZoneRow, {
729
+ key: sz.id,
730
+ subZone: sz,
731
+ y: curY,
732
+ boxX: box.x,
733
+ boxW: box.w,
734
+ expanded: szExpanded,
735
+ onToggle: () => onToggleSubZone?.(sz.id),
736
+ }));
737
+ curY += SUBZONE_ROW_H;
738
+ // Nested file rows when subzone is expanded
739
+ if (szExpanded) {
740
+ const szFiles = sz.files.slice(0, FILE_ROWS_MAX);
741
+ const szOverflow = sz.files.length - FILE_ROWS_MAX;
742
+ for (let fi = 0; fi < szFiles.length; fi++) {
743
+ const file = szFiles[fi];
744
+ const fy = curY;
745
+ const hasCrossZone = fileConnections.has(file.path);
746
+ elements.push(h(FileRow, {
747
+ key: `${sz.id}-${file.path}`,
748
+ file,
749
+ y: fy,
750
+ boxX: box.x + SUBZONE_FILE_INDENT,
751
+ boxW: box.w - SUBZONE_FILE_INDENT,
752
+ searchMatch: searchQ ? matchingFiles.has(file.path) : false,
753
+ hasCrossZone,
754
+ onClick: () => onSelectFile(file.path),
755
+ onDblClick: () => onDblClickFile(file.path),
756
+ }));
757
+ curY += FILE_ROW_H;
758
+ }
759
+ if (szOverflow > 0) {
760
+ elements.push(h("text", {
761
+ key: `${sz.id}-overflow`,
762
+ class: "cg-file-overflow",
763
+ x: box.x + SUBZONE_FILE_INDENT + 14,
764
+ y: curY + 14,
765
+ }, `+${szOverflow} more`));
766
+ curY += 20;
767
+ }
768
+ }
769
+ }
770
+ if (overflow > 0) {
771
+ elements.push(h("text", {
772
+ key: "sz-overflow",
773
+ class: "cg-file-overflow",
774
+ x: box.x + 14,
775
+ y: curY + 14,
776
+ }, `+${overflow} more sub-zones`));
777
+ }
778
+ return elements;
779
+ };
780
+ return h("g", {
781
+ class: `cg-zone-box${expanded ? " expanded" : ""}${selected ? " selected" : ""}${dimmed ? " search-dim" : ""}`,
782
+ "data-zone-id": zone.id,
783
+ style: "cursor: grab;",
784
+ }, h("rect", {
785
+ class: "cg-zone-rect",
786
+ x: box.x,
787
+ y: box.y,
788
+ width: box.w,
789
+ height: box.h,
790
+ rx: 8,
791
+ style: `--zone-color: ${zone.color};`,
792
+ }), h("rect", {
793
+ x: box.x,
794
+ y: box.y,
795
+ width: 4,
796
+ height: box.h,
797
+ rx: 2,
798
+ fill: zone.color,
799
+ style: "pointer-events: none;",
800
+ }), h("text", {
801
+ class: "cg-zone-name",
802
+ x: box.x + 14,
803
+ y: box.y + 22,
804
+ }, zone.name), h("text", {
805
+ class: "cg-zone-stats",
806
+ x: box.x + 14,
807
+ y: box.y + 40,
808
+ }, `${fileCount} file${fileCount !== 1 ? "s" : ""} \u00B7 ${zone.totalFunctions} fn`), h("text", {
809
+ class: "cg-zone-stats",
810
+ x: box.x + 14,
811
+ y: box.y + 56,
812
+ }, `${zone.internalCalls + zone.crossZoneCalls} calls`, zone.crossZoneCalls > 0 ? ` \u00B7 ${zone.crossZoneCalls} cross-zone` : ""), h("g", {
813
+ class: "cg-zone-toggle-btn",
814
+ onMouseDown: (e) => e.stopPropagation(),
815
+ onClick: (e) => { e.stopPropagation(); onToggle(); },
816
+ style: "cursor: pointer;",
817
+ }, h("rect", {
818
+ x: box.x + box.w - 32,
819
+ y: box.y + 6,
820
+ width: 24,
821
+ height: 22,
822
+ rx: 4,
823
+ fill: "transparent",
824
+ style: "pointer-events: all;",
825
+ }), h("text", {
826
+ class: "cg-zone-expand-icon",
827
+ x: box.x + box.w - 18,
828
+ y: box.y + 22,
829
+ "text-anchor": "end",
830
+ style: "pointer-events: none;",
831
+ }, expanded ? "\u25B4" : "\u25BE")),
832
+ // Drill-down badge — visible only for zones with sub-zones
833
+ zone.hasDrillDown && zone.subZones && onDrillDown
834
+ ? h("g", {
835
+ class: "cg-zone-drill-btn",
836
+ onClick: (e) => { e.stopPropagation(); onDrillDown(); },
837
+ }, h("rect", {
838
+ x: box.x + box.w - 88,
839
+ y: box.y + 36,
840
+ width: 74,
841
+ height: 20,
842
+ rx: 4,
843
+ class: "cg-drill-btn-bg",
844
+ style: `--zone-color: ${zone.color}`,
845
+ }), h("text", {
846
+ x: box.x + box.w - 56,
847
+ y: box.y + 50,
848
+ "text-anchor": "middle",
849
+ class: "cg-drill-btn-label",
850
+ }, `${zone.subZones.length} sub-zones \u203A`))
851
+ : null, expanded
852
+ ? h("g", null,
853
+ // Detail button — opens sidebar
854
+ h("g", {
855
+ class: "cg-zone-detail-btn",
856
+ onClick: (e) => { e.stopPropagation(); onSelectZone(); },
857
+ }, h("title", null, "View zone details"), h("rect", {
858
+ x: box.x + box.w - 38,
859
+ y: box.y + 34,
860
+ width: 22,
861
+ height: 22,
862
+ rx: 11,
863
+ class: "cg-detail-btn-bg",
864
+ }), h("text", {
865
+ x: box.x + box.w - 27,
866
+ y: box.y + 49,
867
+ "text-anchor": "middle",
868
+ class: "cg-detail-btn-text",
869
+ }, "\u24D8")), h("line", {
870
+ x1: box.x + 8,
871
+ y1: box.y + BOX_H_COLLAPSED - 12,
872
+ x2: box.x + box.w - 8,
873
+ y2: box.y + BOX_H_COLLAPSED - 12,
874
+ class: "cg-zone-divider",
875
+ }), ...(hasSubZones ? renderSubZoneContent() : renderFileContent()))
876
+ : null);
877
+ }
878
+ function ZoomControls({ onZoomIn, onZoomOut, onFit, }) {
879
+ return h("div", { class: "cg-zoom-controls" }, h("button", {
880
+ class: "cg-zoom-btn",
881
+ onClick: onZoomIn,
882
+ title: "Zoom in",
883
+ "aria-label": "Zoom in",
884
+ }, "+"), h("button", {
885
+ class: "cg-zoom-btn",
886
+ onClick: onZoomOut,
887
+ title: "Zoom out",
888
+ "aria-label": "Zoom out",
889
+ }, "\u2212"), h("button", {
890
+ class: "cg-zoom-btn",
891
+ onClick: onFit,
892
+ title: "Fit to content",
893
+ "aria-label": "Fit to content",
894
+ }, "\u2922"));
895
+ }
896
+ // ── ZoneDiagram (decomposed) ─────────────────────────────────────────
897
+ function ZoneDiagram({ zones, edges, expandedZones, expandedSubZones, selectedZoneId, searchQ, fileConnections, fileToFileMap, onToggleZone, onSelectZone, onSelectFile, onDblClickFile, onDblClickZone, onDrillDown, onToggleSubZone, }) {
898
+ const [hoveredEdge, setHoveredEdge] = useState(null);
899
+ const zoneById = useMemo(() => new Map(zones.map((z) => [z.id, z])), [zones]);
900
+ // Layout computation
901
+ const { boxes: baseBoxes, totalW, totalH } = useMemo(() => computeZoneLayout(zones, edges, expandedZones, expandedSubZones), [zones, edges, expandedZones, expandedSubZones]);
902
+ const fitVB = useMemo(() => ({
903
+ x: 0,
904
+ y: 0,
905
+ w: Math.max(totalW, 400),
906
+ h: Math.max(totalH, 300),
907
+ }), [totalW, totalH]);
908
+ // Extracted hooks for interaction
909
+ const panZoom = usePanZoom(fitVB);
910
+ const zoneDrag = useZoneDrag(panZoom.svgRef, panZoom.viewBox);
911
+ // Apply drag offsets to computed boxes
912
+ const boxes = useMemo(() => {
913
+ if (zoneDrag.dragOffsets.size === 0)
914
+ return baseBoxes;
915
+ const result = new Map();
916
+ for (const [id, box] of baseBoxes) {
917
+ const off = zoneDrag.dragOffsets.get(id);
918
+ if (off) {
919
+ result.set(id, { ...box, x: box.x + off.dx, y: box.y + off.dy });
920
+ }
921
+ else {
922
+ result.set(id, box);
923
+ }
924
+ }
925
+ return result;
926
+ }, [baseBoxes, zoneDrag.dragOffsets]);
927
+ // Edge computations (extracted hooks)
928
+ const { fileEdgeElements, hiddenZoneEdges: fileHiddenEdges } = useFileEdges(edges, boxes, expandedZones, zoneById, fileConnections, fileToFileMap);
929
+ const { subZoneEdgeElements, hiddenZoneEdges: szHiddenEdges } = useSubZoneEdges(edges, boxes, expandedZones, expandedSubZones, zoneById, fileConnections);
930
+ const hiddenZoneEdges = useMemo(() => {
931
+ const merged = new Set(fileHiddenEdges);
932
+ for (const k of szHiddenEdges)
933
+ merged.add(k);
934
+ return merged;
935
+ }, [fileHiddenEdges, szHiddenEdges]);
936
+ // Edge weight stats
937
+ const maxWeight = useMemo(() => {
938
+ let max = 1;
939
+ for (const e of edges)
940
+ if (e.weight > max)
941
+ max = e.weight;
942
+ return max;
943
+ }, [edges]);
944
+ const pairCounts = useMemo(() => {
945
+ const counts = new Map();
946
+ for (const e of edges) {
947
+ const key = [e.from, e.to].sort().join("|");
948
+ counts.set(key, (counts.get(key) ?? 0) + 1);
949
+ }
950
+ return counts;
951
+ }, [edges]);
952
+ const pairIndices = useMemo(() => {
953
+ const indices = new Map();
954
+ const counters = new Map();
955
+ for (const e of edges) {
956
+ const key = [e.from, e.to].sort().join("|");
957
+ const idx = counters.get(key) ?? 0;
958
+ indices.set(`${e.from}->${e.to}`, idx);
959
+ counters.set(key, idx + 1);
960
+ }
961
+ return indices;
962
+ }, [edges]);
963
+ // Search/filter state
964
+ const { dimmedZones, matchingFilesByZone } = useMemo(() => {
965
+ if (!searchQ)
966
+ return { dimmedZones: new Set(), matchingFilesByZone: new Map() };
967
+ const q = searchQ.toLowerCase();
968
+ const dimmed = new Set();
969
+ const matching = new Map();
970
+ for (const z of zones) {
971
+ const zoneNameMatch = z.name.toLowerCase().includes(q);
972
+ const fileMatches = new Set();
973
+ for (const f of z.files) {
974
+ if (f.path.toLowerCase().includes(q) ||
975
+ f.functions.some((fi) => fi.fn.qualifiedName.toLowerCase().includes(q))) {
976
+ fileMatches.add(f.path);
977
+ }
978
+ }
979
+ matching.set(z.id, fileMatches);
980
+ if (!zoneNameMatch && fileMatches.size === 0) {
981
+ dimmed.add(z.id);
982
+ }
983
+ }
984
+ return { dimmedZones: dimmed, matchingFilesByZone: matching };
985
+ }, [zones, searchQ]);
986
+ // Combined mouse handlers that delegate to zone drag or pan/zoom
987
+ const handleMouseDown = useCallback((e) => {
988
+ const zoneEl = e.target?.closest(".cg-zone-box");
989
+ if (zoneEl) {
990
+ const zoneId = zoneEl.getAttribute("data-zone-id");
991
+ if (zoneId) {
992
+ zoneDrag.startDrag(zoneId, e);
993
+ return;
994
+ }
995
+ }
996
+ panZoom.startPan(e);
997
+ }, [zoneDrag, panZoom]);
998
+ const handleMouseMove = useCallback((e) => {
999
+ if (zoneDrag.isDragging()) {
1000
+ zoneDrag.moveDrag(e);
1001
+ return;
1002
+ }
1003
+ panZoom.movePan(e);
1004
+ }, [zoneDrag, panZoom]);
1005
+ const handleMouseUp = useCallback((_e) => {
1006
+ const clickedZoneId = zoneDrag.endDrag();
1007
+ if (clickedZoneId) {
1008
+ const zone = zoneById.get(clickedZoneId);
1009
+ if (zone)
1010
+ onSelectZone(zone);
1011
+ return;
1012
+ }
1013
+ panZoom.endPan();
1014
+ }, [zoneDrag, panZoom, zoneById, onSelectZone]);
1015
+ // Global mouseup listener for edge cases (mouse released outside SVG)
1016
+ useEffect(() => {
1017
+ const onUp = () => {
1018
+ const clickedZoneId = zoneDrag.endDrag();
1019
+ if (clickedZoneId) {
1020
+ const zone = zoneById.get(clickedZoneId);
1021
+ if (zone)
1022
+ onSelectZone(zone);
1023
+ }
1024
+ panZoom.endPan();
1025
+ };
1026
+ window.addEventListener("mouseup", onUp);
1027
+ return () => window.removeEventListener("mouseup", onUp);
1028
+ }, [zoneDrag, panZoom, zoneById, onSelectZone]);
1029
+ if (zones.length === 0)
1030
+ return null;
1031
+ const vbStr = `${panZoom.viewBox.x} ${panZoom.viewBox.y} ${panZoom.viewBox.w} ${panZoom.viewBox.h}`;
1032
+ return h("div", { class: "cg-diagram-container" }, h("svg", {
1033
+ ref: panZoom.svgRef,
1034
+ class: `cg-diagram${panZoom.panning ? " dragging" : ""}`,
1035
+ viewBox: vbStr,
1036
+ preserveAspectRatio: "xMidYMid meet",
1037
+ onWheel: panZoom.handleWheel,
1038
+ onMouseDown: handleMouseDown,
1039
+ onMouseMove: handleMouseMove,
1040
+ onMouseUp: handleMouseUp,
1041
+ onDblClick: (e) => {
1042
+ const zoneEl = e.target?.closest?.(".cg-zone-box[data-zone-id]");
1043
+ if (zoneEl) {
1044
+ const zoneId = zoneEl.getAttribute("data-zone-id");
1045
+ if (zoneId)
1046
+ onDblClickZone(zoneId);
1047
+ }
1048
+ },
1049
+ }, h("defs", null, h("marker", {
1050
+ id: "cg-arrow",
1051
+ viewBox: "0 0 10 8",
1052
+ refX: 9,
1053
+ refY: 4,
1054
+ markerWidth: 8,
1055
+ markerHeight: 6,
1056
+ orient: "auto-start-reverse",
1057
+ }, h("path", {
1058
+ d: "M 0 0 L 10 4 L 0 8 z",
1059
+ class: "cg-arrow-marker",
1060
+ }))),
1061
+ // Zone-level edges (hidden when expanded zones have file-level edges)
1062
+ h("g", { class: "cg-edges" }, edges.map((edge) => {
1063
+ const edgeKey = `${edge.from}->${edge.to}`;
1064
+ if (hiddenZoneEdges.has(edgeKey))
1065
+ return null;
1066
+ const fromBox = boxes.get(edge.from);
1067
+ const toBox = boxes.get(edge.to);
1068
+ if (!fromBox || !toBox)
1069
+ return null;
1070
+ const pairKey = [edge.from, edge.to].sort().join("|");
1071
+ const total = pairCounts.get(pairKey) ?? 1;
1072
+ const idx = pairIndices.get(edgeKey) ?? 0;
1073
+ return h(ZoneEdge, {
1074
+ key: edgeKey,
1075
+ edge,
1076
+ fromBox,
1077
+ toBox,
1078
+ maxWeight,
1079
+ edgeIndex: idx,
1080
+ totalEdgesBetweenPair: total,
1081
+ hovered: hoveredEdge === edgeKey,
1082
+ onHover: () => setHoveredEdge(edgeKey),
1083
+ onLeave: () => setHoveredEdge(null),
1084
+ });
1085
+ })),
1086
+ // Subzone-level edges (between subzone rows and external zones)
1087
+ h("g", { class: "cg-subzone-edges" }, subZoneEdgeElements.map((se) => h("path", {
1088
+ key: se.key,
1089
+ class: se.dashed ? "cg-subzone-edge" : "cg-file-edge",
1090
+ d: se.d,
1091
+ fill: "none",
1092
+ stroke: se.color,
1093
+ "stroke-width": Math.max(1, Math.min(2.5, se.weight * 0.4 + 0.8)),
1094
+ opacity: se.dashed ? 0.4 : 0.55,
1095
+ "stroke-dasharray": se.dashed ? "4 3" : undefined,
1096
+ }))),
1097
+ // File-level edges (from expanded zone files to other zones)
1098
+ h("g", { class: "cg-file-edges" }, fileEdgeElements.map((fe) => h("path", {
1099
+ key: fe.key,
1100
+ class: "cg-file-edge",
1101
+ d: fe.d,
1102
+ fill: "none",
1103
+ stroke: fe.color,
1104
+ "stroke-width": Math.max(1, Math.min(2.5, fe.weight * 0.4 + 0.8)),
1105
+ opacity: 0.55,
1106
+ }))),
1107
+ // Edge labels (only for visible zone-level edges)
1108
+ h("g", { class: "cg-edge-labels" }, edges.map((edge) => {
1109
+ const edgeKey = `${edge.from}->${edge.to}`;
1110
+ if (hiddenZoneEdges.has(edgeKey))
1111
+ return null;
1112
+ const fromBox = boxes.get(edge.from);
1113
+ const toBox = boxes.get(edge.to);
1114
+ if (!fromBox || !toBox)
1115
+ return null;
1116
+ return h(EdgeLabel, {
1117
+ key: `label-${edgeKey}`,
1118
+ edge,
1119
+ fromBox,
1120
+ toBox,
1121
+ hovered: hoveredEdge === edgeKey,
1122
+ });
1123
+ })),
1124
+ // Zone boxes (rendered last so they're on top)
1125
+ h("g", { class: "cg-zones" }, zones.map((zone) => {
1126
+ const box = boxes.get(zone.id);
1127
+ if (!box)
1128
+ return null;
1129
+ return h(ZoneBox, {
1130
+ key: zone.id,
1131
+ zone,
1132
+ box,
1133
+ expanded: expandedZones.has(zone.id),
1134
+ selected: selectedZoneId === zone.id,
1135
+ dimmed: dimmedZones.has(zone.id),
1136
+ searchQ,
1137
+ matchingFiles: matchingFilesByZone.get(zone.id) ?? new Set(),
1138
+ fileConnections,
1139
+ expandedSubZoneIds: expandedSubZones.get(zone.id),
1140
+ onToggle: () => onToggleZone(zone.id),
1141
+ onSelectZone: () => onSelectZone(zone),
1142
+ onSelectFile,
1143
+ onDblClickFile,
1144
+ onDrillDown: zone.hasDrillDown && onDrillDown
1145
+ ? () => onDrillDown(zone.id)
1146
+ : undefined,
1147
+ onToggleSubZone: (szId) => onToggleSubZone(zone.id, szId),
1148
+ });
1149
+ }))), h(ZoomControls, {
1150
+ onZoomIn: panZoom.handleZoomIn,
1151
+ onZoomOut: panZoom.handleZoomOut,
1152
+ onFit: panZoom.handleFit,
1153
+ }));
1154
+ }
1155
+ function TopFunctionsTables({ summary }) {
1156
+ const hasMostCalled = summary.mostCalled.length > 0;
1157
+ const hasMostCalling = summary.mostCalling.length > 0;
1158
+ if (!hasMostCalled && !hasMostCalling)
1159
+ return null;
1160
+ return h(CollapsibleSection, {
1161
+ title: "Top Functions",
1162
+ count: undefined,
1163
+ defaultOpen: false,
1164
+ }, h("div", { style: "display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 8px;" }, hasMostCalled
1165
+ ? h("div", null, h("h3", { style: "font-size: 14px; margin-bottom: 8px; color: var(--text);" }, "Most Called Functions"), h("table", { class: "data-table", style: "width: 100%; font-size: 12px;" }, h("thead", null, h("tr", null, h("th", { style: "text-align: left; padding: 4px 8px;" }, "Function"), h("th", { style: "text-align: left; padding: 4px 8px;" }, "File"), h("th", { style: "text-align: right; padding: 4px 8px;" }, "Callers"))), h("tbody", null, summary.mostCalled.slice(0, 10).map((item, i) => h("tr", { key: i, style: "border-top: 1px solid var(--border);" }, h("td", { style: "padding: 4px 8px; font-family: var(--font-mono); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px;" }, item.qualifiedName), h("td", { style: "padding: 4px 8px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px;" }, basename(item.file)), h("td", { style: "padding: 4px 8px; text-align: right; font-weight: 600;" }, String(item.callerCount)))))))
1166
+ : null, hasMostCalling
1167
+ ? h("div", null, h("h3", { style: "font-size: 14px; margin-bottom: 8px; color: var(--text);" }, "Most Complex Functions"), h("table", { class: "data-table", style: "width: 100%; font-size: 12px;" }, h("thead", null, h("tr", null, h("th", { style: "text-align: left; padding: 4px 8px;" }, "Function"), h("th", { style: "text-align: left; padding: 4px 8px;" }, "File"), h("th", { style: "text-align: right; padding: 4px 8px;" }, "Callees"))), h("tbody", null, summary.mostCalling.slice(0, 10).map((item, i) => h("tr", { key: i, style: "border-top: 1px solid var(--border);" }, h("td", { style: "padding: 4px 8px; font-family: var(--font-mono); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px;" }, item.qualifiedName), h("td", { style: "padding: 4px 8px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px;" }, basename(item.file)), h("td", { style: "padding: 4px 8px; text-align: right; font-weight: 600;" }, String(item.calleeCount)))))))
1168
+ : null));
1169
+ }
1170
+ // ── Main component ───────────────────────────────────────────────────
1171
+ export function ZonesView({ data, onSelect, navigateTo }) {
1172
+ const { zones, callGraph, imports: importsData } = data;
1173
+ const [search, setSearch] = useState("");
1174
+ const [expandedZones, setExpandedZones] = useState(new Set());
1175
+ const [expandedSubZones, setExpandedSubZones] = useState(new Map());
1176
+ const [slideoutZone, setSlideoutZone] = useState(null);
1177
+ // ── Drill-down navigation state ─────────────────────────────────────
1178
+ const ROOT_BREADCRUMB = { zoneId: null, label: "All Zones" };
1179
+ const [drillPath, setDrillPath] = useState([ROOT_BREADCRUMB]);
1180
+ if (!zones) {
1181
+ return h("div", { class: "loading" }, "No zone data available.");
1182
+ }
1183
+ // Diagram data
1184
+ const fileToZoneMap = useMemo(() => buildFileToZoneMap(zones), [zones]);
1185
+ const { zoneDataList } = useMemo(() => {
1186
+ if (callGraph) {
1187
+ return buildExplorerData(callGraph, fileToZoneMap, zones);
1188
+ }
1189
+ // Fallback: build zone data from zones.json alone (no call graph enrichment)
1190
+ if (!zones)
1191
+ return { zoneDataList: [], unzonedFiles: [] };
1192
+ const list = zones.zones.map((z, i) => {
1193
+ const zd = {
1194
+ id: z.id,
1195
+ name: z.name,
1196
+ color: getZoneColorByIndex(i),
1197
+ description: z.description,
1198
+ cohesion: z.cohesion,
1199
+ coupling: z.coupling,
1200
+ files: z.files.map((path) => ({ path, functions: [], internalCalls: 0, crossZoneCalls: 0 })),
1201
+ totalFiles: z.files.length,
1202
+ totalFunctions: 0,
1203
+ internalCalls: 0,
1204
+ crossZoneCalls: 0,
1205
+ };
1206
+ if (z.subZones && z.subZones.length > 0) {
1207
+ zd.subZones = convertSubZones(z.subZones);
1208
+ zd.subCrossings = convertCrossings(z.subCrossings);
1209
+ zd.hasDrillDown = true;
1210
+ }
1211
+ return zd;
1212
+ });
1213
+ return { zoneDataList: list, unzonedFiles: [] };
1214
+ }, [callGraph, fileToZoneMap, zones]);
1215
+ const flowEdges = useMemo(() => {
1216
+ const crossingEdges = zones ? buildFlowEdges(zones.crossings) : [];
1217
+ const callEdges = callGraph ? buildCallFlowEdges(callGraph.edges, fileToZoneMap) : [];
1218
+ const importEdges = importsData && zones
1219
+ ? buildExternalImportEdges(importsData.external, fileToZoneMap, zones)
1220
+ : [];
1221
+ const merged = new Map();
1222
+ for (const e of [...crossingEdges, ...callEdges, ...importEdges]) {
1223
+ const key = `${e.from}->${e.to}`;
1224
+ const existing = merged.get(key);
1225
+ if (existing) {
1226
+ existing.weight += e.weight;
1227
+ }
1228
+ else {
1229
+ merged.set(key, { ...e });
1230
+ }
1231
+ }
1232
+ return [...merged.values()];
1233
+ }, [callGraph, fileToZoneMap, importsData, zones]);
1234
+ // ── Drill-down derived data ─────────────────────────────────────────
1235
+ // Walk the drill path to resolve the current zone level. At root (depth 0),
1236
+ // show top-level zones. When drilled into a zone, show its subZones.
1237
+ const { visibleZones, visibleCrossings } = useMemo(() => {
1238
+ // Root level — show all top-level zones and top-level flow edges
1239
+ if (drillPath.length <= 1) {
1240
+ return { visibleZones: zoneDataList, visibleCrossings: flowEdges };
1241
+ }
1242
+ // Walk the drill path starting from the top-level zone data
1243
+ let currentZones = zoneDataList;
1244
+ let currentCrossings = flowEdges;
1245
+ for (let i = 1; i < drillPath.length; i++) {
1246
+ const crumb = drillPath[i];
1247
+ const parent = currentZones.find((z) => z.id === crumb.zoneId);
1248
+ if (!parent?.subZones) {
1249
+ // Drill path points to a zone without sub-zones — fall back to parent level
1250
+ return { visibleZones: currentZones, visibleCrossings: currentCrossings };
1251
+ }
1252
+ currentZones = parent.subZones;
1253
+ currentCrossings = parent.subCrossings ?? [];
1254
+ }
1255
+ return { visibleZones: currentZones, visibleCrossings: currentCrossings };
1256
+ }, [drillPath, zoneDataList, flowEdges]);
1257
+ const fileConnections = useMemo(() => {
1258
+ if (!callGraph)
1259
+ return new Map();
1260
+ return buildFileConnectionMap(callGraph, importsData?.external ?? [], fileToZoneMap, zones);
1261
+ }, [callGraph, importsData, fileToZoneMap, zones]);
1262
+ const fileToFileMap = useMemo(() => {
1263
+ if (!callGraph)
1264
+ return new Map();
1265
+ return buildFileToFileMap(callGraph, fileToZoneMap);
1266
+ }, [callGraph, fileToZoneMap]);
1267
+ const crossZoneTotal = useMemo(() => {
1268
+ return flowEdges.reduce((sum, e) => sum + e.weight, 0);
1269
+ }, [flowEdges]);
1270
+ // Search
1271
+ const searchQ = search.toLowerCase();
1272
+ const filteredZones = useMemo(() => {
1273
+ if (!search)
1274
+ return zones.zones;
1275
+ const q = search.toLowerCase();
1276
+ return zones.zones.filter((z) => z.name.toLowerCase().includes(q) ||
1277
+ z.description.toLowerCase().includes(q) ||
1278
+ z.files.some((f) => f.toLowerCase().includes(q)));
1279
+ }, [zones.zones, search]);
1280
+ const effectiveExpandedZones = useMemo(() => {
1281
+ if (!searchQ)
1282
+ return expandedZones;
1283
+ const set = new Set(expandedZones);
1284
+ for (const zd of visibleZones) {
1285
+ const nameMatch = zd.name.toLowerCase().includes(searchQ);
1286
+ const fileMatch = zd.files.some((f) => f.path.toLowerCase().includes(searchQ) ||
1287
+ f.functions.some((fi) => fi.fn.qualifiedName.toLowerCase().includes(searchQ)));
1288
+ if (nameMatch || fileMatch)
1289
+ set.add(zd.id);
1290
+ }
1291
+ return set;
1292
+ }, [searchQ, visibleZones, expandedZones]);
1293
+ // Handlers
1294
+ const toggleZone = useCallback((id) => {
1295
+ setExpandedZones((prev) => {
1296
+ const next = new Set(prev);
1297
+ if (next.has(id)) {
1298
+ next.delete(id);
1299
+ // Clean up subzone expansion state when collapsing
1300
+ setExpandedSubZones((prevSz) => {
1301
+ if (!prevSz.has(id))
1302
+ return prevSz;
1303
+ const nextSz = new Map(prevSz);
1304
+ nextSz.delete(id);
1305
+ return nextSz;
1306
+ });
1307
+ }
1308
+ else {
1309
+ next.add(id);
1310
+ }
1311
+ return next;
1312
+ });
1313
+ }, []);
1314
+ const toggleSubZone = useCallback((parentId, subZoneId) => {
1315
+ setExpandedSubZones((prev) => {
1316
+ const next = new Map(prev);
1317
+ const set = new Set(prev.get(parentId) ?? []);
1318
+ if (set.has(subZoneId))
1319
+ set.delete(subZoneId);
1320
+ else
1321
+ set.add(subZoneId);
1322
+ if (set.size === 0)
1323
+ next.delete(parentId);
1324
+ else
1325
+ next.set(parentId, set);
1326
+ return next;
1327
+ });
1328
+ }, []);
1329
+ const handleZoneDblClick = useCallback((id) => {
1330
+ if (!zones)
1331
+ return;
1332
+ const zone = zones.zones.find((z) => z.id === id);
1333
+ if (zone)
1334
+ setSlideoutZone(zone);
1335
+ }, [zones]);
1336
+ const handleDiagramZoneSelect = useCallback((zd) => {
1337
+ if (!zones)
1338
+ return;
1339
+ const zone = zones.zones.find((z) => z.id === zd.id);
1340
+ if (!zone)
1341
+ return;
1342
+ setSlideoutZone(zone);
1343
+ }, [zones]);
1344
+ const handleFileSelect = useCallback((filePath) => {
1345
+ onSelect({
1346
+ type: "file",
1347
+ title: basename(filePath),
1348
+ path: filePath,
1349
+ zone: fileToZoneMap.get(filePath)?.name,
1350
+ });
1351
+ }, [fileToZoneMap, onSelect]);
1352
+ const handleFileDblClick = useCallback((filePath) => {
1353
+ if (navigateTo)
1354
+ navigateTo("files", { file: filePath });
1355
+ }, [navigateTo]);
1356
+ const handleSlideoutClose = useCallback(() => {
1357
+ setSlideoutZone(null);
1358
+ }, []);
1359
+ /** Navigate the drill-down breadcrumb: truncate path to the given depth. */
1360
+ const handleBreadcrumbNavigate = useCallback((depth) => {
1361
+ setDrillPath((prev) => prev.slice(0, depth + 1));
1362
+ setExpandedZones(new Set());
1363
+ }, []);
1364
+ /** Drill into a zone: push a new breadcrumb and reset expanded state. */
1365
+ const handleDrillDown = useCallback((zoneId) => {
1366
+ const zone = visibleZones.find((z) => z.id === zoneId);
1367
+ if (!zone?.hasDrillDown)
1368
+ return;
1369
+ setDrillPath((prev) => [...prev, { zoneId, label: zone.name }]);
1370
+ setExpandedZones(new Set());
1371
+ }, [visibleZones]);
1372
+ return h("div", null, h("div", { class: "view-header" }, h(BrandedHeader, { product: "sourcevision", title: "SourceVision", class: "branded-header-sv" }), h("h2", { class: "section-header" }, "Zones")), h("p", { class: "section-sub" }, drillPath.length <= 1
1373
+ ? `${zones.zones.length} zones, ${zones.crossings.length} cross-zone dependencies, ${zones.unzoned.length} unzoned files`
1374
+ : `${visibleZones.length} sub-zones in ${drillPath[drillPath.length - 1].label}`),
1375
+ // Search
1376
+ h(SearchFilter, {
1377
+ placeholder: "Search zones, files...",
1378
+ value: search,
1379
+ onInput: setSearch,
1380
+ resultCount: filteredZones.length,
1381
+ totalCount: zones.zones.length,
1382
+ }),
1383
+ // Stat cards (only when call graph data available)
1384
+ callGraph
1385
+ ? h("div", { style: "display: flex; gap: 16px; margin: 12px 0; flex-wrap: wrap;" }, h(StatCard, { value: String(callGraph.summary.totalFunctions), label: "Functions" }), h(StatCard, { value: String(callGraph.summary.totalCalls), label: "Calls" }), crossZoneTotal > 0
1386
+ ? h(StatCard, { value: String(crossZoneTotal), label: "Cross-Zone", color: "var(--orange)" })
1387
+ : null, callGraph.summary.cycleCount > 0
1388
+ ? h(StatCard, { value: String(callGraph.summary.cycleCount), label: "Cycles", color: "var(--orange)" })
1389
+ : null)
1390
+ : null,
1391
+ // Drill-down breadcrumb (hidden at root level)
1392
+ h(ZoneBreadcrumbNav, { drillPath, onNavigate: handleBreadcrumbNavigate }),
1393
+ // Zone Diagram
1394
+ visibleZones.length > 0
1395
+ ? h(ZoneDiagram, {
1396
+ zones: visibleZones,
1397
+ edges: visibleCrossings,
1398
+ expandedZones: effectiveExpandedZones,
1399
+ expandedSubZones,
1400
+ selectedZoneId: slideoutZone?.id ?? null,
1401
+ searchQ,
1402
+ fileConnections,
1403
+ fileToFileMap,
1404
+ onToggleZone: toggleZone,
1405
+ onSelectZone: handleDiagramZoneSelect,
1406
+ onSelectFile: handleFileSelect,
1407
+ onDblClickFile: handleFileDblClick,
1408
+ onDblClickZone: handleZoneDblClick,
1409
+ onDrillDown: handleDrillDown,
1410
+ onToggleSubZone: toggleSubZone,
1411
+ })
1412
+ : null,
1413
+ // Unzoned files
1414
+ zones.unzoned.length
1415
+ ? h(CollapsibleSection, {
1416
+ title: "Unzoned Files",
1417
+ count: zones.unzoned.length,
1418
+ defaultOpen: false,
1419
+ threshold: 10,
1420
+ }, ...zones.unzoned.map((f) => h("div", {
1421
+ key: f,
1422
+ class: "mono-sm text-dim",
1423
+ style: "line-height: 1.8",
1424
+ }, f)))
1425
+ : null,
1426
+ // Top functions (only when call graph data available)
1427
+ callGraph ? h(TopFunctionsTables, { summary: callGraph.summary }) : null,
1428
+ // Zone slideout panel
1429
+ h(ZoneSlideout, {
1430
+ zone: slideoutZone,
1431
+ crossings: zones.crossings,
1432
+ allZones: zones.zones,
1433
+ onClose: handleSlideoutClose,
1434
+ onFileClick: handleFileSelect,
1435
+ navigateTo,
1436
+ }));
1437
+ }
1438
+ //# sourceMappingURL=zones.js.map