pkgviz 0.7.7 → 0.8.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 (367) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -1
  3. package/.next/build-manifest.json +15 -24
  4. package/.next/fallback-build-manifest.json +15 -0
  5. package/.next/images-manifest.json +14 -3
  6. package/.next/next-minimal-server.js.nft.json +1 -1
  7. package/.next/next-server.js.nft.json +1 -1
  8. package/.next/prerender-manifest.json +52 -16
  9. package/.next/required-server-files.js +349 -0
  10. package/.next/required-server-files.json +106 -75
  11. package/.next/routes-manifest.json +16 -6
  12. package/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
  13. package/.next/server/app/_global-error/page/build-manifest.json +20 -0
  14. package/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
  15. package/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
  16. package/.next/server/app/_global-error/page.js +9 -0
  17. package/.next/server/app/_global-error/page.js.map +5 -0
  18. package/.next/server/app/_global-error/page.js.nft.json +1 -0
  19. package/.next/server/app/_global-error/page_client-reference-manifest.js +3 -0
  20. package/.next/server/app/_global-error.html +1 -0
  21. package/.next/server/app/_global-error.meta +22 -0
  22. package/.next/server/app/_global-error.rsc +15 -0
  23. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +20 -0
  24. package/.next/server/app/_global-error.segments/_full.segment.rsc +15 -0
  25. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
  26. package/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
  27. package/.next/server/app/_not-found/page/build-manifest.json +20 -0
  28. package/.next/server/app/_not-found/page/next-font-manifest.json +11 -0
  29. package/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
  30. package/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
  31. package/.next/server/app/_not-found/page.js +13 -2
  32. package/.next/server/app/_not-found/page.js.map +5 -0
  33. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  34. package/.next/server/app/_not-found/page_client-reference-manifest.js +3 -1
  35. package/.next/server/app/_not-found.html +1 -1
  36. package/.next/server/app/_not-found.meta +19 -0
  37. package/.next/server/app/_not-found.rsc +18 -19
  38. package/.next/server/app/_not-found.segments/_full.segment.rsc +18 -0
  39. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +24 -0
  40. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -0
  41. package/.next/server/app/favicon.ico/route/app-paths-manifest.json +3 -0
  42. package/.next/server/app/favicon.ico/route/build-manifest.json +12 -0
  43. package/.next/server/app/favicon.ico/route.js +6 -1
  44. package/.next/server/app/favicon.ico/route.js.map +5 -0
  45. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  46. package/.next/server/app/page/app-paths-manifest.json +3 -0
  47. package/.next/server/app/page/build-manifest.json +20 -0
  48. package/.next/server/app/page/next-font-manifest.json +11 -0
  49. package/.next/server/app/page/react-loadable-manifest.json +1 -0
  50. package/.next/server/app/page/server-reference-manifest.json +93 -0
  51. package/.next/server/app/page.js +16 -13
  52. package/.next/server/app/page.js.map +5 -0
  53. package/.next/server/app/page.js.nft.json +1 -1
  54. package/.next/server/app/page_client-reference-manifest.js +3 -1
  55. package/.next/server/app-paths-manifest.json +2 -1
  56. package/.next/server/chunks/[externals]__0l8ei7u._.js +3 -0
  57. package/.next/server/chunks/[externals]__0l8ei7u._.js.map +1 -0
  58. package/.next/server/chunks/[root-of-the-server]__1to-yfd._.js +160 -0
  59. package/.next/server/chunks/[root-of-the-server]__1to-yfd._.js.map +1 -0
  60. package/.next/server/chunks/[turbopack]_runtime.js +807 -0
  61. package/.next/server/chunks/[turbopack]_runtime.js.map +10 -0
  62. package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_0g2jjls.js +3 -0
  63. package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_0g2jjls.js.map +1 -0
  64. package/.next/server/chunks/ssr/[root-of-the-server]__0agsrep._.js +28 -0
  65. package/.next/server/chunks/ssr/[root-of-the-server]__0agsrep._.js.map +1 -0
  66. package/.next/server/chunks/ssr/[root-of-the-server]__0f_e7c8._.js +33 -0
  67. package/.next/server/chunks/ssr/[root-of-the-server]__0f_e7c8._.js.map +1 -0
  68. package/.next/server/chunks/ssr/[root-of-the-server]__0yqn68e._.js +3 -0
  69. package/.next/server/chunks/ssr/[root-of-the-server]__0yqn68e._.js.map +1 -0
  70. package/.next/server/chunks/ssr/[root-of-the-server]__112mz-6._.js +14 -0
  71. package/.next/server/chunks/ssr/[root-of-the-server]__112mz-6._.js.map +1 -0
  72. package/.next/server/chunks/ssr/[root-of-the-server]__11_76b7._.js +3 -0
  73. package/.next/server/chunks/ssr/[root-of-the-server]__11_76b7._.js.map +1 -0
  74. package/.next/server/chunks/ssr/[root-of-the-server]__12ovu0c._.js +3 -0
  75. package/.next/server/chunks/ssr/[root-of-the-server]__12ovu0c._.js.map +1 -0
  76. package/.next/server/chunks/ssr/[root-of-the-server]__16pmw__._.js +3 -0
  77. package/.next/server/chunks/ssr/[root-of-the-server]__16pmw__._.js.map +1 -0
  78. package/.next/server/chunks/ssr/[root-of-the-server]__1f2jx51._.js +33 -0
  79. package/.next/server/chunks/ssr/[root-of-the-server]__1f2jx51._.js.map +1 -0
  80. package/.next/server/chunks/ssr/[root-of-the-server]__1rn09vt._.js +3 -0
  81. package/.next/server/chunks/ssr/[root-of-the-server]__1rn09vt._.js.map +1 -0
  82. package/.next/server/chunks/ssr/[root-of-the-server]__20e4gkp._.js +3 -0
  83. package/.next/server/chunks/ssr/[root-of-the-server]__20e4gkp._.js.map +1 -0
  84. package/.next/server/chunks/ssr/[turbopack]_runtime.js +807 -0
  85. package/.next/server/chunks/ssr/[turbopack]_runtime.js.map +10 -0
  86. package/.next/server/chunks/ssr/_1acy_2s._.js +158 -0
  87. package/.next/server/chunks/ssr/_1acy_2s._.js.map +1 -0
  88. package/.next/server/chunks/ssr/_1rh2tf9._.js +367 -0
  89. package/.next/server/chunks/ssr/_1rh2tf9._.js.map +1 -0
  90. package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0zi5s8-.js +3 -0
  91. package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0zi5s8-.js.map +1 -0
  92. package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0pt47yr.js +3 -0
  93. package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0pt47yr.js.map +1 -0
  94. package/.next/server/chunks/ssr/node_modules_01xbj_9._.js +33 -0
  95. package/.next/server/chunks/ssr/node_modules_01xbj_9._.js.map +1 -0
  96. package/.next/server/chunks/ssr/node_modules_next_dist_1c5ky4f._.js +3 -0
  97. package/.next/server/chunks/ssr/node_modules_next_dist_1c5ky4f._.js.map +1 -0
  98. package/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +6 -0
  99. package/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js.map +1 -0
  100. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0wpq8j3._.js +3 -0
  101. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0wpq8j3._.js.map +1 -0
  102. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0symwr9.js +3 -0
  103. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0symwr9.js.map +1 -0
  104. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0q-w892.js +3 -0
  105. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0q-w892.js.map +1 -0
  106. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0l_sp0x.js +3 -0
  107. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0l_sp0x.js.map +1 -0
  108. package/.next/server/chunks/ssr/src_app_loading_tsx_01g_-m0._.js +3 -0
  109. package/.next/server/chunks/ssr/src_app_loading_tsx_01g_-m0._.js.map +1 -0
  110. package/.next/server/middleware-build-manifest.js +24 -1
  111. package/.next/server/middleware-manifest.json +2 -2
  112. package/.next/server/next-font-manifest.js +1 -1
  113. package/.next/server/next-font-manifest.json +15 -1
  114. package/.next/server/pages/404.html +1 -1
  115. package/.next/server/pages/500.html +1 -1
  116. package/.next/server/pages-manifest.json +2 -4
  117. package/.next/server/prefetch-hints.json +25 -0
  118. package/.next/server/server-reference-manifest.js +1 -1
  119. package/.next/server/server-reference-manifest.json +94 -1
  120. package/.next/static/chunks/19mx3mg6lkumu.js +1 -0
  121. package/.next/static/chunks/1las_6j17xa9p.js +1 -0
  122. package/.next/static/chunks/1xk6snnh6e5_j.css +3 -0
  123. package/.next/static/chunks/2-rtuqsgzmno4.js +22 -0
  124. package/.next/static/chunks/27t_qfc-3_lzs.js +1 -0
  125. package/.next/static/chunks/2sswp23h6zh08.js +345 -0
  126. package/.next/static/chunks/310vm2bl3xxpt.js +31 -0
  127. package/.next/static/chunks/3fntmmi971322.js +1 -0
  128. package/.next/static/chunks/turbopack-1b6uko1cu_h42.js +1 -0
  129. package/.next/static/media/4fa387ec64143e14-s.2tuy5pz7dlieh.woff2 +0 -0
  130. package/.next/static/media/53b9e256198e5412-s.390ncx5urfkfu.woff2 +0 -0
  131. package/.next/static/media/5ce348bf30bf5439-s.31988l_ccedte.woff2 +0 -0
  132. package/.next/static/media/6306c77e7c8268e4-s.2dbetqa9o8jxf.woff2 +0 -0
  133. package/.next/static/media/7178b3e590c64307-s.21jp631_3pja2.woff2 +0 -0
  134. package/.next/static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2 +0 -0
  135. package/.next/static/media/7d817b4c03b0c5f1-s.1uyisp29ctx0d.woff2 +0 -0
  136. package/.next/static/media/8a480f0b521d4e75-s.1qq4vpdcun5oj.woff2 +0 -0
  137. package/.next/static/media/bbc41e54d2fcbd21-s.1rgnod-3esatf.woff2 +0 -0
  138. package/.next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2 +0 -0
  139. package/.next/static/media/favicon.2246eezznj2ab.ico +0 -0
  140. package/.next/static/media/fef07dbb0973bf53-s.3p2_lha1f2xer.woff2 +0 -0
  141. package/.next/static/rjdzHqzPw2GCNhDbjcu0-/_buildManifest.js +11 -0
  142. package/.next/static/rjdzHqzPw2GCNhDbjcu0-/_clientMiddlewareManifest.js +1 -0
  143. package/README.md +176 -4
  144. package/bin/pkgviz.ts +57 -29
  145. package/package.json +57 -36
  146. package/src/app/actions/audit.actions.ts +7 -49
  147. package/src/app/globals.css +0 -2
  148. package/src/app/layout.tsx +3 -2
  149. package/src/app/loading.tsx +8 -0
  150. package/src/app/page.tsx +16 -2
  151. package/src/app/utils/buildGraph.ts +99 -100
  152. package/src/app/utils/getParsedFileStructure.ts +128 -54
  153. package/src/app/utils/inspectParserLanguageAsync.ts +39 -0
  154. package/src/app/utils/markCyclicPackages.ts +45 -185
  155. package/src/app/utils/parser/cpp/parseCppFile.ts +68 -86
  156. package/src/app/utils/parser/delphi/parseFile.ts +36 -120
  157. package/src/app/utils/parser/java/findEntryPoint.ts +2 -1
  158. package/src/app/utils/parser/java/parseJavaFile.ts +33 -45
  159. package/src/app/utils/parser/kotlin/parseFile.ts +34 -87
  160. package/src/app/utils/parser/python/parseFile.ts +30 -100
  161. package/src/app/utils/parser/typescript/parseFile.ts +17 -52
  162. package/src/app/utils/selectParserLanguage.ts +39 -0
  163. package/src/cli/formatAuditRuleFailures.ts +21 -0
  164. package/src/cli/runAuditAsync.ts +41 -0
  165. package/src/components/Breadcrumb.tsx +5 -4
  166. package/src/components/ExportPanel.tsx +53 -0
  167. package/src/components/Header.tsx +1 -0
  168. package/src/components/Loader.tsx +8 -2
  169. package/src/components/ProjectLoadError.tsx +15 -0
  170. package/src/components/SettingsPanel.tsx +184 -0
  171. package/src/components/Switch.tsx +9 -17
  172. package/src/components/ThemeToggle.tsx +2 -1
  173. package/src/components/ToggleSwitch.tsx +36 -0
  174. package/src/components/sidebar/Sidebar.tsx +14 -0
  175. package/src/components/sidebar/SidebarBadge.tsx +25 -0
  176. package/src/components/sidebar/SidebarRow.tsx +14 -0
  177. package/src/components/sidebar/SidebarSection.tsx +16 -0
  178. package/src/components/sidebar/SidebarTabs.tsx +58 -0
  179. package/src/contexts/SettingsContext.tsx +19 -20
  180. package/src/features/audit/adapters/inbound/react/AuditRuleList.tsx +76 -0
  181. package/src/features/audit/adapters/inbound/react/AuditRulePanel.tsx +30 -0
  182. package/src/features/audit/adapters/inbound/react/CycleInspector.tsx +116 -0
  183. package/src/features/audit/adapters/inbound/react/CyclicDependenciesRuleDetails.tsx +124 -0
  184. package/src/features/audit/adapters/inbound/react/useCycleSelection.ts +47 -0
  185. package/src/features/audit/application/use-cases/createAuditFromSnapshot.ts +28 -0
  186. package/src/features/audit/application/use-cases/resolveSelectedCycleIds.ts +18 -0
  187. package/src/features/audit/composition/createAuditAsync.ts +11 -0
  188. package/src/features/audit/domain/evaluateAuditRules.ts +42 -0
  189. package/src/features/audit/domain/hasBlockingAuditRuleFailure.ts +6 -0
  190. package/src/features/audit/domain/resolveAuditConfiguration.ts +27 -0
  191. package/src/features/audit/utils/cycleVisualization.ts +86 -0
  192. package/src/features/dependency-analysis/adapters/outbound/dependency-graph/analyzeDependencyImportsAsync.ts +172 -0
  193. package/src/features/dependency-analysis/adapters/outbound/dependency-graph/analyzeTypeScriptImportsAsync.ts +6 -0
  194. package/src/features/graph-view/adapters/inbound/react/DependencyGraphView.tsx +184 -0
  195. package/src/features/graph-view/adapters/inbound/react/GraphZoomControls.tsx +48 -0
  196. package/src/features/graph-view/adapters/inbound/react/applyGraphInteractionPresentation.ts +35 -0
  197. package/src/features/graph-view/adapters/inbound/react/createGraphViewModel.ts +240 -0
  198. package/src/features/graph-view/adapters/inbound/react/createGraphViewStyles.ts +37 -0
  199. package/src/features/graph-view/adapters/inbound/react/useGraphFocus.ts +104 -0
  200. package/src/features/graph-view/adapters/inbound/react/useGraphInteractions.ts +50 -0
  201. package/src/features/graph-view/adapters/inbound/react/useGraphProjection.ts +71 -0
  202. package/src/features/graph-view/adapters/inbound/react/useGraphViewport.ts +48 -0
  203. package/src/features/graph-view/domain/getDependencyHighlightClasses.ts +39 -0
  204. package/src/features/graph-view/domain/reduceGraphInteraction.ts +23 -0
  205. package/src/features/graph-view/utils/createCompoundOpacityIndex.ts +38 -0
  206. package/src/features/graph-view/utils/projectVisibleGraph.ts +94 -0
  207. package/src/features/graph-view/utils/readNodeDefinitionId.ts +7 -0
  208. package/src/features/graph-view/utils/removeEmptyStructuralNodes.ts +27 -0
  209. package/src/features/project-analysis/adapters/outbound/filesystem/readProjectSnapshotAsync.ts +12 -0
  210. package/src/features/project-analysis/application/use-cases/createProjectSnapshotReader.ts +29 -0
  211. package/src/features/project-analysis/composition/loadProjectOverviewAsync.ts +14 -0
  212. package/src/features/project-analysis/composition/loadProjectSnapshotAsync.ts +12 -0
  213. package/src/features/project-tree/adapters/inbound/react/ProjectTreePanel.tsx +52 -0
  214. package/src/features/project-tree/adapters/inbound/react/useProjectTreeExpansion.ts +46 -0
  215. package/src/features/project-tree/application/use-cases/buildProjectTree.ts +61 -0
  216. package/src/features/project-tree/application/use-cases/resolveProjectTreeNavigation.ts +13 -0
  217. package/src/features/project-tree/utils/findProjectTreeNode.ts +14 -0
  218. package/src/features/project-tree/utils/findProjectTreeNodeByGraphPackage.ts +21 -0
  219. package/src/features/project-tree/utils/getProjectTreeAncestorIds.ts +26 -0
  220. package/src/features/settings/utils/readSettingsEnvironment.ts +53 -0
  221. package/src/i18n/en.ts +21 -2
  222. package/src/i18n/i18n.ts +2 -3
  223. package/src/layouts/breadthfirst/style.ts +1 -0
  224. package/src/layouts/circle/style.ts +1 -0
  225. package/src/layouts/concentric/style.ts +1 -0
  226. package/src/layouts/elk/layout.ts +1 -3
  227. package/src/layouts/elk/style.ts +1 -0
  228. package/src/layouts/getWeightBuckets.ts +3 -1
  229. package/src/layouts/grid/style.ts +1 -0
  230. package/src/layouts/index.ts +0 -4
  231. package/src/layouts/style.ts +172 -25
  232. package/src/screens/home/Home.tsx +73 -25
  233. package/src/screens/home/HomeGraph.tsx +43 -0
  234. package/src/screens/home/HomeSidebar.tsx +129 -0
  235. package/src/shared/types/index.ts +0 -9
  236. package/src/shared/utils/getProjectName.ts +4 -1
  237. package/src/shared/utils/parseProjectPath.ts +1 -0
  238. package/src/shared/utils/toPosix.ts +1 -1
  239. package/src/types/audit.ts +76 -0
  240. package/src/types/auditVisualization.ts +22 -0
  241. package/src/types/graphInteraction.ts +9 -0
  242. package/src/types/parserSelection.ts +11 -0
  243. package/src/types/projectAnalysis.ts +20 -0
  244. package/src/types/projectAnalysisActionResult.ts +2 -0
  245. package/src/types/projectTree.ts +7 -0
  246. package/src/utils/filter/filterByPackagePrefix.ts +25 -7
  247. package/src/utils/filter/filterEmptyPackages.ts +28 -29
  248. package/src/utils/filter/filterSubPackagesFromDepth.ts +20 -17
  249. package/src/utils/filter/filterVendorPackages.ts +1 -1
  250. package/src/utils/filter/isIntrinsicGraphNode.ts +8 -0
  251. package/src/utils/filter/toggleCompoundNodes.ts +41 -31
  252. package/src/utils/runProjectAnalysisActionAsync.ts +14 -0
  253. package/tsconfig.json +11 -3
  254. package/.next/app-build-manifest.json +0 -29
  255. package/.next/cache/.previewinfo +0 -1
  256. package/.next/cache/.rscinfo +0 -1
  257. package/.next/cache/.tsbuildinfo +0 -1
  258. package/.next/cache/chrome-devtools-workspace-uuid +0 -1
  259. package/.next/cache/eslint/.cache_6t30dc +0 -1
  260. package/.next/cache/webpack/client-production/0.pack +0 -4
  261. package/.next/cache/webpack/client-production/1.pack +0 -0
  262. package/.next/cache/webpack/client-production/10.pack +0 -0
  263. package/.next/cache/webpack/client-production/11.pack +0 -0
  264. package/.next/cache/webpack/client-production/12.pack +0 -0
  265. package/.next/cache/webpack/client-production/13.pack +0 -0
  266. package/.next/cache/webpack/client-production/14.pack +0 -0
  267. package/.next/cache/webpack/client-production/15.pack +0 -0
  268. package/.next/cache/webpack/client-production/16.pack +0 -0
  269. package/.next/cache/webpack/client-production/2.pack +0 -0
  270. package/.next/cache/webpack/client-production/3.pack +0 -0
  271. package/.next/cache/webpack/client-production/4.pack +0 -0
  272. package/.next/cache/webpack/client-production/5.pack +0 -0
  273. package/.next/cache/webpack/client-production/6.pack +0 -0
  274. package/.next/cache/webpack/client-production/7.pack +0 -0
  275. package/.next/cache/webpack/client-production/8.pack +0 -0
  276. package/.next/cache/webpack/client-production/9.pack +0 -0
  277. package/.next/cache/webpack/client-production/index.pack +0 -0
  278. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  279. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  280. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  281. package/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  282. package/.next/cache/webpack/server-production/0.pack +0 -0
  283. package/.next/cache/webpack/server-production/1.pack +0 -0
  284. package/.next/cache/webpack/server-production/10.pack +0 -4
  285. package/.next/cache/webpack/server-production/11.pack +0 -0
  286. package/.next/cache/webpack/server-production/12.pack +0 -0
  287. package/.next/cache/webpack/server-production/13.pack +0 -0
  288. package/.next/cache/webpack/server-production/14.pack +0 -0
  289. package/.next/cache/webpack/server-production/15.pack +0 -0
  290. package/.next/cache/webpack/server-production/16.pack +0 -0
  291. package/.next/cache/webpack/server-production/17.pack +0 -0
  292. package/.next/cache/webpack/server-production/18.pack +0 -4
  293. package/.next/cache/webpack/server-production/2.pack +0 -0
  294. package/.next/cache/webpack/server-production/3.pack +0 -0
  295. package/.next/cache/webpack/server-production/4.pack +0 -0
  296. package/.next/cache/webpack/server-production/5.pack +0 -0
  297. package/.next/cache/webpack/server-production/6.pack +0 -0
  298. package/.next/cache/webpack/server-production/7.pack +0 -0
  299. package/.next/cache/webpack/server-production/8.pack +0 -0
  300. package/.next/cache/webpack/server-production/9.pack +0 -0
  301. package/.next/cache/webpack/server-production/index.pack +0 -0
  302. package/.next/cache/webpack/server-production/index.pack.old +0 -0
  303. package/.next/diagnostics/build-diagnostics.json +0 -6
  304. package/.next/diagnostics/framework.json +0 -1
  305. package/.next/server/app/index.html +0 -1
  306. package/.next/server/app/index.meta +0 -7
  307. package/.next/server/app/index.rsc +0 -20
  308. package/.next/server/chunks/548.js +0 -6
  309. package/.next/server/chunks/610.js +0 -7
  310. package/.next/server/chunks/985.js +0 -22
  311. package/.next/server/middleware-react-loadable-manifest.js +0 -1
  312. package/.next/server/pages/_app.js +0 -1
  313. package/.next/server/pages/_app.js.nft.json +0 -1
  314. package/.next/server/pages/_document.js +0 -1
  315. package/.next/server/pages/_document.js.nft.json +0 -1
  316. package/.next/server/pages/_error.js +0 -19
  317. package/.next/server/pages/_error.js.nft.json +0 -1
  318. package/.next/server/webpack-runtime.js +0 -1
  319. package/.next/static/_YC2p40ZiYT2Gcw-h_tjC/_buildManifest.js +0 -1
  320. package/.next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js +0 -1
  321. package/.next/static/chunks/680-55ad17895c8fb70b.js +0 -1
  322. package/.next/static/chunks/90542734-29e255269788f1bd.js +0 -1
  323. package/.next/static/chunks/91dbc596-b9cdccd3f5cba95d.js +0 -1
  324. package/.next/static/chunks/964-b440f7af22c41ca5.js +0 -1
  325. package/.next/static/chunks/app/_not-found/page-11e4fea8e290f304.js +0 -1
  326. package/.next/static/chunks/app/layout-8aae7fba5ed8afb3.js +0 -1
  327. package/.next/static/chunks/app/page-91450b1230da8bca.js +0 -1
  328. package/.next/static/chunks/framework-bc35dade6ff9684e.js +0 -1
  329. package/.next/static/chunks/main-9e86d333c1e02dd1.js +0 -1
  330. package/.next/static/chunks/main-app-69a9ff75ff3465ca.js +0 -1
  331. package/.next/static/chunks/pages/_app-0a0020ddd67f79cf.js +0 -1
  332. package/.next/static/chunks/pages/_error-03529f2c21436739.js +0 -1
  333. package/.next/static/chunks/webpack-7adfafc806738767.js +0 -1
  334. package/.next/static/css/ec31779168107a11.css +0 -3
  335. package/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  336. package/.next/static/media/747892c23ea88013-s.woff2 +0 -0
  337. package/.next/static/media/8d697b304b401681-s.woff2 +0 -0
  338. package/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
  339. package/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
  340. package/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
  341. package/.next/trace +0 -2
  342. package/.next/types/app/layout.ts +0 -84
  343. package/.next/types/app/page.ts +0 -84
  344. package/.next/types/cache-life.d.ts +0 -141
  345. package/.next/types/package.json +0 -1
  346. package/src/app/actions/graph.actions.ts +0 -25
  347. package/src/app/utils/parser/cpp/extractCppPackageFromImport.ts +0 -18
  348. package/src/app/utils/parser/delphi/extractPackageFromImport.ts +0 -21
  349. package/src/app/utils/parser/java/extractJavaPackageFromImport.ts +0 -39
  350. package/src/app/utils/parser/java/getIntrinsicPackagesRecursive.ts +0 -33
  351. package/src/app/utils/parser/kotlin/extractPackageFromImport.ts +0 -19
  352. package/src/app/utils/parser/python/extractPythonPackageFromImport.ts +0 -18
  353. package/src/app/utils/parser/typescript/extractTypeScriptPackageFromImport.ts +0 -18
  354. package/src/components/Cytoscape.tsx +0 -23
  355. package/src/components/Setting.tsx +0 -17
  356. package/src/components/Settings.tsx +0 -189
  357. package/src/components/ZoomInput.tsx +0 -94
  358. package/src/components/useCytoscape.ts +0 -343
  359. package/src/layouts/getLayoutStyle.ts +0 -19
  360. package/src/shared/utils/detectLanguage.ts +0 -255
  361. package/src/shared/utils/getJsonAsync.ts +0 -13
  362. package/src/shared/utils/parseEnv.ts +0 -91
  363. package/src/store/useLocalStorage.ts +0 -29
  364. package/src/utils/hasChildren.ts +0 -7
  365. /package/.next/{react-loadable-manifest.json → server/app/_global-error/page/react-loadable-manifest.json} +0 -0
  366. /package/.next/static/chunks/{polyfills-42372ed130431b0a.js → 0cz1d0mv5g_q7.js} +0 -0
  367. /package/.next/static/{_YC2p40ZiYT2Gcw-h_tjC → rjdzHqzPw2GCNhDbjcu0-}/_ssgManifest.js +0 -0
@@ -0,0 +1,21 @@
1
+ import type { ProjectTreeNode } from '@/types/projectTree';
2
+
3
+ /*** Finds the deepest directory representing a graph package, falling back to a matching file. */
4
+ export function findProjectTreeNodeByGraphPackage(
5
+ nodes: readonly ProjectTreeNode[],
6
+ graphPackage: string
7
+ ): ProjectTreeNode | null {
8
+ const matches = collectProjectTreePackageMatches(nodes, graphPackage);
9
+ return matches.find(node => node.kind === 'directory') ?? matches.at(0) ?? null;
10
+ }
11
+
12
+ /*** Collects package matches depth-first so nested directories outrank their ancestors. */
13
+ function collectProjectTreePackageMatches(
14
+ nodes: readonly ProjectTreeNode[],
15
+ graphPackage: string
16
+ ): readonly ProjectTreeNode[] {
17
+ return nodes.flatMap(node => [
18
+ ...(node.children ? collectProjectTreePackageMatches(node.children, graphPackage) : []),
19
+ ...(node.graphPackage === graphPackage ? [node] : []),
20
+ ]);
21
+ }
@@ -0,0 +1,26 @@
1
+ import type { ProjectTreeNode } from '@/types/projectTree';
2
+
3
+ /*** Returns every directory id that must be expanded to reveal one tree node. */
4
+ export function getProjectTreeAncestorIds(
5
+ nodes: readonly ProjectTreeNode[],
6
+ targetId: string
7
+ ): readonly string[] {
8
+ return findProjectTreeAncestorIds(nodes, targetId, []) ?? [];
9
+ }
10
+
11
+ /*** Finds one target recursively while carrying its directory ancestry. */
12
+ function findProjectTreeAncestorIds(
13
+ nodes: readonly ProjectTreeNode[],
14
+ targetId: string,
15
+ ancestors: readonly string[]
16
+ ): readonly string[] | null {
17
+ for (const node of nodes) {
18
+ if (node.id === targetId) return ancestors;
19
+ if (!node.children) continue;
20
+
21
+ const match = findProjectTreeAncestorIds(node.children, targetId, [...ancestors, node.id]);
22
+ if (match !== null) return match;
23
+ }
24
+
25
+ return null;
26
+ }
@@ -0,0 +1,53 @@
1
+ import { readEnvBoolean, readEnvNumber, readEnvString } from '@ankhorage/utility/node/env';
2
+ import type { LayoutOptions } from 'cytoscape';
3
+
4
+ /*** Reads and validates PKGViz viewer defaults from public settings environment variables. */
5
+ export function readSettingsEnvironment(): SettingsEnvironment {
6
+ const environment = {
7
+ NEXT_PUBLIC_SETTINGS_SHOW_CYCLES: process.env.NEXT_PUBLIC_SETTINGS_SHOW_CYCLES,
8
+ NEXT_PUBLIC_SETTINGS_LAYOUT: process.env.NEXT_PUBLIC_SETTINGS_LAYOUT,
9
+ NEXT_PUBLIC_SETTINGS_LAYOUT_SPACING: process.env.NEXT_PUBLIC_SETTINGS_LAYOUT_SPACING,
10
+ NEXT_PUBLIC_SETTINGS_SHOW_COMPOUNDNODES: process.env.NEXT_PUBLIC_SETTINGS_SHOW_COMPOUNDNODES,
11
+ NEXT_PUBLIC_SETTINGS_SHOW_VENDORPACKAGES: process.env.NEXT_PUBLIC_SETTINGS_SHOW_VENDORPACKAGES,
12
+ NEXT_PUBLIC_SETTINGS_SUBPACKAGE_DEPTH: process.env.NEXT_PUBLIC_SETTINGS_SUBPACKAGE_DEPTH,
13
+ };
14
+
15
+ const layout = readEnvString('NEXT_PUBLIC_SETTINGS_LAYOUT', environment);
16
+ const layoutSpacing = readEnvNumber('NEXT_PUBLIC_SETTINGS_LAYOUT_SPACING', environment);
17
+ const subPackageDepth = readEnvNumber('NEXT_PUBLIC_SETTINGS_SUBPACKAGE_DEPTH', environment);
18
+
19
+ return {
20
+ showCycles: readEnvBoolean('NEXT_PUBLIC_SETTINGS_SHOW_CYCLES', environment) ?? false,
21
+ cytoscapeLayout: isSupportedLayout(layout) ? layout : 'concentric',
22
+ cytoscapeLayoutSpacing:
23
+ layoutSpacing !== undefined && layoutSpacing >= 0.1 && layoutSpacing <= 1 ? layoutSpacing : 1,
24
+ showCompoundNodes:
25
+ readEnvBoolean('NEXT_PUBLIC_SETTINGS_SHOW_COMPOUNDNODES', environment) ?? true,
26
+ showVendorPackages:
27
+ readEnvBoolean('NEXT_PUBLIC_SETTINGS_SHOW_VENDORPACKAGES', environment) ?? false,
28
+ subPackageDepth:
29
+ subPackageDepth !== undefined && Number.isInteger(subPackageDepth) && subPackageDepth >= 1
30
+ ? subPackageDepth
31
+ : 1,
32
+ };
33
+ }
34
+
35
+ /*** Determines whether an environment value names a supported Cytoscape layout. */
36
+ function isSupportedLayout(value: string | undefined): value is LayoutOptions['name'] {
37
+ return (
38
+ value === 'breadthfirst' ||
39
+ value === 'circle' ||
40
+ value === 'concentric' ||
41
+ value === 'elk' ||
42
+ value === 'grid'
43
+ );
44
+ }
45
+
46
+ interface SettingsEnvironment {
47
+ readonly showCycles: boolean;
48
+ readonly cytoscapeLayout: LayoutOptions['name'];
49
+ readonly cytoscapeLayoutSpacing: number;
50
+ readonly showCompoundNodes: boolean;
51
+ readonly showVendorPackages: boolean;
52
+ readonly subPackageDepth: number;
53
+ }
package/src/i18n/en.ts CHANGED
@@ -7,15 +7,34 @@ const en: ILanguage = {
7
7
 
8
8
  // Settings
9
9
  'settings.title': 'Settings',
10
+ 'settings.tree': 'Tree',
11
+ 'settings.rules': 'Rules',
12
+ 'settings.export': 'Export',
13
+ 'sidebar.tools': 'Sidebar tools',
10
14
  'settings.showCompoundNodes': 'Show Compound Nodes',
11
15
  'settings.showVendorPackages': 'Show Vendor',
12
16
  'settings.subPackageDepth': 'Subpackage depth',
13
- 'settings.audit': 'Audit',
14
- 'settings.download': 'Download',
15
17
  'settings.layout': 'Layout',
16
18
  'settings.layoutSpacing': 'Layout Spacing',
17
19
  'settings.filter': 'Filter',
18
20
 
21
+ // Audit
22
+ 'audit.rules': 'Audit rules',
23
+ 'audit.loading': 'Loading audit…',
24
+ 'audit.loadError': 'Unable to load audit details.',
25
+ 'audit.noRules': 'No audit rules are enabled.',
26
+ 'audit.cycle': 'Cycle',
27
+ 'audit.cycles': 'cycles',
28
+ 'audit.findings': 'findings',
29
+ 'audit.imports': 'imports',
30
+ 'audit.cyclePath': 'Cycle path',
31
+ 'audit.dependencyEvidence': 'Dependency evidence',
32
+ 'audit.packages': 'packages',
33
+ 'audit.dependencyEdges': 'dependency edges',
34
+ 'audit.noEvidence': 'No source/import evidence available.',
35
+ 'audit.closeInspector': 'Close cycle inspector',
36
+ 'audit.rule.cyclicDependencies': 'Cyclic Dependencies',
37
+
19
38
  // Layouts
20
39
  breadthfirst: 'Breadthfirst',
21
40
  circle: 'Circle',
package/src/i18n/i18n.ts CHANGED
@@ -3,10 +3,9 @@ import en from '@/i18n/en';
3
3
  const languages = { en };
4
4
  const activeLang = languages.en;
5
5
 
6
+ /*** Returns the localized string for a translation key. */
6
7
  export function t(term: string) {
7
8
  return activeLang[term] || `{${term}}`;
8
9
  }
9
10
 
10
- export interface ILanguage {
11
- readonly [key: string]: string;
12
- }
11
+ export type ILanguage = Readonly<Record<string, string>>;
@@ -1,6 +1,7 @@
1
1
  import type { StylesheetJson } from 'cytoscape';
2
2
 
3
3
  // No specific 'breadthfirst' styles for now
4
+ /*** Returns Cytoscape style overrides for this layout. */
4
5
  export function getStyle() {
5
6
  const style: StylesheetJson = [];
6
7
 
@@ -1,5 +1,6 @@
1
1
  import type { StylesheetJson } from 'cytoscape';
2
2
 
3
+ /*** Returns Cytoscape style overrides for this layout. */
3
4
  export function getStyle() {
4
5
  const styles: StylesheetJson = [
5
6
  {
@@ -1,6 +1,7 @@
1
1
  import type { /* NodeSingular, */ StylesheetJson } from 'cytoscape';
2
2
 
3
3
  // No specific 'concentric' styles for now
4
+ /*** Returns Cytoscape style overrides for this layout. */
4
5
  export function getStyle() {
5
6
  const style: StylesheetJson = [
6
7
  {
@@ -1,6 +1,4 @@
1
- import cytoscape from 'cytoscape';
2
- import elk from 'cytoscape-elk';
3
- cytoscape.use(elk as cytoscape.Ext);
1
+ import type cytoscape from 'cytoscape';
4
2
 
5
3
  type ElkLayoutOptions = cytoscape.BaseLayoutOptions &
6
4
  cytoscape.AnimatedLayoutOptions & {
@@ -1,5 +1,6 @@
1
1
  import type { StylesheetJson } from 'cytoscape';
2
2
 
3
+ /*** Returns Cytoscape style overrides for this layout. */
3
4
  export function getStyle() {
4
5
  const style: StylesheetJson = [
5
6
  {
@@ -1,5 +1,6 @@
1
1
  import type { ElementsDefinition } from 'cytoscape';
2
2
 
3
+ /*** Computes edge-weight thresholds and bucket counts. */
3
4
  export function getWeightBuckets(
4
5
  categoryCount: number,
5
6
  algorithm: 'linear' | 'log' | 'quantile' = 'linear',
@@ -49,7 +50,8 @@ export function getWeightBuckets(
49
50
  };
50
51
  }
51
52
 
52
- export function getMaxEdgeWeight(filteredElements: ElementsDefinition) {
53
+ /*** Returns the largest edge weight in the graph. */
54
+ function getMaxEdgeWeight(filteredElements: ElementsDefinition) {
53
55
  return (
54
56
  filteredElements?.edges.reduce((max, edge) => {
55
57
  return edge.data.weight > max ? edge.data.weight : max;
@@ -1,5 +1,6 @@
1
1
  import type { StylesheetJson } from 'cytoscape';
2
2
 
3
+ /*** Returns Cytoscape style overrides for this layout. */
3
4
  export function getStyle() {
4
5
  const style: StylesheetJson = [
5
6
  {
@@ -1,14 +1,10 @@
1
1
  export { layout as breadthfirstLayout } from '@/layouts/breadthfirst/layout';
2
2
  export { getStyle as getBreadthfirstStyle } from '@/layouts/breadthfirst/style';
3
-
4
3
  export { layout as circleLayout } from '@/layouts/circle/layout';
5
4
  export { getStyle as getCircleStyle } from '@/layouts/circle/style';
6
-
7
5
  export { layout as concentricLayout } from '@/layouts/concentric/layout';
8
6
  export { getStyle as getConcentricStyle } from '@/layouts/concentric/style';
9
-
10
7
  export { layout as elkLayout } from '@/layouts/elk/layout';
11
8
  export { getStyle as getElkStyle } from '@/layouts/elk/style';
12
-
13
9
  export { layout as gridLayout } from '@/layouts/grid/layout';
14
10
  export { getStyle as getGridStyle } from '@/layouts/grid/style';
@@ -1,5 +1,6 @@
1
- import type { ElementsDefinition, NodeSingular, StylesheetJson } from 'cytoscape';
1
+ import type { ElementsDefinition, StylesheetJson } from 'cytoscape';
2
2
 
3
+ import { createCompoundOpacityIndex } from '@/features/graph-view/utils/createCompoundOpacityIndex';
3
4
  import { getWeightBuckets } from '@/layouts/getWeightBuckets';
4
5
 
5
6
  export type ThemeKey = 'dark' | 'light';
@@ -7,7 +8,6 @@ export type ThemeKey = 'dark' | 'light';
7
8
  const palette = {
8
9
  light: {
9
10
  canvasBg: '#ffffff',
10
- // edge: '#9E9E9E',
11
11
  edge: '#000',
12
12
  weightXs: '#000',
13
13
  weightMd: '#000',
@@ -18,6 +18,7 @@ const palette = {
18
18
  nodeBorderVendor: '#E2D5FF',
19
19
  nodeBgVendor: '#D1C4FF',
20
20
  nodeText: '#0B5FFF',
21
+ compoundBg: '#7892B3',
21
22
 
22
23
  selectedFill: '#0B5FFF',
23
24
  selectedFillVendor: '#a025aa',
@@ -36,6 +37,7 @@ const palette = {
36
37
  nodeBorder: '#2A3A4A',
37
38
  nodeBorderVendor: '#351542',
38
39
  nodeText: '#E8F0FF',
40
+ compoundBg: '#A9BCD5',
39
41
 
40
42
  selectedFill: '#2E6FFF',
41
43
  selectedFillVendor: '#4E25AA',
@@ -44,12 +46,55 @@ const palette = {
44
46
  },
45
47
  } as const;
46
48
 
49
+ /*** Returns the canvas background for the active theme. */
47
50
  export const getCanvasBg = (theme: ThemeKey) => palette[theme].canvasBg;
48
51
 
52
+ /*** Builds the shared Cytoscape styles for the active theme. */
49
53
  export function getStyle(filteredElements: ElementsDefinition, theme: ThemeKey): StylesheetJson {
50
54
  const colors = palette[theme];
51
55
  const { thresholds } = getWeightBuckets(3, 'linear', filteredElements);
52
56
 
57
+ return [
58
+ ...getNodeBaseStyles(colors),
59
+ ...getNodeInteractionStyles(colors),
60
+ ...getNodeStateStyles(colors),
61
+ ...getCompoundStyles(colors),
62
+ ...getCompoundOpacityStyles(filteredElements),
63
+ ...getEdgeBaseStyles(colors),
64
+ ...getEdgeWeightStyles(colors, thresholds),
65
+ ...getCycleEdgeStyles(colors),
66
+ ];
67
+ }
68
+
69
+ type Palette = (typeof palette)[ThemeKey];
70
+
71
+ /***
72
+ * Shows leaf neighborhoods without changing layout geometry or dimming compound descendants.
73
+ * @performance Use paint-only interaction styles; border size, labels and dimensions stay stable.
74
+ */
75
+ function getNodeInteractionStyles(colors: Palette): StylesheetJson {
76
+ return [
77
+ { selector: 'node:childless.hushed', style: { opacity: 0.2 } },
78
+ {
79
+ selector: 'node.highlight-outgoer, node.highlight-incomer',
80
+ style: { 'border-color': colors.selectedRing, opacity: 1 },
81
+ },
82
+ {
83
+ selector: 'node:childless.highlight',
84
+ style: { 'background-color': colors.selectedFill, color: colors.selectedText, opacity: 1 },
85
+ },
86
+ ];
87
+ }
88
+
89
+ /***
90
+ * Maps prepared node presentation data without per-element style callbacks.
91
+ * @performance
92
+ * Preserve data(label): createGraphViewModel prepares labels outside Cytoscape style recalculation.
93
+ * ZORA owns measured label width. Keep height independent of the previous rendered height, otherwise repeated
94
+ * style updates can change geometry and trigger further layout work. The style regression tests
95
+ * cover stable dimensions across theme changes and updates to prepared label data.
96
+ */
97
+ function getNodeBaseStyles(colors: Palette): StylesheetJson {
53
98
  return [
54
99
  {
55
100
  selector: 'node',
@@ -58,16 +103,12 @@ export function getStyle(filteredElements: ElementsDefinition, theme: ThemeKey):
58
103
  'background-color': colors.nodeBg,
59
104
  'border-color': colors.nodeBorder,
60
105
  color: colors.nodeText,
61
- label: 'data(name)',
106
+ label: 'data(label)',
62
107
  'text-valign': 'center',
63
108
  'text-halign': 'center',
64
- width: (node: NodeSingular) => {
65
- return node.data('name').length * 7;
66
- },
67
- height: (node: NodeSingular) => {
68
- return node.height() / 2 + 10;
69
- },
70
- padding: '8px 8px',
109
+ width: 24,
110
+ height: 24,
111
+ padding: '12px',
71
112
  'border-width': 1,
72
113
  'font-size': '14px',
73
114
  'overlay-opacity': 0, // avoid gray overlay
@@ -79,14 +120,36 @@ export function getStyle(filteredElements: ElementsDefinition, theme: ThemeKey):
79
120
  'background-color': colors.nodeBgVendor,
80
121
  },
81
122
  },
82
- { selector: 'node.packageCycle', style: { 'border-color': '#d80303', 'border-width': 3 } },
123
+ ];
124
+ }
125
+
126
+ /***
127
+ * Preserves selected-node and audit-cycle presentation.
128
+ * @performance Selection uses an outline: changing border width changes layout dimensions.
129
+ * Audit overlays retain their existing geometry and remain separate from transient interactions.
130
+ */
131
+ function getNodeStateStyles(colors: Palette): StylesheetJson {
132
+ return [
133
+ {
134
+ selector: 'node.auditCycle',
135
+ style: {
136
+ 'border-color': 'data(auditCycleColor)',
137
+ 'border-width': 5,
138
+ opacity: 1,
139
+ 'underlay-color': 'data(auditCycleColor)',
140
+ 'underlay-opacity': 0.18,
141
+ 'underlay-padding': 4,
142
+ 'z-index': 999,
143
+ },
144
+ },
83
145
  { selector: 'node.isParent', style: { 'font-weight': 'bold' } },
84
146
  {
85
147
  selector: 'node:selected',
86
148
  style: {
87
149
  'background-color': colors.selectedFill,
88
150
  'border-color': colors.selectedRing,
89
- 'border-width': 3,
151
+ 'outline-color': colors.selectedRing,
152
+ 'outline-width': 2,
90
153
  color: colors.selectedText,
91
154
  'background-opacity': 1,
92
155
  opacity: 1,
@@ -97,23 +160,35 @@ export function getStyle(filteredElements: ElementsDefinition, theme: ThemeKey):
97
160
  selector: 'node.isVendor:selected',
98
161
  style: { 'background-color': colors.selectedFillVendor },
99
162
  },
163
+ ];
164
+ }
165
+
166
+ /***
167
+ * Shows softly tinted compound boundaries with bounded, prepared nested-depth opacity.
168
+ * @performance Keep grouping paint-only; do not add nested DOM surfaces or extra graph elements.
169
+ */
170
+ function getCompoundStyles(colors: Palette): StylesheetJson {
171
+ return [
100
172
  {
101
173
  selector: 'node:parent, node:parent:selected',
102
174
  style: {
103
- 'background-opacity': 0.3,
104
- 'background-color': colors.selectedFill,
175
+ 'background-opacity': 0.04,
176
+ 'background-color': colors.compoundBg,
105
177
  color: colors.nodeText,
106
- 'border-width': 2,
178
+ 'border-width': 1,
179
+ 'border-opacity': 0.35,
107
180
  'border-color': colors.nodeBorder,
108
181
  'text-valign': 'top',
109
182
  'text-margin-y': -5,
110
- padding: '10px',
111
- 'padding-top': '20px',
183
+ padding: '16px',
184
+ 'text-background-color': colors.canvasBg,
185
+ 'text-background-opacity': 0.85,
186
+ 'text-background-padding': '3px',
112
187
  'text-halign': 'center',
113
188
  'font-size': 14,
114
189
  'font-weight': 'bold',
115
190
  'font-style': 'italic',
116
- label: 'data(name)',
191
+ label: 'data(label)',
117
192
  },
118
193
  },
119
194
  {
@@ -123,6 +198,50 @@ export function getStyle(filteredElements: ElementsDefinition, theme: ThemeKey):
123
198
  'border-color': colors.nodeBorderVendor,
124
199
  },
125
200
  },
201
+ {
202
+ selector: 'node:parent.auditCycle, node:parent.auditCycle:selected',
203
+ style: {
204
+ 'underlay-opacity': 0,
205
+ 'border-color': 'data(auditCycleColor)',
206
+ 'border-opacity': 0.8,
207
+ 'outline-color': 'data(auditCycleColor)',
208
+ 'outline-width': 2,
209
+ color: 'data(auditCycleColor)',
210
+ },
211
+ },
212
+ ];
213
+ }
214
+
215
+ /***
216
+ * Emits one numeric paint rule per visible depth, including depths created by detached endpoints.
217
+ * @performance Bound stylesheet size by hierarchy depth, not node count; no style callbacks.
218
+ */
219
+ function getCompoundOpacityStyles(elements: ElementsDefinition): StylesheetJson {
220
+ const ids = new Set(elements.nodes.map(node => node.data.id));
221
+ const parents = new Map(
222
+ elements.nodes.flatMap(node => {
223
+ const { id, parent } = node.data;
224
+ return typeof id === 'string' && typeof parent === 'string' && ids.has(parent)
225
+ ? [[id, parent] as const]
226
+ : [];
227
+ })
228
+ );
229
+ const opacities = createCompoundOpacityIndex(parents, new Set());
230
+ return [...new Set(opacities.values())].map(opacity => ({
231
+ selector: `node:parent[compoundFillOpacity = ${opacity}]`,
232
+ style: { 'background-opacity': opacity },
233
+ }));
234
+ }
235
+
236
+ /***
237
+ * Keeps ordinary directed edges opaque to avoid the extra rendering cost of translucent arrows.
238
+ * @performance
239
+ * Hushed edges are an intentional interaction state, not the base rendering policy. Retain arrows
240
+ * and loop-capable routing: cheaper edge styles must not silently remove dependency direction or
241
+ * lifted self-loops. Measure and discuss those visual tradeoffs before changing this baseline.
242
+ */
243
+ function getEdgeBaseStyles(colors: Palette): StylesheetJson {
244
+ return [
126
245
  {
127
246
  selector: 'edge',
128
247
  style: {
@@ -131,7 +250,10 @@ export function getStyle(filteredElements: ElementsDefinition, theme: ThemeKey):
131
250
  'target-arrow-shape': 'chevron',
132
251
  'target-arrow-fill': 'filled',
133
252
  'line-color': colors.edge,
134
- 'curve-style': 'straight',
253
+ // Bezier routing keeps lifted self-loops renderable; line endpoints avoid invalid compound overlaps.
254
+ 'curve-style': 'bezier',
255
+ 'source-endpoint': 'outside-to-line',
256
+ 'target-endpoint': 'outside-to-line',
135
257
  opacity: 1,
136
258
  'line-opacity': 1,
137
259
  },
@@ -153,6 +275,12 @@ export function getStyle(filteredElements: ElementsDefinition, theme: ThemeKey):
153
275
  'source-arrow-color': colors.weightXl,
154
276
  },
155
277
  },
278
+ ];
279
+ }
280
+
281
+ /*** Applies the existing aggregate dependency-weight buckets. */
282
+ function getEdgeWeightStyles(colors: Palette, thresholds: readonly number[]): StylesheetJson {
283
+ return [
156
284
  { selector: 'edge[weight <= 1]', style: { label: '' } },
157
285
  {
158
286
  selector: `edge[weight > 1][weight <= ${thresholds[0]}]`,
@@ -179,13 +307,32 @@ export function getStyle(filteredElements: ElementsDefinition, theme: ThemeKey):
179
307
  'target-arrow-color': colors.weightXl,
180
308
  },
181
309
  },
310
+ ];
311
+ }
312
+
313
+ /*** Shows audit steps above compound content only for active cycle edges. */
314
+ function getCycleEdgeStyles(colors: Palette): StylesheetJson {
315
+ return [
182
316
  {
183
- selector: 'edge.packageCycle',
184
- style: { 'line-color': '#d80303', 'target-arrow-color': '#d80303' },
185
- },
186
- {
187
- selector: 'edge.packageCycle',
188
- style: { 'line-color': '#d80303', 'target-arrow-color': '#d80303' },
317
+ selector: 'edge.auditCycle',
318
+ style: {
319
+ width: 6,
320
+ opacity: 1,
321
+ 'line-opacity': 1,
322
+ 'arrow-scale': 1.5,
323
+ 'line-color': 'data(auditCycleColor)',
324
+ 'target-arrow-color': 'data(auditCycleColor)',
325
+ color: 'data(auditCycleColor)',
326
+ label: 'data(auditCycleStep)',
327
+ 'font-size': 12,
328
+ 'font-weight': 'bold',
329
+ 'text-background-color': colors.canvasBg,
330
+ 'text-background-opacity': 0.9,
331
+ 'text-background-padding': '3px',
332
+ 'z-compound-depth': 'top',
333
+ 'z-index-compare': 'manual',
334
+ 'z-index': 9999,
335
+ },
189
336
  },
190
337
  ];
191
338
  }
@@ -1,48 +1,96 @@
1
1
  'use client';
2
- import type { ElementsDefinition } from 'cytoscape';
2
+ import { useState } from 'react';
3
3
 
4
- import { useEffect, useState } from 'react';
5
- import { getGraphAction } from '@/app/actions/graph.actions';
6
4
  import Breadcrumb from '@/components/Breadcrumb';
7
5
  import Header from '@/components/Header';
8
- import Loader from '@/components/Loader';
9
- import Settings from '@/components/Settings';
10
- import { Cytoscape } from '@/components/Cytoscape';
6
+ import ProjectLoadError from '@/components/ProjectLoadError';
11
7
  import { SettingsProvider } from '@/contexts/SettingsContext';
8
+ import { useCycleSelection } from '@/features/audit/adapters/inbound/react/useCycleSelection';
9
+ import { resolveProjectTreeNavigation } from '@/features/project-tree/application/use-cases/resolveProjectTreeNavigation';
10
+ import { findProjectTreeNodeByGraphPackage } from '@/features/project-tree/utils/findProjectTreeNodeByGraphPackage';
11
+ import { HomeGraph } from '@/screens/home/HomeGraph';
12
+ import { HomeSidebar } from '@/screens/home/HomeSidebar';
13
+ import type { Audit } from '@/types/audit';
14
+ import type { CycleInspection } from '@/types/auditVisualization';
15
+ import type { ProjectOverview } from '@/types/projectAnalysis';
16
+ import type { ProjectAnalysisActionResult } from '@/types/projectAnalysisActionResult';
17
+ import type { ProjectTreeNode } from '@/types/projectTree';
12
18
 
13
- export default function HomeScreen() {
19
+ /*** Renders the PKGViz home screen and composes project navigation, diagnostics, and the graph. */
20
+ export default function HomeScreen({ project }: HomeScreenProps) {
14
21
  const [currentPackage, setCurrentPackage] = useState<string>('');
15
- const [packageGraph, setPackageGraph] = useState<ElementsDefinition | null>(null);
22
+ const packageGraph = project.ok ? project.value.graph : null;
23
+ const projectTree = project.ok ? project.value.tree : [];
24
+ const [selectedTreeId, setSelectedTreeId] = useState<string | null>(null);
25
+ const auditEvaluation = project.ok ? project.value.evaluation : null;
26
+ const projectError = project.ok ? null : project.error;
27
+ const cycleSelection = useCycleSelection(auditEvaluation?.cyclicPackages ?? EMPTY_CYCLES);
28
+ const [cycleInspection, setCycleInspection] = useState<CycleInspection | null>(null);
16
29
 
17
- useEffect(() => {
18
- if (!packageGraph) {
19
- getGraphAction().then(setPackageGraph);
20
- }
21
- }, [packageGraph]);
30
+ /*** Navigates graph scope and mirrors the matching package selection in the project tree. */
31
+ const navigateToPackage = (path: string) => {
32
+ const packageName = normalizeGraphPackage(path);
33
+ const matchingTreeNode = findProjectTreeNodeByGraphPackage(projectTree, packageName);
34
+ setCurrentPackage(packageName);
35
+ setSelectedTreeId(matchingTreeNode?.id ?? null);
36
+ };
37
+
38
+ /*** Navigates only into packages with graph descendants; selecting a leaf keeps the current view. */
39
+ const selectProjectTreeNode = (node: ProjectTreeNode) => {
40
+ setSelectedTreeId(node.id);
41
+ setCurrentPackage(
42
+ resolveProjectTreeNavigation(
43
+ node,
44
+ packageGraph?.nodes.map(candidate => String(candidate.data.id ?? '')) ?? [],
45
+ currentPackage
46
+ )
47
+ );
48
+ };
22
49
 
23
50
  return (
24
51
  <>
25
52
  <Header title="nav.packages">
26
- <Breadcrumb
27
- path={currentPackage.replace(/\./g, '/')}
28
- onNavigate={(path: string) => setCurrentPackage(path.replace(/\//g, '.'))}
29
- />
53
+ <Breadcrumb path={currentPackage.replace(/\./g, '/')} onNavigate={navigateToPackage} />
30
54
  </Header>
31
-
32
55
  <SettingsProvider>
33
- <main data-testid="main" className="flex flex-1 flex-row dark:bg-[#171717]">
34
- <Settings />
35
- {packageGraph ? (
36
- <Cytoscape
56
+ <main
57
+ data-testid="main"
58
+ className="flex min-h-0 min-w-0 flex-1 flex-row overflow-hidden dark:bg-[#171717]"
59
+ >
60
+ <HomeSidebar
61
+ evaluation={auditEvaluation}
62
+ projectTree={projectTree}
63
+ selectedTreeId={selectedTreeId}
64
+ onProjectTreeSelect={selectProjectTreeNode}
65
+ cycleSelection={cycleSelection}
66
+ inspectedCycleId={cycleInspection?.id ?? null}
67
+ onCycleInspectionChange={setCycleInspection}
68
+ />
69
+ {projectError ? (
70
+ <ProjectLoadError message={projectError} />
71
+ ) : (
72
+ <HomeGraph
37
73
  currentPackage={currentPackage}
38
- setCurrentPackage={setCurrentPackage}
74
+ cycleHighlights={cycleSelection.highlights}
75
+ cycleInspection={cycleInspection}
39
76
  packageGraph={packageGraph}
77
+ setCurrentPackage={navigateToPackage}
78
+ onCloseInspection={() => setCycleInspection(null)}
40
79
  />
41
- ) : (
42
- <Loader />
43
80
  )}
44
81
  </main>
45
82
  </SettingsProvider>
46
83
  </>
47
84
  );
48
85
  }
86
+
87
+ const EMPTY_CYCLES: NonNullable<Audit['evaluation']>['cyclicPackages'] = [];
88
+
89
+ interface HomeScreenProps {
90
+ readonly project: ProjectAnalysisActionResult<ProjectOverview>;
91
+ }
92
+
93
+ /*** Normalizes graph navigation paths to the package-id representation used by Cytoscape. */
94
+ function normalizeGraphPackage(path: string): string {
95
+ return path.replaceAll('/', '.').replace(/^\.+|\.+$/g, '');
96
+ }