nebulosity 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (522) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-path-routes-manifest.json +18 -0
  3. package/.next/build/chunks/[root-of-the-server]__05i36w1t4lzaf._.js +502 -0
  4. package/.next/build/chunks/[root-of-the-server]__1kki86fknkq7y._.js +191 -0
  5. package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_1gfkiq9iz8yb8._.js +13 -0
  6. package/.next/build/chunks/[turbopack]_runtime.js +1056 -0
  7. package/.next/build/chunks/node_modules_20v-8wlfss9m1._.js +7192 -0
  8. package/.next/build/chunks/pool_entry-[turbopack-node]_transforms_postcss_ts_0tp-k2vy8nj6i._.js +6 -0
  9. package/.next/build/package.json +1 -0
  10. package/.next/build-manifest.json +54 -0
  11. package/.next/diagnostics/build-diagnostics.json +6 -0
  12. package/.next/diagnostics/framework.json +1 -0
  13. package/.next/diagnostics/route-bundle-stats.json +26 -0
  14. package/.next/export-marker.json +6 -0
  15. package/.next/fallback-build-manifest.json +15 -0
  16. package/.next/images-manifest.json +68 -0
  17. package/.next/node_modules/@huggingface/transformers-31f28a0eb9b916d1/index.cjs +1 -0
  18. package/.next/node_modules/@huggingface/transformers-31f28a0eb9b916d1/index.mjs +1 -0
  19. package/.next/node_modules/@huggingface/transformers-31f28a0eb9b916d1/package.json +9 -0
  20. package/.next/node_modules/better-sqlite3-90e2652d1716b047/index.cjs +1 -0
  21. package/.next/node_modules/better-sqlite3-90e2652d1716b047/index.mjs +2 -0
  22. package/.next/node_modules/better-sqlite3-90e2652d1716b047/package.json +9 -0
  23. package/.next/node_modules/typescript-279735412610ef8d/index.cjs +1 -0
  24. package/.next/node_modules/typescript-279735412610ef8d/index.mjs +2 -0
  25. package/.next/node_modules/typescript-279735412610ef8d/package.json +9 -0
  26. package/.next/package.json +1 -0
  27. package/.next/prerender-manifest.json +161 -0
  28. package/.next/required-server-files.js +369 -0
  29. package/.next/required-server-files.json +369 -0
  30. package/.next/routes-manifest.json +152 -0
  31. package/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
  32. package/.next/server/app/_global-error/page/build-manifest.json +21 -0
  33. package/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
  34. package/.next/server/app/_global-error/page/react-loadable-manifest.json +1 -0
  35. package/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
  36. package/.next/server/app/_global-error/page.js +10 -0
  37. package/.next/server/app/_global-error/page_client-reference-manifest.js +3 -0
  38. package/.next/server/app/_global-error.html +1 -0
  39. package/.next/server/app/_global-error.meta +30 -0
  40. package/.next/server/app/_global-error.rsc +19 -0
  41. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +24 -0
  42. package/.next/server/app/_global-error.segments/_full.segment.rsc +19 -0
  43. package/.next/server/app/_global-error.segments/_tree.segment.rsc +2 -0
  44. package/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
  45. package/.next/server/app/_not-found/page/build-manifest.json +21 -0
  46. package/.next/server/app/_not-found/page/next-font-manifest.json +12 -0
  47. package/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
  48. package/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
  49. package/.next/server/app/_not-found/page.js +12 -0
  50. package/.next/server/app/_not-found/page_client-reference-manifest.js +3 -0
  51. package/.next/server/app/_not-found.html +1 -0
  52. package/.next/server/app/_not-found.meta +35 -0
  53. package/.next/server/app/_not-found.rsc +21 -0
  54. package/.next/server/app/_not-found.segments/_full.segment.rsc +21 -0
  55. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +27 -0
  56. package/.next/server/app/_not-found.segments/_tree.segment.rsc +4 -0
  57. package/.next/server/app/api/describe/route/app-paths-manifest.json +3 -0
  58. package/.next/server/app/api/describe/route/build-manifest.json +12 -0
  59. package/.next/server/app/api/describe/route/server-reference-manifest.json +4 -0
  60. package/.next/server/app/api/describe/route.js +9 -0
  61. package/.next/server/app/api/describe/route_client-reference-manifest.js +3 -0
  62. package/.next/server/app/api/imports/route/app-paths-manifest.json +3 -0
  63. package/.next/server/app/api/imports/route/build-manifest.json +12 -0
  64. package/.next/server/app/api/imports/route/server-reference-manifest.json +4 -0
  65. package/.next/server/app/api/imports/route.js +9 -0
  66. package/.next/server/app/api/imports/route_client-reference-manifest.js +3 -0
  67. package/.next/server/app/api/index/route/app-paths-manifest.json +3 -0
  68. package/.next/server/app/api/index/route/build-manifest.json +12 -0
  69. package/.next/server/app/api/index/route/server-reference-manifest.json +4 -0
  70. package/.next/server/app/api/index/route.js +10 -0
  71. package/.next/server/app/api/index/route_client-reference-manifest.js +3 -0
  72. package/.next/server/app/api/level/route/app-paths-manifest.json +3 -0
  73. package/.next/server/app/api/level/route/build-manifest.json +12 -0
  74. package/.next/server/app/api/level/route/server-reference-manifest.json +4 -0
  75. package/.next/server/app/api/level/route.js +9 -0
  76. package/.next/server/app/api/level/route_client-reference-manifest.js +3 -0
  77. package/.next/server/app/api/live/route/app-paths-manifest.json +3 -0
  78. package/.next/server/app/api/live/route/build-manifest.json +12 -0
  79. package/.next/server/app/api/live/route/server-reference-manifest.json +4 -0
  80. package/.next/server/app/api/live/route.js +9 -0
  81. package/.next/server/app/api/live/route_client-reference-manifest.js +3 -0
  82. package/.next/server/app/api/live/stream/route/app-paths-manifest.json +3 -0
  83. package/.next/server/app/api/live/stream/route/build-manifest.json +12 -0
  84. package/.next/server/app/api/live/stream/route/server-reference-manifest.json +4 -0
  85. package/.next/server/app/api/live/stream/route.js +7 -0
  86. package/.next/server/app/api/live/stream/route_client-reference-manifest.js +3 -0
  87. package/.next/server/app/api/repos/route/app-paths-manifest.json +3 -0
  88. package/.next/server/app/api/repos/route/build-manifest.json +12 -0
  89. package/.next/server/app/api/repos/route/server-reference-manifest.json +4 -0
  90. package/.next/server/app/api/repos/route.js +10 -0
  91. package/.next/server/app/api/repos/route_client-reference-manifest.js +3 -0
  92. package/.next/server/app/api/search/route/app-paths-manifest.json +3 -0
  93. package/.next/server/app/api/search/route/build-manifest.json +12 -0
  94. package/.next/server/app/api/search/route/server-reference-manifest.json +4 -0
  95. package/.next/server/app/api/search/route.js +10 -0
  96. package/.next/server/app/api/search/route_client-reference-manifest.js +3 -0
  97. package/.next/server/app/api/source/route/app-paths-manifest.json +3 -0
  98. package/.next/server/app/api/source/route/build-manifest.json +12 -0
  99. package/.next/server/app/api/source/route/server-reference-manifest.json +4 -0
  100. package/.next/server/app/api/source/route.js +9 -0
  101. package/.next/server/app/api/source/route_client-reference-manifest.js +3 -0
  102. package/.next/server/app/api/status/route/app-paths-manifest.json +3 -0
  103. package/.next/server/app/api/status/route/build-manifest.json +12 -0
  104. package/.next/server/app/api/status/route/server-reference-manifest.json +4 -0
  105. package/.next/server/app/api/status/route.js +11 -0
  106. package/.next/server/app/api/status/route_client-reference-manifest.js +3 -0
  107. package/.next/server/app/api/summarize/route/app-paths-manifest.json +3 -0
  108. package/.next/server/app/api/summarize/route/build-manifest.json +12 -0
  109. package/.next/server/app/api/summarize/route/server-reference-manifest.json +4 -0
  110. package/.next/server/app/api/summarize/route.js +10 -0
  111. package/.next/server/app/api/summarize/route_client-reference-manifest.js +3 -0
  112. package/.next/server/app/favicon.ico/route/app-paths-manifest.json +3 -0
  113. package/.next/server/app/favicon.ico/route/build-manifest.json +12 -0
  114. package/.next/server/app/favicon.ico/route.js +8 -0
  115. package/.next/server/app/favicon.ico.body +0 -0
  116. package/.next/server/app/favicon.ico.meta +1 -0
  117. package/.next/server/app/icon.svg/route/app-paths-manifest.json +3 -0
  118. package/.next/server/app/icon.svg/route/build-manifest.json +12 -0
  119. package/.next/server/app/icon.svg/route.js +8 -0
  120. package/.next/server/app/icon.svg.body +31 -0
  121. package/.next/server/app/icon.svg.meta +1 -0
  122. package/.next/server/app/index.html +1 -0
  123. package/.next/server/app/index.meta +29 -0
  124. package/.next/server/app/index.rsc +25 -0
  125. package/.next/server/app/index.segments/__PAGE__.segment.rsc +27 -0
  126. package/.next/server/app/index.segments/_full.segment.rsc +25 -0
  127. package/.next/server/app/index.segments/_tree.segment.rsc +7 -0
  128. package/.next/server/app/page/app-paths-manifest.json +3 -0
  129. package/.next/server/app/page/build-manifest.json +21 -0
  130. package/.next/server/app/page/next-font-manifest.json +12 -0
  131. package/.next/server/app/page/react-loadable-manifest.json +1 -0
  132. package/.next/server/app/page/server-reference-manifest.json +4 -0
  133. package/.next/server/app/page.js +14 -0
  134. package/.next/server/app/page_client-reference-manifest.js +3 -0
  135. package/.next/server/app-paths-manifest.json +18 -0
  136. package/.next/server/chunks/[externals]_@huggingface_transformers_05bd-zziu8167._.js +3 -0
  137. package/.next/server/chunks/[externals]__0zvqltcvrhqse._.js +3 -0
  138. package/.next/server/chunks/[externals]__1i28q8c2pqfup._.js +3 -0
  139. package/.next/server/chunks/[externals]__1xk8lm5my7js6._.js +3 -0
  140. package/.next/server/chunks/[root-of-the-server]__051s9u4mcetdb._.js +6 -0
  141. package/.next/server/chunks/[root-of-the-server]__06blyow8irfw3._.js +10 -0
  142. package/.next/server/chunks/[root-of-the-server]__0_6g347u9qa57._.js +3 -0
  143. package/.next/server/chunks/[root-of-the-server]__0bmqko-d8fd5k._.js +10 -0
  144. package/.next/server/chunks/[root-of-the-server]__0cmmlmvta4i0g._.js +10 -0
  145. package/.next/server/chunks/[root-of-the-server]__0dt_sq7idt1os._.js +3 -0
  146. package/.next/server/chunks/[root-of-the-server]__0hgacpyjkqojz._.js +3 -0
  147. package/.next/server/chunks/[root-of-the-server]__0jgr1dt1j95ku._.js +10 -0
  148. package/.next/server/chunks/[root-of-the-server]__0m0n163465-zb._.js +10 -0
  149. package/.next/server/chunks/[root-of-the-server]__0p7epsgp2b5m3._.js +10 -0
  150. package/.next/server/chunks/[root-of-the-server]__0vyz_1ns_nua5._.js +3 -0
  151. package/.next/server/chunks/[root-of-the-server]__1167paf30_wq8._.js +3 -0
  152. package/.next/server/chunks/[root-of-the-server]__15bjnu5yieigz._.js +5 -0
  153. package/.next/server/chunks/[root-of-the-server]__16ik54iewx9z3._.js +6 -0
  154. package/.next/server/chunks/[root-of-the-server]__1bhqa26ehjh0e._.js +3 -0
  155. package/.next/server/chunks/[root-of-the-server]__1cfurs3fcv85q._.js +3 -0
  156. package/.next/server/chunks/[root-of-the-server]__1i5e5sqg45ens._.js +10 -0
  157. package/.next/server/chunks/[root-of-the-server]__1qmpom1yjgoe6._.js +3 -0
  158. package/.next/server/chunks/[root-of-the-server]__1vjt0neva-819._.js +10 -0
  159. package/.next/server/chunks/[root-of-the-server]__1z2mv_4lkoix_._.js +68 -0
  160. package/.next/server/chunks/[root-of-the-server]__1zjq62ps9p2xm._.js +3 -0
  161. package/.next/server/chunks/[turbopack]_runtime.js +1069 -0
  162. package/.next/server/chunks/_0btv5i-8jzrkh._.js +3 -0
  163. package/.next/server/chunks/_0htkitjiqi3b4._.js +17 -0
  164. package/.next/server/chunks/_0ile-nyzjkyl_._.js +3 -0
  165. package/.next/server/chunks/_0uqus0ehnijt-._.js +3 -0
  166. package/.next/server/chunks/_0wemi40xtpagp._.js +3 -0
  167. package/.next/server/chunks/_143ny4gdo3fhb._.js +3 -0
  168. package/.next/server/chunks/_17i2yyl2kj1bt._.js +3 -0
  169. package/.next/server/chunks/_18retujk6-b40._.js +3 -0
  170. package/.next/server/chunks/_1cl7bj2-5tjc0._.js +3 -0
  171. package/.next/server/chunks/_1cstru0a1pgds._.js +52 -0
  172. package/.next/server/chunks/_1mxn_lbuesj3_._.js +3 -0
  173. package/.next/server/chunks/_1vrfq569dhlwo._.js +3 -0
  174. package/.next/server/chunks/_1w2kaw7y48w70._.js +3 -0
  175. package/.next/server/chunks/_1zws2vt6wj4h9._.js +3 -0
  176. package/.next/server/chunks/_next-internal_server_app_api_describe_route_actions_0vr6420i-ro3y.js +3 -0
  177. package/.next/server/chunks/_next-internal_server_app_api_imports_route_actions_0ky48tq6vuffm.js +3 -0
  178. package/.next/server/chunks/_next-internal_server_app_api_index_route_actions_1w8-cjl9-tql7.js +3 -0
  179. package/.next/server/chunks/_next-internal_server_app_api_level_route_actions_0tn4in5bloyc8.js +3 -0
  180. package/.next/server/chunks/_next-internal_server_app_api_live_route_actions_0dk5m02naz6qd.js +3 -0
  181. package/.next/server/chunks/_next-internal_server_app_api_live_stream_route_actions_1bumpsz7v94gh.js +3 -0
  182. package/.next/server/chunks/_next-internal_server_app_api_repos_route_actions_1o27l51oy7-gf.js +3 -0
  183. package/.next/server/chunks/_next-internal_server_app_api_search_route_actions_0k0mocb1dpixh.js +3 -0
  184. package/.next/server/chunks/_next-internal_server_app_api_source_route_actions_18mmajvsmslwc.js +3 -0
  185. package/.next/server/chunks/_next-internal_server_app_api_status_route_actions_1qgdetofwsmqf.js +3 -0
  186. package/.next/server/chunks/_next-internal_server_app_api_summarize_route_actions_0iw_jzll1h8pz.js +3 -0
  187. package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_0g2jjlssn1kss.js +3 -0
  188. package/.next/server/chunks/_next-internal_server_app_icon_svg_route_actions_1r2h_ubdhjtcp.js +3 -0
  189. package/.next/server/chunks/lib_background_start_ts_11u_1uu8b9yuy._.js +10 -0
  190. package/.next/server/chunks/lib_background_start_ts_1uu0hs4_kephi._.js +3 -0
  191. package/.next/server/chunks/lib_db_store_summary-vectors_ts_019e4mun161yc._.js +3 -0
  192. package/.next/server/chunks/node_modules_next_0g8xe7c6fvfgl._.js +61 -0
  193. package/.next/server/chunks/ssr/[root-of-the-server]__0dyligeswbb47._.js +3 -0
  194. package/.next/server/chunks/ssr/[root-of-the-server]__0ekw8p2ramybv._.js +3 -0
  195. package/.next/server/chunks/ssr/[root-of-the-server]__0fwbsqt9e2tr-._.js +3 -0
  196. package/.next/server/chunks/ssr/[root-of-the-server]__0py1t7q9ntzgc._.js +3 -0
  197. package/.next/server/chunks/ssr/[root-of-the-server]__17_rqmx62cz59._.js +3 -0
  198. package/.next/server/chunks/ssr/[root-of-the-server]__18ntzywv7t1cq._.js +3 -0
  199. package/.next/server/chunks/ssr/[root-of-the-server]__1cf6qt9agg_9-._.js +14 -0
  200. package/.next/server/chunks/ssr/[root-of-the-server]__1zfp6sltse4hf._.js +3 -0
  201. package/.next/server/chunks/ssr/[turbopack]_runtime.js +1069 -0
  202. package/.next/server/chunks/ssr/_0vfwmvlqr2-ce._.js +3 -0
  203. package/.next/server/chunks/ssr/_1d2woi6toj_0_._.js +3 -0
  204. package/.next/server/chunks/ssr/_1wfc1zz26g5ji._.js +9 -0
  205. package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0zi5s8-psc_d2.js +3 -0
  206. package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0pt47yrisjoev.js +3 -0
  207. package/.next/server/chunks/ssr/_next-internal_server_app_page_actions_0hhsz1jew5ls7.js +3 -0
  208. package/.next/server/chunks/ssr/app_0gw6epmxyea3q._.js +3 -0
  209. package/.next/server/chunks/ssr/node_modules_0gxlxpuufujz9._.js +33 -0
  210. package/.next/server/chunks/ssr/node_modules_@swc_helpers_cjs__interop_require_default_cjs_1ztp13a5szri_._.js +3 -0
  211. package/.next/server/chunks/ssr/node_modules_next_dist_0cbzfnmfs57ie._.js +3 -0
  212. package/.next/server/chunks/ssr/node_modules_next_dist_0janj0i6dqko7._.js +6 -0
  213. package/.next/server/chunks/ssr/node_modules_next_dist_1c5t_vsn6u8fr._.js +3 -0
  214. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0wpq8j32_ibz4._.js +3 -0
  215. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_1bi5-b2i59sgs._.js +33 -0
  216. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0symwr9mbf-fh.js +3 -0
  217. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0l_sp0xky89qu.js +3 -0
  218. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_13q04th-918ib.js +3 -0
  219. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_19fbxi_wfcafr.js +3 -0
  220. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1vmwyi-ropznv.js +3 -0
  221. package/.next/server/edge/chunks/_0o_tvsklgn-yk._.js +3 -0
  222. package/.next/server/edge/chunks/turbopack-node_modules_next_dist_esm_build_templates_edge-wrapper_1oiuqfca5iqka.js +3 -0
  223. package/.next/server/functions-config-manifest.json +25 -0
  224. package/.next/server/instrumentation/middleware-manifest.json +12 -0
  225. package/.next/server/instrumentation.js +4 -0
  226. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  227. package/.next/server/middleware/middleware-manifest.json +6 -0
  228. package/.next/server/middleware-build-manifest.js +54 -0
  229. package/.next/server/middleware-manifest.json +6 -0
  230. package/.next/server/middleware.js +5 -0
  231. package/.next/server/next-font-manifest.js +1 -0
  232. package/.next/server/next-font-manifest.json +17 -0
  233. package/.next/server/pages/404.html +1 -0
  234. package/.next/server/pages/500.html +1 -0
  235. package/.next/server/pages-manifest.json +4 -0
  236. package/.next/server/prefetch-hints.json +34 -0
  237. package/.next/server/preview-props.json +5 -0
  238. package/.next/server/server-reference-manifest.js +1 -0
  239. package/.next/server/server-reference-manifest.json +5 -0
  240. package/.next/static/chunks/02x9pxyl2d8om.css +3 -0
  241. package/.next/static/chunks/0cz1d0mv5g_q7.js +1 -0
  242. package/.next/static/chunks/0p-0bv2dzer18.js +7 -0
  243. package/.next/static/chunks/0umf92h1sb77c.js +1 -0
  244. package/.next/static/chunks/10u7p2gj02qay.js +1 -0
  245. package/.next/static/chunks/1ix59xuhq-q14.css +1 -0
  246. package/.next/static/chunks/1rljem7o1jtdo.js +1 -0
  247. package/.next/static/chunks/1tty6l8tgntgq.js +22 -0
  248. package/.next/static/chunks/31qp8jfkw0x2h.js +31 -0
  249. package/.next/static/chunks/3iyxeb0r4wnp2.js +1 -0
  250. package/.next/static/chunks/turbopack-2c3mg3r-05n1w.js +1 -0
  251. package/.next/static/media/123d516c9baeb953-s.1qc6dx89jd2qv.woff2 +0 -0
  252. package/.next/static/media/41ba59f553543ecf-s.3cb5lpr2l6xud.woff2 +0 -0
  253. package/.next/static/media/44ed10da57ebfb0e-s.2y65deuc30pmy.woff2 +0 -0
  254. package/.next/static/media/48eea41d8317494f-s.p.0wiir8udbzjvx.woff2 +0 -0
  255. package/.next/static/media/49b1d53681a91a99-s.1yd4_i3e35931.woff2 +0 -0
  256. package/.next/static/media/67bba87670b402d1-s.1u30mmyq6lnsd.woff2 +0 -0
  257. package/.next/static/media/6fca7e74603dd412-s.02z5rrbg5nt92.woff2 +0 -0
  258. package/.next/static/media/73263b2957b73100-s.2qt3tdwt-y9x_.woff2 +0 -0
  259. package/.next/static/media/7ac0b1cd3e695dd0-s.1i-sca43-qwng.woff2 +0 -0
  260. package/.next/static/media/916cd8a3bde3edf8-s.p.0oace-s_gkfks.woff2 +0 -0
  261. package/.next/static/media/a45aa1001bf9176f-s.2vemzm0itqzfy.woff2 +0 -0
  262. package/.next/static/media/a74d2542cc4c775f-s.29yke99gs9qpn.woff2 +0 -0
  263. package/.next/static/media/ab664c0e419cc841-s.3cvcemwuzoywn.woff2 +0 -0
  264. package/.next/static/media/c1b11b726b36a5e5-s.2rixgazldzomk.woff2 +0 -0
  265. package/.next/static/media/d6c1ff7e86974212-s.p.40sczeszzbjw1.woff2 +0 -0
  266. package/.next/static/media/faab88e4be272d29-s.0rgnxg2b64rzs.woff2 +0 -0
  267. package/.next/static/media/favicon.2vob68tjqpejf.ico +0 -0
  268. package/.next/static/media/icon.0absiuod-8x8n.svg +31 -0
  269. package/.next/static/prDi039MhjlFqDOOwZ4aR/_buildManifest.js +11 -0
  270. package/.next/static/prDi039MhjlFqDOOwZ4aR/_clientMiddlewareManifest.js +6 -0
  271. package/.next/static/prDi039MhjlFqDOOwZ4aR/_ssgManifest.js +1 -0
  272. package/.next/trace +1 -0
  273. package/.next/trace-build +1 -0
  274. package/.next/turbopack +0 -0
  275. package/.next/types/cache-life.d.ts +114 -0
  276. package/.next/types/root-params.d.ts +3 -0
  277. package/.next/types/routes.d.ts +83 -0
  278. package/.next/types/validator.ts +201 -0
  279. package/LICENSE +21 -0
  280. package/README.md +104 -3
  281. package/app/api/_lib/respond.ts +33 -0
  282. package/app/api/_lib/server.ts +14 -0
  283. package/app/api/describe/route.ts +22 -0
  284. package/app/api/imports/route.ts +20 -0
  285. package/app/api/index/route.ts +38 -0
  286. package/app/api/level/route.ts +28 -0
  287. package/app/api/live/route.ts +17 -0
  288. package/app/api/live/stream/route.ts +47 -0
  289. package/app/api/repos/route.ts +28 -0
  290. package/app/api/search/route.ts +23 -0
  291. package/app/api/source/route.ts +21 -0
  292. package/app/api/status/route.ts +25 -0
  293. package/app/api/summarize/route.ts +24 -0
  294. package/app/favicon.ico +0 -0
  295. package/app/globals.css +158 -0
  296. package/app/icon.svg +31 -0
  297. package/app/layout.tsx +39 -0
  298. package/app/map/ActivityBar.tsx +81 -0
  299. package/app/map/BackButton.tsx +25 -0
  300. package/app/map/BoxActions.tsx +55 -0
  301. package/app/map/Breadcrumb.tsx +83 -0
  302. package/app/map/DetailPanel.tsx +572 -0
  303. package/app/map/HelpDialog.tsx +134 -0
  304. package/app/map/Legend.tsx +82 -0
  305. package/app/map/MapBox.tsx +88 -0
  306. package/app/map/MapCanvas.tsx +297 -0
  307. package/app/map/MapView.tsx +686 -0
  308. package/app/map/RepoMenu.tsx +97 -0
  309. package/app/map/RoutedEdge.tsx +39 -0
  310. package/app/map/SearchPalette.tsx +158 -0
  311. package/app/map/SourceViewer.tsx +144 -0
  312. package/app/map/StatusPill.tsx +93 -0
  313. package/app/map/TopBar.tsx +84 -0
  314. package/app/map/activity.ts +100 -0
  315. package/app/map/api.ts +125 -0
  316. package/app/map/apiError.ts +9 -0
  317. package/app/map/demoData.ts +35 -0
  318. package/app/map/embed.ts +120 -0
  319. package/app/map/emphasis.ts +78 -0
  320. package/app/map/flowNodes.ts +71 -0
  321. package/app/map/format.ts +66 -0
  322. package/app/map/history.ts +61 -0
  323. package/app/map/labels.ts +34 -0
  324. package/app/map/levelLayout.ts +199 -0
  325. package/app/map/links.ts +14 -0
  326. package/app/map/liveConnection.ts +37 -0
  327. package/app/map/mode.ts +7 -0
  328. package/app/map/narrow.ts +168 -0
  329. package/app/map/paths.ts +60 -0
  330. package/app/map/perf.ts +31 -0
  331. package/app/map/popover.ts +36 -0
  332. package/app/map/portals.ts +69 -0
  333. package/app/map/pulses.ts +18 -0
  334. package/app/map/routes.ts +105 -0
  335. package/app/map/searchHits.ts +19 -0
  336. package/app/map/shortcuts.ts +43 -0
  337. package/app/map/staticApi.ts +133 -0
  338. package/app/map/summaries.ts +38 -0
  339. package/app/map/syntax.ts +39 -0
  340. package/app/map/text.ts +82 -0
  341. package/app/map/useDismiss.ts +34 -0
  342. package/app/map/useEditorBridge.ts +71 -0
  343. package/app/map/useIndexStatus.ts +44 -0
  344. package/app/map/useLive.ts +41 -0
  345. package/app/map/useScrollLock.ts +28 -0
  346. package/app/map/useSize.ts +45 -0
  347. package/app/map/viewport.ts +68 -0
  348. package/app/page.tsx +14 -0
  349. package/bin/boot.mjs +38 -0
  350. package/bin/nebulosity.mjs +9 -0
  351. package/instrumentation.ts +17 -0
  352. package/lib/agent/tools.ts +311 -0
  353. package/lib/background/child.ts +63 -0
  354. package/lib/background/protocol.ts +20 -0
  355. package/lib/background/service.ts +495 -0
  356. package/lib/background/start.ts +77 -0
  357. package/lib/background/supervisor.ts +130 -0
  358. package/lib/chunks.ts +432 -0
  359. package/lib/config.ts +224 -0
  360. package/lib/contracts.ts +473 -0
  361. package/lib/db/batch.ts +43 -0
  362. package/lib/db/client.ts +134 -0
  363. package/lib/db/migrations/0000_lonely_steve_rogers.sql +29 -0
  364. package/lib/db/migrations/0001_tidy_karen_page.sql +31 -0
  365. package/lib/db/migrations/0002_pivot_v1_1.sql +48 -0
  366. package/lib/db/migrations/0003_v1_2_summaries.sql +40 -0
  367. package/lib/db/migrations/0004_v1_2_search_index.sql +44 -0
  368. package/lib/db/migrations/0005_v1_3_drop_chunk_vectors.sql +1 -0
  369. package/lib/db/migrations/0006_v1_3_deferred_code_search.sql +11 -0
  370. package/lib/db/migrations/0007_v1_6_entry_points.sql +9 -0
  371. package/lib/db/migrations/0008_v1_6_docs.sql +21 -0
  372. package/lib/db/migrations/0009_v1_7_calls.sql +43 -0
  373. package/lib/db/migrations/0010_e4_declares.sql +1 -0
  374. package/lib/db/migrations/meta/0000_snapshot.json +213 -0
  375. package/lib/db/migrations/meta/0001_snapshot.json +422 -0
  376. package/lib/db/migrations/meta/0002_snapshot.json +747 -0
  377. package/lib/db/migrations/meta/0003_snapshot.json +1026 -0
  378. package/lib/db/migrations/meta/0004_snapshot.json +1026 -0
  379. package/lib/db/migrations/meta/0005_snapshot.json +981 -0
  380. package/lib/db/migrations/meta/0006_snapshot.json +981 -0
  381. package/lib/db/migrations/meta/0007_snapshot.json +1042 -0
  382. package/lib/db/migrations/meta/0008_snapshot.json +1185 -0
  383. package/lib/db/migrations/meta/0009_snapshot.json +1483 -0
  384. package/lib/db/migrations/meta/0010_snapshot.json +1491 -0
  385. package/lib/db/migrations/meta/_journal.json +83 -0
  386. package/lib/db/schema.ts +343 -0
  387. package/lib/db/store/calls.ts +73 -0
  388. package/lib/db/store/docs.ts +32 -0
  389. package/lib/db/store/entries.ts +34 -0
  390. package/lib/db/store/lock.ts +122 -0
  391. package/lib/db/store/map.ts +140 -0
  392. package/lib/db/store/search.ts +77 -0
  393. package/lib/db/store/stages.ts +46 -0
  394. package/lib/db/store/structure.ts +327 -0
  395. package/lib/db/store/summaries.ts +357 -0
  396. package/lib/db/store/summary-vectors.ts +73 -0
  397. package/lib/db/store/version.ts +66 -0
  398. package/lib/demo/export.ts +214 -0
  399. package/lib/demo/privacy.ts +111 -0
  400. package/lib/embed.ts +151 -0
  401. package/lib/engine/architecture.ts +378 -0
  402. package/lib/engine/calls.ts +424 -0
  403. package/lib/engine/describe.ts +89 -0
  404. package/lib/engine/docs.ts +94 -0
  405. package/lib/engine/errors.ts +97 -0
  406. package/lib/engine/freshness.ts +21 -0
  407. package/lib/engine/git-remote.ts +91 -0
  408. package/lib/engine/graph.ts +140 -0
  409. package/lib/engine/impact.ts +98 -0
  410. package/lib/engine/imports-page.ts +129 -0
  411. package/lib/engine/level.ts +130 -0
  412. package/lib/engine/overview.ts +77 -0
  413. package/lib/engine/pages.ts +196 -0
  414. package/lib/engine/rank.ts +228 -0
  415. package/lib/engine/reach.ts +240 -0
  416. package/lib/engine/repos.ts +43 -0
  417. package/lib/engine/search.ts +195 -0
  418. package/lib/engine/source.ts +95 -0
  419. package/lib/engine/staticSearch.ts +150 -0
  420. package/lib/engine/status.ts +66 -0
  421. package/lib/hints.ts +18 -0
  422. package/lib/home.ts +42 -0
  423. package/lib/index/aggregate.ts +112 -0
  424. package/lib/index/discover.ts +289 -0
  425. package/lib/index/docs.ts +215 -0
  426. package/lib/index/embed.ts +62 -0
  427. package/lib/index/identifiers.ts +14 -0
  428. package/lib/index/languages/adapter.ts +101 -0
  429. package/lib/index/languages/chunking.ts +67 -0
  430. package/lib/index/languages/csharp/arity.ts +34 -0
  431. package/lib/index/languages/csharp/index.ts +17 -0
  432. package/lib/index/languages/csharp/parse.ts +563 -0
  433. package/lib/index/languages/csharp/resolve.ts +344 -0
  434. package/lib/index/languages/csharp/rules.ts +71 -0
  435. package/lib/index/languages/csharp/scan.ts +86 -0
  436. package/lib/index/languages/go/index.ts +13 -0
  437. package/lib/index/languages/go/parse.ts +497 -0
  438. package/lib/index/languages/go/resolve.ts +260 -0
  439. package/lib/index/languages/go/rules.ts +39 -0
  440. package/lib/index/languages/grammars/NOTICE.md +200 -0
  441. package/lib/index/languages/grammars/grammars.json +37 -0
  442. package/lib/index/languages/grammars/tree-sitter-c_sharp.wasm.gz +0 -0
  443. package/lib/index/languages/grammars/tree-sitter-go.wasm.gz +0 -0
  444. package/lib/index/languages/grammars/tree-sitter-java.wasm.gz +0 -0
  445. package/lib/index/languages/grammars/tree-sitter-python.wasm.gz +0 -0
  446. package/lib/index/languages/grammars/tree-sitter-rust.wasm.gz +0 -0
  447. package/lib/index/languages/java/index.ts +13 -0
  448. package/lib/index/languages/java/parse.ts +478 -0
  449. package/lib/index/languages/java/resolve.ts +175 -0
  450. package/lib/index/languages/java/rules.ts +53 -0
  451. package/lib/index/languages/python/index.ts +17 -0
  452. package/lib/index/languages/python/parse.ts +386 -0
  453. package/lib/index/languages/python/resolve.ts +309 -0
  454. package/lib/index/languages/python/rules.ts +51 -0
  455. package/lib/index/languages/python/stdlib.ts +80 -0
  456. package/lib/index/languages/registry.ts +33 -0
  457. package/lib/index/languages/resolution.ts +70 -0
  458. package/lib/index/languages/rules.ts +51 -0
  459. package/lib/index/languages/rust/cargo.ts +163 -0
  460. package/lib/index/languages/rust/index.ts +13 -0
  461. package/lib/index/languages/rust/parse.ts +348 -0
  462. package/lib/index/languages/rust/resolve.ts +331 -0
  463. package/lib/index/languages/rust/rules.ts +47 -0
  464. package/lib/index/languages/rust/scan.ts +36 -0
  465. package/lib/index/languages/rust/syntax.ts +146 -0
  466. package/lib/index/languages/treesitter.ts +157 -0
  467. package/lib/index/languages/typescript-adapter.ts +55 -0
  468. package/lib/index/languages/typescript-calls.ts +663 -0
  469. package/lib/index/languages/typescript-rules.ts +24 -0
  470. package/lib/index/languages/typescript.ts +238 -0
  471. package/lib/index/paths.ts +54 -0
  472. package/lib/index/pipeline.ts +404 -0
  473. package/lib/index/resolve.ts +304 -0
  474. package/lib/index/summarize/budget.ts +105 -0
  475. package/lib/index/summarize/claude.ts +366 -0
  476. package/lib/index/summarize/compare.ts +195 -0
  477. package/lib/index/summarize/concurrency.ts +19 -0
  478. package/lib/index/summarize/env.ts +4 -0
  479. package/lib/index/summarize/estimate.ts +14 -0
  480. package/lib/index/summarize/models.ts +185 -0
  481. package/lib/index/summarize/plan.ts +252 -0
  482. package/lib/index/summarize/preview.ts +442 -0
  483. package/lib/index/summarize/pricing.ts +52 -0
  484. package/lib/index/summarize/prompts.ts +253 -0
  485. package/lib/index/summarize/run.ts +948 -0
  486. package/lib/index/summarize/snapshot.ts +28 -0
  487. package/lib/index/summarize/status.ts +106 -0
  488. package/lib/index/summarize/types.ts +77 -0
  489. package/lib/index/update.ts +311 -0
  490. package/lib/index/watch.ts +156 -0
  491. package/lib/index/workspace.ts +337 -0
  492. package/lib/live/client.ts +24 -0
  493. package/lib/live/event.ts +40 -0
  494. package/lib/live/hub.ts +37 -0
  495. package/lib/live/local.ts +42 -0
  496. package/lib/live/sse.ts +13 -0
  497. package/lib/port.ts +15 -0
  498. package/lib/tools/answers.ts +530 -0
  499. package/lib/tools/context.ts +17 -0
  500. package/lib/tools/registry.ts +230 -0
  501. package/lib/tools/repo.ts +101 -0
  502. package/lib/tools/text.ts +78 -0
  503. package/mcp/launch.mjs +11 -0
  504. package/mcp/server.ts +106 -0
  505. package/next.config.ts +31 -0
  506. package/package.json +148 -6
  507. package/postcss.config.mjs +7 -0
  508. package/proxy.ts +19 -0
  509. package/public/file.svg +1 -0
  510. package/public/globe.svg +1 -0
  511. package/public/next.svg +1 -0
  512. package/public/vercel.svg +1 -0
  513. package/public/window.svg +1 -0
  514. package/scripts/background.ts +43 -0
  515. package/scripts/cli.ts +446 -0
  516. package/scripts/commands/index-command.ts +112 -0
  517. package/scripts/commands/summarize-command.ts +276 -0
  518. package/scripts/env.ts +19 -0
  519. package/scripts/index.ts +6 -0
  520. package/scripts/mcp-smoke.mjs +78 -0
  521. package/scripts/summarize.ts +2 -0
  522. package/tsconfig.json +34 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The Map's place in the address bar: `/?path=src/vs&page=2`. Every move to another folder or page goes into the
3
+ * browser's history, so Back returns to the previous folder (owner review), and a reload or a pasted link opens the
4
+ * same place. The page is counted from 1 for people and from 0 inside the app; the slashes of a path stay readable.
5
+ */
6
+ export type MapPlace = { path: string; page: number };
7
+
8
+ const encodePath = (folderPath: string): string => encodeURIComponent(folderPath).replaceAll("%2F", "/");
9
+
10
+ /**
11
+ * The address for a place. Inside the editor's panel (`embedded`) it ends with `embed=vscode`, so a reload, a restarted
12
+ * server or Next's own reload stays embedded and keeps the bridge (V1.8 plan P8); the browser Map never has the flag.
13
+ */
14
+ export function mapUrl(folderPath: string, page: number, embedded = false): string {
15
+ const params = [
16
+ ...(folderPath === "" ? [] : [`path=${encodePath(folderPath)}`]),
17
+ ...(page > 0 ? [`page=${page + 1}`] : []),
18
+ ...(embedded ? ["embed=vscode"] : []),
19
+ ];
20
+ return params.length === 0 ? "/" : `/?${params.join("&")}`;
21
+ }
22
+
23
+ /** The place a query string names; anything it can't read is the first page of the root. */
24
+ export function placeFromSearch(search: string): MapPlace {
25
+ const params = new URLSearchParams(search);
26
+ const page = params.get("page") ?? "";
27
+ return { path: params.get("path") ?? "", page: /^\d+$/.test(page) && Number(page) >= 1 ? Number(page) - 1 : 0 };
28
+ }
29
+
30
+ /**
31
+ * What the address bar needs when the Map moves to `place`: nothing when it already says so; a rewrite when it names the
32
+ * same place another way (`?path=src%2Fvs`, `?page=1`, `?path=`), so Back doesn't stop at a step that goes nowhere; and
33
+ * a new history entry for a move to another place. `address` is the path and query string, `/?path=src`. `embedded`
34
+ * (see `mapUrl`) makes an address without `embed=vscode` one to rewrite, so the flag is not lost.
35
+ */
36
+ export function historyStep(address: string, place: MapPlace, embedded = false): "none" | "replace" | "push" {
37
+ if (address === mapUrl(place.path, place.page, embedded)) return "none";
38
+ const query = address.includes("?") ? address.slice(address.indexOf("?")) : "";
39
+ const named = placeFromSearch(query);
40
+ return named.path === place.path && named.page === place.page ? "replace" : "push";
41
+ }
42
+
43
+ /**
44
+ * A move the editor asked for with `follow` (the active editor changed) changes the Map's place but must not fill Back
45
+ * with a step per file the person clicked through: a push becomes a rewrite of the current entry. A `show` (a deliberate
46
+ * jump) keeps `mode` "push". Everything else is as `historyStep` said.
47
+ */
48
+ export function followStep(step: "none" | "replace" | "push", mode: "push" | "replace"): "none" | "replace" | "push" {
49
+ return step === "push" && mode === "replace" ? "replace" : step;
50
+ }
51
+
52
+ /**
53
+ * How many of the Map's own steps lie behind a history entry: the `mapDepth` the Map writes into `history.state` with
54
+ * each push (and keeps with each rewrite). VS Code's panel has no browser Back, so the Map's own Back control is enabled
55
+ * only above 0: going back past the Map's first entry would take the webview off the Map (owner review, 2026-10-25).
56
+ * An entry the Map didn't write, a fresh load's or another page's, is 0, and so is anything that isn't a whole number.
57
+ */
58
+ export function historyDepth(state: unknown): number {
59
+ const depth = typeof state === "object" && state !== null ? (state as { mapDepth?: unknown }).mapDepth : undefined;
60
+ return typeof depth === "number" && Number.isInteger(depth) && depth > 0 ? depth : 0;
61
+ }
@@ -0,0 +1,34 @@
1
+ import type { DescribeResponse, MapNode } from "@/lib/contracts";
2
+ import { formatCount } from "./format";
3
+
4
+ /**
5
+ * Words for what the Map draws. The owner's three-task test (2026-10-13) found a newcomer reading an arrow backwards
6
+ * ("base depends on editor"), so a line says what it means in a sentence when it's hovered.
7
+ */
8
+
9
+ /** What a box is called inside a sentence: its name, or for a portal the boxes behind it. */
10
+ function subject(box: MapNode): string {
11
+ if (box.kind !== "portal") return box.name;
12
+ return box.pages ? `boxes on ${box.pages.length} other pages` : `boxes on ${box.name}`;
13
+ }
14
+
15
+ const capitalized = (text: string): string => text.charAt(0).toUpperCase() + text.slice(1);
16
+
17
+ /** A line's hover label: "editor uses base · 12 imports" (the arrow runs from the user to the used). */
18
+ export function lineLabel(source: MapNode, target: MapNode, count: number): string {
19
+ const imports = `${formatCount(count)} import${count === 1 ? "" : "s"}`;
20
+ const verb = source.kind === "portal" ? "use" : "uses";
21
+ const who = source.kind === "portal" ? capitalized(subject(source)) : subject(source);
22
+ return `${who} ${verb} ${subject(target)} · ${imports}`;
23
+ }
24
+
25
+ /** A box's hover title: its path, and its one-line summary when there is one. */
26
+ export function boxTitle(box: Pick<MapNode, "path" | "name" | "tagline">): string {
27
+ const where = box.path || box.name;
28
+ return box.tagline ? `${where}: ${box.tagline}` : where;
29
+ }
30
+
31
+ /** The detail panel's words for a file with no declarations: a file over the size cap had them left unread (ADR-053). */
32
+ export function noDeclarationsText(file: Pick<DescribeResponse, "importsOnly">): string {
33
+ return file.importsOnly ? "Not read (over the 1 MB size cap; imports only)." : "None found.";
34
+ }
@@ -0,0 +1,199 @@
1
+ import dagre from "@dagrejs/dagre";
2
+ import type { MapEdge, MapNode } from "@/lib/contracts";
3
+
4
+ /**
5
+ * Every box is drawn the same size, so the layout needs no measuring (VISUAL_REFRESH.md §4). 56 tall, not V1.3.1's
6
+ * first 64, and layers 64 apart, not 72: measured on VS Code's busiest levels, the tighter layout fits them 10–30%
7
+ * larger (src/vs/platform's names from 8.8 to 11.6 px on a 1907 px screen), while GAP stays 40 so the routed lines
8
+ * keep their channels.
9
+ */
10
+ export const BOX_WIDTH = 232;
11
+ export const BOX_HEIGHT = 56;
12
+ export const GAP = 40;
13
+ /** Room between layers: enough for a line's arrowhead to read before it meets the box below. */
14
+ export const RANK_GAP = 64;
15
+
16
+ export type Position = { x: number; y: number };
17
+
18
+ /** A layer wider than this wraps into rows: 6 boxes span about 1,600 px, a laptop screen at a readable zoom. */
19
+ export const MAX_PER_ROW = 6;
20
+ /** On a wide canvas a layer wraps at 8 instead (about 2,100 px): fewer rows, so a dense level fits larger. */
21
+ export const WIDE_PER_ROW = 8;
22
+ /** A canvas at least this wide counts as wide. Below it, 8 a row would make levels fit smaller, not larger (measured). */
23
+ export const WIDE_CANVAS = 1700;
24
+
25
+ /** How many boxes a layer holds per row on a canvas this wide. */
26
+ export function perRowFor(canvasWidth: number): number {
27
+ return canvasWidth >= WIDE_CANVAS ? WIDE_PER_ROW : MAX_PER_ROW;
28
+ }
29
+
30
+ /**
31
+ * Dagre's layers, redrawn as centred tiers (VISUAL_REFRESH.md §4: the level fits the viewport). Dagre decides which
32
+ * layer each box is in and their order within it (the order that crosses fewest lines); its x positions are dropped.
33
+ * Left to them, 49 files that all import one barrel make one layer 13,000 px wide, and fitting that to a screen
34
+ * shrinks every box to a sliver. So each layer is centred on x = 0, and one wider than MAX_PER_ROW wraps into rows,
35
+ * balanced so the last isn't left with one box (13 → 5, 5, 3; at 8 a row, 7 and 6). The tiers stack from y = 0,
36
+ * RANK_GAP apart.
37
+ */
38
+ function placeTiers(centres: readonly { path: string; x: number; y: number }[], perRowCap: number): Map<string, Position> {
39
+ const layers = new Map<number, { path: string; x: number }[]>();
40
+ for (const { path, x, y } of centres) layers.set(y, [...(layers.get(y) ?? []), { path, x }]);
41
+
42
+ const placed = new Map<string, Position>();
43
+ let top = 0;
44
+ for (const y of [...layers.keys()].sort((a, b) => a - b)){
45
+ const layer = layers.get(y)!.sort((a, b) => a.x - b.x || (a.path < b.path ? -1 : 1));
46
+ const rowCount = Math.ceil(layer.length / perRowCap);
47
+ const perRow = Math.ceil(layer.length / rowCount);
48
+ for (let row = 0; row < rowCount; row++){
49
+ const boxes = layer.slice(row * perRow, (row + 1) * perRow);
50
+ const left = -(boxes.length * BOX_WIDTH + (boxes.length - 1) * GAP) / 2;
51
+ boxes.forEach((box, i) => placed.set(box.path, { x: left + i * (BOX_WIDTH + GAP), y: top + row * (BOX_HEIGHT + GAP) }));
52
+ }
53
+ top += rowCount * (BOX_HEIGHT + GAP) - GAP + RANK_GAP;
54
+ }
55
+ return placed;
56
+ }
57
+
58
+ /**
59
+ * At most this many lines shape a level's layout: the heaviest. Dagre's crossing reduction grows with the lines, and
60
+ * VS Code's src/vs/platform level has 390 of them: about 0.6 s to lay out, against 30 ms with the heaviest 100. Every
61
+ * line is still drawn; the lighter ones just don't move the boxes (S3, PIVOT_SPEC.md §2).
62
+ */
63
+ export const LAYOUT_MAX_LINES = 100;
64
+
65
+ /** The fit's padding (MapCanvas's FIT): React Flow fits a level at most this much smaller than the canvas, each way. */
66
+ export const FIT_PADDING = 0.15;
67
+ /**
68
+ * Room for the divider over the loose boxes ("Not linked to anything on this page", owner's design, 2026-10-13): a grid
69
+ * below the tiers starts this far under them, three gaps instead of two, with the line halfway; a column beside them
70
+ * starts this far down, the line in the band above it.
71
+ */
72
+ export const BELOW_GAP = 3 * GAP;
73
+ export const DIVIDER_BAND = 64;
74
+
75
+ /** The canvas a level is judged against when choosing where its loose boxes go: a 1440 × 900 screen less the bars. */
76
+ const NOMINAL_CANVAS = { width: 1440, height: 780 };
77
+
78
+ /** The zoom React Flow's fit would pick for a level this big on the nominal canvas: never past 100% (MapCanvas's maxZoom). */
79
+ function fitZoom(width: number, height: number): number {
80
+ return Math.min(1, NOMINAL_CANVAS.width / (width * (1 + FIT_PADDING)), NOMINAL_CANVAS.height / (height * (1 + FIT_PADDING)));
81
+ }
82
+
83
+ /** A grid of `count` boxes, `columns` wide, from (left, top), in order: the level's order for its loose boxes. */
84
+ function grid(count: number, columns: number, left: number, top: number): Position[] {
85
+ return Array.from({ length: count }, (_, i) => ({
86
+ x: left + (i % columns) * (BOX_WIDTH + GAP),
87
+ y: top + Math.floor(i / columns) * (BOX_HEIGHT + GAP),
88
+ }));
89
+ }
90
+
91
+ /** How wide (or tall) `k` boxes side by side (or stacked) are, with the gaps between them. */
92
+ const span = (k: number, size: number): number => k * (size + GAP) - GAP;
93
+
94
+ /** The paths joined by a line to another box at this level; a line from a box to itself joins nothing. */
95
+ function linkedPaths(edges: readonly MapEdge[]): Set<string> {
96
+ return new Set(edges.filter((e) => e.source !== e.target).flatMap((e) => [e.source, e.target]));
97
+ }
98
+
99
+ /**
100
+ * Where each box of a level goes (ADR-011, PIVOT_SPEC.md §7): dagre's layers, importers above what they import, from
101
+ * the level's `maxLines` heaviest lines, drawn as centred tiers that wrap past `perRow` (placeTiers). A box with no
102
+ * line to any other at this level adds nothing to the layering, so those go in a grid, in the level's order, on the
103
+ * side that lets the whole level fit larger (VISUAL_REFRESH.md §4: readable, fitting the viewport):
104
+ * - beside the tiers, as tall as them: right for a tall chain on a wide screen (VS Code's root: 91% against 68%);
105
+ * - or below them, square-ish and centred: right for a short stack with many loose boxes (2 tiers and 20 loose: 95%
106
+ * against 42%). A tie stays beside.
107
+ * Either way they leave room for the divider that says they're not linked (looseDivider, below).
108
+ * A level with no lines at all is one square-ish grid. Pure and deterministic: the same level always draws the same
109
+ * picture. Positions are top-left corners, as React Flow places nodes.
110
+ */
111
+ export function layoutLevel(
112
+ nodes: readonly MapNode[],
113
+ edges: readonly MapEdge[],
114
+ maxLines: number = LAYOUT_MAX_LINES,
115
+ perRow: number = MAX_PER_ROW,
116
+ ): Map<string, Position> {
117
+ const positions = new Map<string, Position>();
118
+ const lines = edges.filter((e) => e.source !== e.target);
119
+ // a box joined by any line stays in the layers, even when only lighter lines join it
120
+ const linked = linkedPaths(edges);
121
+ const connected = nodes.filter((n) => linked.has(n.path));
122
+ const loose = nodes.filter((n) => !linked.has(n.path));
123
+ // heaviest first; ties by source, then target, so the same level always keeps the same lines
124
+ const shaping = [...lines]
125
+ .sort((a, b) => b.count - a.count || (a.source < b.source ? -1 : a.source > b.source ? 1 : 0) || (a.target < b.target ? -1 : a.target > b.target ? 1 : 0))
126
+ .slice(0, maxLines);
127
+ const squareColumns = Math.max(1, Math.ceil(Math.sqrt(loose.length)));
128
+
129
+ if (connected.length === 0){
130
+ grid(loose.length, squareColumns, 0, 0).forEach((at, i) => positions.set(loose[i].path, at));
131
+ return positions;
132
+ }
133
+
134
+ const graph = new dagre.graphlib.Graph();
135
+ graph.setGraph({ rankdir: "TB", nodesep: GAP, ranksep: RANK_GAP, marginx: 0, marginy: 0 });
136
+ graph.setDefaultEdgeLabel(() => ({}));
137
+ for (const node of connected) graph.setNode(node.path, { width: BOX_WIDTH, height: BOX_HEIGHT });
138
+ // a heavier line pulls its boxes closer; the square root keeps 20,000 imports from flattening everything else
139
+ for (const edge of shaping) graph.setEdge(edge.source, edge.target, { weight: Math.max(1, Math.round(Math.sqrt(edge.count))) });
140
+ dagre.layout(graph);
141
+
142
+ // the tiers are centred on x = 0 and start at y = 0
143
+ let bottom = 0;
144
+ let right = 0;
145
+ for (const [path, at] of placeTiers(connected.map((node) => ({ path: node.path, ...(graph.node(node.path) as { x: number; y: number }) })), perRow)){
146
+ positions.set(path, at);
147
+ bottom = Math.max(bottom, at.y + BOX_HEIGHT);
148
+ right = Math.max(right, at.x + BOX_WIDTH);
149
+ }
150
+ if (loose.length === 0) return positions;
151
+
152
+ // beside: as many rows as the tiers are tall, starting two gaps right of them and a band down, under the divider
153
+ const besideRows = Math.max(1, Math.floor((bottom + GAP) / (BOX_HEIGHT + GAP)));
154
+ const besideColumns = Math.ceil(loose.length / besideRows);
155
+ const besideWidth = right + 2 * GAP + span(besideColumns, BOX_WIDTH) + right; // the tiers span -right … right
156
+ const besideHeight = Math.max(bottom, DIVIDER_BAND + span(Math.ceil(loose.length / besideColumns), BOX_HEIGHT));
157
+ // below: square-ish, centred, starting BELOW_GAP under the tiers, the divider halfway
158
+ const belowColumns = Math.min(squareColumns, loose.length);
159
+ const belowWidth = Math.max(2 * right, span(belowColumns, BOX_WIDTH));
160
+ const belowHeight = bottom + BELOW_GAP + span(Math.ceil(loose.length / belowColumns), BOX_HEIGHT);
161
+
162
+ const cells = fitZoom(belowWidth, belowHeight) > fitZoom(besideWidth, besideHeight)
163
+ ? grid(loose.length, belowColumns, -span(belowColumns, BOX_WIDTH) / 2, bottom + BELOW_GAP)
164
+ : grid(loose.length, besideColumns, right + 2 * GAP, DIVIDER_BAND);
165
+ cells.forEach((at, i) => positions.set(loose[i].path, at));
166
+ return positions;
167
+ }
168
+
169
+ /** Where the divider over a level's loose boxes runs: its left end, its centre line, its length. */
170
+ export type Divider = { x: number; y: number; width: number };
171
+
172
+ /**
173
+ * The line with "Not linked to anything on this page" in its middle, between the boxes joined by lines and the loose
174
+ * ones (owner's design, 2026-10-13: without it the loose grid read as randomly placed cards). Below the tiers it runs
175
+ * the whole level's width, halfway down BELOW_GAP; over a column beside them it's as wide as the column, centred in
176
+ * DIVIDER_BAND. No divider when every box is linked or none is: there's nothing to set apart.
177
+ */
178
+ export function looseDivider(nodes: readonly MapNode[], edges: readonly MapEdge[], positions: ReadonlyMap<string, Position>): Divider | null {
179
+ const linked = linkedPaths(edges);
180
+ const boundsOf = (paths: readonly string[]) => {
181
+ const at = paths.map((p) => positions.get(p)).filter((q): q is Position => q !== undefined);
182
+ return {
183
+ left: Math.min(...at.map((q) => q.x)),
184
+ right: Math.max(...at.map((q) => q.x + BOX_WIDTH)),
185
+ top: Math.min(...at.map((q) => q.y)),
186
+ bottom: Math.max(...at.map((q) => q.y + BOX_HEIGHT)),
187
+ };
188
+ };
189
+ const joined = nodes.filter((n) => linked.has(n.path)).map((n) => n.path);
190
+ const loose = nodes.filter((n) => !linked.has(n.path)).map((n) => n.path);
191
+ if (joined.length === 0 || loose.length === 0) return null;
192
+
193
+ const tiers = boundsOf(joined);
194
+ const grid = boundsOf(loose);
195
+ // beside: the grid starts right of the tiers (layoutLevel puts it two gaps past them); below, it's centred under them
196
+ if (grid.left >= tiers.right) return { x: grid.left, y: grid.top - DIVIDER_BAND / 2, width: grid.right - grid.left };
197
+ const left = Math.min(tiers.left, grid.left);
198
+ return { x: left, y: (tiers.bottom + grid.top) / 2, width: Math.max(tiers.right, grid.right) - left };
199
+ }
@@ -0,0 +1,14 @@
1
+ import type { GitHubSource } from "./api";
2
+
3
+ export type LinkTarget = { repo: string; path: string };
4
+
5
+ /** A file in the user's editor, from the app (PIVOT_SPEC.md §7): needs the repo's absolute root, so never in the demo. */
6
+ export function editorLink(target: LinkTarget, line?: number): string {
7
+ return `vscode://file/${target.repo}/${target.path}${line ? `:${line}` : ""}`;
8
+ }
9
+
10
+ /** A file on GitHub at the commit the demo's index was built from, at a declaration's lines (ADR-049). */
11
+ export function githubLink(source: GitHubSource, filePath: string, lines?: { start: number; end: number }): string {
12
+ const anchor = lines === undefined ? "" : lines.start === lines.end ? `#L${lines.start}` : `#L${lines.start}-L${lines.end}`;
13
+ return `https://github.com/${source.owner}/${source.name}/blob/${source.commit}/${filePath.split("/").map(encodeURIComponent).join("/")}${anchor}`;
14
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Keeps one live stream open while the page is on screen (PIVOT_SPEC.md §8).
3
+ *
4
+ * Why this exists: when you leave a page, Chrome may keep it frozen in its back/forward cache so Back is instant, and a
5
+ * frozen page's EventSource stays connected. Over plain HTTP/1.1 a browser allows only 6 connections to one server, so
6
+ * after a handful of full page loads in one tab the frozen pages' streams held them all, and the next page's level,
7
+ * status and repo requests waited forever (measured 2026-10-13: the 7th load stalled with the cache on, never with it
8
+ * off). So the stream closes on `pagehide` and a new one opens on `pageshow` when the page comes back from the cache.
9
+ */
10
+
11
+ /** What a stream must offer: a way to close it. EventSource has it; tests pass a stand-in. */
12
+ export type Closable = { close(): void };
13
+
14
+ /** Where pagehide and pageshow fire: the window, or a stand-in EventTarget in tests. */
15
+ type PageEvents = Pick<EventTarget, "addEventListener" | "removeEventListener">;
16
+
17
+ /** Opens a stream now and keeps one open while the page shows; returns a function that closes it and stops listening. */
18
+ export function keepLiveStream(open: () => Closable, page: PageEvents): () => void {
19
+ let stream: Closable | null = open();
20
+
21
+ const onHide = (): void => {
22
+ stream?.close();
23
+ stream = null;
24
+ };
25
+ // pageshow fires on every load too; only a page restored from the cache (persisted) needs its stream back
26
+ const onShow = (event: Event): void => {
27
+ if ((event as PageTransitionEvent).persisted && stream === null) stream = open();
28
+ };
29
+
30
+ page.addEventListener("pagehide", onHide);
31
+ page.addEventListener("pageshow", onShow);
32
+ return () => {
33
+ page.removeEventListener("pagehide", onHide);
34
+ page.removeEventListener("pageshow", onShow);
35
+ onHide();
36
+ };
37
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * True in the static demo's build (ADR-049). `npm run build-demo` builds with NEBULOSITY_DEMO=1, which next.config.ts
3
+ * turns into NEXT_PUBLIC_NEBULOSITY_DEMO ("1" in the demo, "0" in the app) for the browser; Next inlines it at build
4
+ * time. For the components. The transport's own switch is written inline in api.ts, so the bundler can drop the demo's
5
+ * code from the app.
6
+ */
7
+ export const DEMO = process.env.NEXT_PUBLIC_NEBULOSITY_DEMO === "1";
@@ -0,0 +1,168 @@
1
+ /**
2
+ * The Map at any window size (VISUAL_REFRESH.md, "Narrow windows (2026-10-24)"). At 1100 × 700 and up nothing here
3
+ * changes how the Map looks. Narrower, each surface gives up secondary text first; below the floor the Map stops
4
+ * shrinking and the page scrolls instead, with the theme's thin scrollbars. Pure, so each rule is tested on its own.
5
+ */
6
+
7
+ import { TOP_INSET } from "./viewport";
8
+
9
+ /**
10
+ * The floor: the smallest the Map lays itself out at. Narrower or shorter, the page scrolls sideways or down.
11
+ * 360 px wide holds the top bar's essentials (logo, repo, search, help, status dot) and a canvas as wide as the floor
12
+ * beside nothing. 320 px tall leaves a 200 px canvas under the bars (48 + 40 + 32 px), which stacks a paged level's
13
+ * pill (16 + 44 px from the top) over the zoom controls (104 px, 15 px from the bottom) with about 20 px between them.
14
+ */
15
+ export const MIN_WIDTH = 360;
16
+ export const MIN_HEIGHT = 320;
17
+
18
+ /** The details panel's width, when there's room beside it (VISUAL_REFRESH.md §5). */
19
+ export const PANEL_WIDTH = 400;
20
+ /**
21
+ * The least canvas worth keeping beside the panel: the floor's own width. With less left, the panel covers the whole
22
+ * canvas (its × closes it), since a sliver of Map beside it can't be read or used.
23
+ */
24
+ export const MIN_CANVAS_BESIDE_PANEL = MIN_WIDTH;
25
+
26
+ /**
27
+ * Whether the panel covers the whole canvas in a Map area `mainWidth` px wide; beside it, it's PANEL_WIDTH wide.
28
+ * `null`, before the area is measured, is the normal case: beside the canvas.
29
+ */
30
+ export function panelLayout(mainWidth: number | null): { covers: boolean } {
31
+ return { covers: mainWidth !== null && mainWidth - PANEL_WIDTH < MIN_CANVAS_BESIDE_PANEL };
32
+ }
33
+
34
+ /**
35
+ * Where a selected box's actions go while the panel would cover the canvas (owner review, 2026-10-25). A click then only
36
+ * selects, so a double click still reaches the box and the Map stays in sight, and the box shows Open and Details beside
37
+ * it. They go under a box whose middle is in the canvas's top half, clear of the page pill's band, and over one lower
38
+ * down, clear of the legend and the zoom controls. `boxTop` and `boxHeight` are the box's on screen, in canvas px.
39
+ */
40
+ export function actionsSide(boxTop: number, boxHeight: number, canvasHeight: number): "top" | "bottom" {
41
+ return boxTop + boxHeight / 2 < canvasHeight / 2 ? "bottom" : "top";
42
+ }
43
+
44
+ /** The gap between a box and its actions, and the least room they keep from the canvas's sides. */
45
+ const ACTIONS_GAP = 8;
46
+
47
+ /**
48
+ * Where the selected box's actions go on the canvas (their top-left corner, canvas px), given the box and the actions'
49
+ * size on screen: on actionsSide's side, 8 px off the box, centred on it, but kept 8 px inside the canvas's sides, so a
50
+ * box hanging off an edge (a level opened from its top at 75%) never takes them past it. Each axis is its own rule.
51
+ */
52
+ export function actionsPlace(
53
+ box: { left: number; top: number; width: number; height: number },
54
+ actions: { width: number; height: number },
55
+ canvasWidth: number,
56
+ canvasHeight: number,
57
+ ): { left: number; top: number } {
58
+ const top = actionsSide(box.top, box.height, canvasHeight) === "bottom" ? box.top + box.height + ACTIONS_GAP : box.top - ACTIONS_GAP - actions.height;
59
+ const centred = box.left + box.width / 2 - actions.width / 2;
60
+ const left = Math.min(Math.max(centred, ACTIONS_GAP), canvasWidth - ACTIONS_GAP - actions.width);
61
+ return { left, top };
62
+ }
63
+
64
+ /**
65
+ * How much the page pill says, by the canvas width it sits in (`null`: not measured yet, the normal case):
66
+ * "Page 2 of 5 · 44 of 182 folders and files", "Page 2 of 5", or "2 / 5". The whole note is about 350 px, 370 at
67
+ * most with four-digit counts; with its 16 px inset on the right and as much again on its left that's about 400 px,
68
+ * and 440 leaves room for a wider font.
69
+ */
70
+ export type PillForm = "full" | "page" | "numbers";
71
+ const FULL_PILL_ROOM = 440;
72
+ const PAGE_PILL_ROOM = 240;
73
+
74
+ export function pillForm(room: number | null): PillForm {
75
+ if (room === null || room >= FULL_PILL_ROOM) return "full";
76
+ return room >= PAGE_PILL_ROOM ? "page" : "numbers";
77
+ }
78
+
79
+ /**
80
+ * The first breadcrumb step to show so the trail fits in `available` px: the steps before it fold into one "…" step
81
+ * (`ellipsis` px), so the trail truncates from its start and the current folder stays. `widths` are the steps' natural
82
+ * widths, `gap` the space between steps. The last step always shows, and truncates itself if it alone is too wide.
83
+ */
84
+ export function trailStart(widths: readonly number[], available: number, ellipsis: number, gap: number): number {
85
+ const last = widths.length - 1;
86
+ if (last <= 0) return 0;
87
+ let needed = widths.reduce((sum, w) => sum + w, 0) + gap * last;
88
+ if (needed <= available) return 0;
89
+ // drop steps from the start, one at a time; the "…" stands in for all of them
90
+ needed += ellipsis + gap;
91
+ for (let start = 1; start < last; start++){
92
+ needed -= widths[start - 1] + gap;
93
+ if (needed <= available) return start;
94
+ }
95
+ return last;
96
+ }
97
+
98
+ /** The space a popover keeps from the Map's edges. */
99
+ const POPOVER_GUTTER = 16;
100
+
101
+ /**
102
+ * A popover (the repo list, the status menu) placed to stay inside the Map: aligned with its anchor's start or end
103
+ * edge as at normal sizes, moved in only as far as it takes to keep 16 px from the Map's edges, and narrowed only when
104
+ * the Map is narrower than the popover and both margins. `shift` is how far it moved from that alignment (px, + is
105
+ * right): a start-aligned popover's `left`, an end-aligned one's `right` negated.
106
+ */
107
+ export function fitPopover(
108
+ anchor: { left: number; right: number },
109
+ container: { left: number; right: number },
110
+ width: number,
111
+ align: "start" | "end",
112
+ ): { shift: number; width: number } {
113
+ const fitted = Math.min(width, container.right - container.left - 2 * POPOVER_GUTTER);
114
+ const natural = align === "start" ? anchor.left : anchor.right - fitted;
115
+ const left = Math.min(Math.max(natural, container.left + POPOVER_GUTTER), container.right - POPOVER_GUTTER - fitted);
116
+ return { shift: left - natural, width: fitted };
117
+ }
118
+
119
+ /*
120
+ * The Map's error line (a failed jump, a level that wouldn't load), over the canvas beside the panel. Its place at
121
+ * normal sizes is the one it always had: 24 px down, in the page pill's band, centred, now over the canvas you can
122
+ * see rather than under an open panel, and keeping clear of the pill. Where that band can't hold it, it's a solid card
123
+ * in the canvas's top-left corner instead, clear of the pill and the zoom controls. Over a panel that covers the
124
+ * canvas it's the panel's own last row (DetailPanel). It never takes a click.
125
+ */
126
+
127
+ /** The overlays' inset from the canvas's edges (the pill's right-4, the legend's left-4 and bottom-4). */
128
+ const OVERLAY_INSET = 16;
129
+ /** The gap the banner keeps from the pill and the legend. */
130
+ const OVERLAY_GAP = 8;
131
+ /** React Flow's zoom controls: 26 px wide, 15 px in from the canvas's right edge. */
132
+ const ZOOM_COLUMN = 15 + 26;
133
+ /** The banner's distance from the canvas's top: as a line in the pill's band (top-6), and as a card (top-4). */
134
+ export const BANNER_TOP = 24;
135
+ export const CARD_TOP = OVERLAY_INSET;
136
+
137
+ export type BannerForm = "band" | "card";
138
+
139
+ /**
140
+ * Which form the banner takes, by the height it has as a line in the band (`null`: not measured yet, the normal case):
141
+ * the band while it ends inside the pill's 64 px band, where a paged level's boxes don't open (two lines), else the card.
142
+ */
143
+ export function bannerForm(bandHeight: number | null): BannerForm {
144
+ return bandHeight === null || BANNER_TOP + bandHeight <= TOP_INSET ? "band" : "card";
145
+ }
146
+
147
+ /**
148
+ * The room the line keeps on its right for the pill, past the band's own 16 px padding: the pill's width and a gap,
149
+ * and as much on its left, so it stays centred over the canvas while it can (`null`: no pill, or not measured yet).
150
+ */
151
+ export function bandClear(pillWidth: number | null): number {
152
+ return pillWidth === null ? 0 : pillWidth + OVERLAY_GAP;
153
+ }
154
+
155
+ /** How far the card's right edge keeps from the canvas's right edge: past the pill, and always past the zoom controls. */
156
+ export function cardClear(pillWidth: number | null): number {
157
+ return Math.max(ZOOM_COLUMN + OVERLAY_GAP, pillWidth === null ? 0 : OVERLAY_INSET + pillWidth + OVERLAY_GAP);
158
+ }
159
+
160
+ /**
161
+ * The open legend's greatest height on a canvas `canvasHeight` px tall (`0`: not measured yet, then no cap). It sits
162
+ * 16 px from the bottom and stops short of what's above it: 16 px from the top, a paged level's pill band, or the
163
+ * banner's bottom (`bannerBottom`, 0 with none) and a gap. Its rows scroll inside.
164
+ */
165
+ export function legendMaxHeight(canvasHeight: number, pillBand: number, bannerBottom: number): number | undefined {
166
+ if (canvasHeight <= 0) return undefined;
167
+ return canvasHeight - OVERLAY_INSET - Math.max(OVERLAY_INSET, pillBand, bannerBottom > 0 ? bannerBottom + OVERLAY_GAP : 0);
168
+ }
@@ -0,0 +1,60 @@
1
+ import type { LevelResponse } from "@/lib/contracts";
2
+
3
+ // The UI imports from lib/ only types and the few pure, browser-safe modules ARCHITECTURE.md §3 names, and
4
+ // lib/index/paths.ts isn't one of them, so these few lines mirror it.
5
+
6
+ /** The folder that holds a path ("" for something at the root). */
7
+ export function parentOf(targetPath: string): string {
8
+ const slash = targetPath.lastIndexOf("/");
9
+ return slash === -1 ? "" : targetPath.slice(0, slash);
10
+ }
11
+
12
+ /** What a path is called on screen: its last segment, or the repo's folder name for the root. */
13
+ export function nameOf(targetPath: string, repoRoot: string): string {
14
+ if (targetPath === "") return repoRoot.slice(repoRoot.lastIndexOf("/") + 1) || repoRoot;
15
+ return targetPath.slice(targetPath.lastIndexOf("/") + 1);
16
+ }
17
+
18
+ /** A path's last two segments, "src/vs/platform/files/common" → "files/common": enough to tell areas apart in a chip. */
19
+ export function shortPath(targetPath: string): string {
20
+ return targetPath.split("/").slice(-2).join("/");
21
+ }
22
+
23
+ /**
24
+ * The extensions of each language the index reads, named as GET /api/source and highlight.js name them (E4, ADR-065).
25
+ * The UI can't import lib/index/languages/rules.ts (ARCHITECTURE.md §3 rule 3), so this mirrors it, one row per
26
+ * language; syntax.test.ts holds the two equal for every extension the rules claim.
27
+ */
28
+ const LANGUAGES: readonly (readonly [extension: RegExp, language: string])[] = [
29
+ [/\.(ts|tsx|mts|cts)$/, "typescript"],
30
+ [/\.(js|jsx|mjs|cjs)$/, "javascript"],
31
+ [/\.pyi?$/, "python"],
32
+ [/\.go$/, "go"],
33
+ [/\.java$/, "java"],
34
+ [/\.cs$/, "csharp"],
35
+ [/\.rs$/, "rust"],
36
+ ];
37
+
38
+ /** The language a path's extension implies, the way GET /api/source names it (the UI can't import lib's copy). */
39
+ export function languageFor(filePath: string): string {
40
+ return LANGUAGES.find(([extension]) => extension.test(filePath))?.[1] ?? "text";
41
+ }
42
+
43
+ /** Whether `targetPath` is strictly inside `folder` ("" holds everything but itself). */
44
+ export function isInside(targetPath: string, folder: string): boolean {
45
+ return folder === "" ? targetPath !== "" : targetPath.startsWith(`${folder}/`);
46
+ }
47
+
48
+ /**
49
+ * The box on this level that holds `targetPath`: the child on the way down to it, or, when that child is on another
50
+ * page, the portal standing for that page. Null when the path isn't inside this level, is the level itself, or is on a
51
+ * page this one has no portal to. Live pulses and highlights use it.
52
+ */
53
+ export function boxFor(level: Pick<LevelResponse, "path" | "nodes">, targetPath: string): string | null {
54
+ if (!isInside(targetPath, level.path)) return null;
55
+ const below = level.path === "" ? targetPath : targetPath.slice(level.path.length + 1);
56
+ const slash = below.indexOf("/");
57
+ const child = slash === -1 ? targetPath : `${level.path === "" ? "" : `${level.path}/`}${below.slice(0, slash)}`;
58
+ if (level.nodes.some((n) => n.path === child)) return child;
59
+ return level.nodes.find((n) => n.kind === "portal" && n.members?.includes(child))?.path ?? null;
60
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Interaction timings for S3 (PIVOT_SPEC.md §2: click, zoom or level change → rendered ≤ 100 ms). A start mark goes
3
+ * down when the interaction begins, and the measure is taken after the frame that painted its result. Task 22 reads
4
+ * them from window.__nebulosityPerf.
5
+ */
6
+ type PerfWindow = Window & { __nebulosityPerf?: { name: string; ms: number }[] };
7
+
8
+ export function markStart(name: string): void {
9
+ performance.clearMarks(`${name}:start`);
10
+ performance.mark(`${name}:start`);
11
+ }
12
+
13
+ /**
14
+ * Drops a start mark nothing will measure: a jump that ended without a new level (the editor's quiet jump to a path the
15
+ * index doesn't hold, or a failed one). Left down, the next level drawn without a mark of its own (an index update's
16
+ * redraw) would be timed from it, as one long change.
17
+ */
18
+ export function clearStart(name: string): void {
19
+ performance.clearMarks(`${name}:start`);
20
+ }
21
+
22
+ export function measureAfterPaint(name: string): void {
23
+ // the first frame callback runs before the paint that shows the change; the second runs after it
24
+ requestAnimationFrame(() => requestAnimationFrame(() => {
25
+ if (performance.getEntriesByName(`${name}:start`, "mark").length === 0) return;
26
+ const measure = performance.measure(name, `${name}:start`);
27
+ performance.clearMarks(`${name}:start`);
28
+ const w = window as PerfWindow;
29
+ (w.__nebulosityPerf ??= []).push({ name, ms: Math.round(measure.duration * 10) / 10 });
30
+ }));
31
+ }
@@ -0,0 +1,36 @@
1
+ import { useEffect, type CSSProperties } from "react";
2
+ import { fitPopover } from "./narrow";
3
+
4
+ /** How far below its anchor's top a popover opens (top-9), and the room it keeps from the Map's bottom edge. */
5
+ const POPOVER_TOP = 36;
6
+ const POPOVER_BOTTOM_ROOM = 8;
7
+
8
+ /**
9
+ * Where a popover opened from `anchor` goes, read from the page as it opens (narrow.ts's fitPopover): aligned with the
10
+ * anchor's start or end edge, kept 16 px inside the Map's sides, and no taller than the Map leaves below it, above the
11
+ * activity bar, which is drawn over it (it then scrolls). At normal sizes that's exactly where it always opened. The
12
+ * Map's box is the top bar's parent, MapView's column, which spans the Map down to the floor.
13
+ */
14
+ export function placePopover(anchor: HTMLElement, width: number, align: "start" | "end"): CSSProperties {
15
+ const column = anchor.closest("header")?.parentElement ?? document.documentElement;
16
+ const map = column.getBoundingClientRect();
17
+ const bottom = column.querySelector(":scope > footer")?.getBoundingClientRect().top ?? map.bottom;
18
+ const box = anchor.getBoundingClientRect();
19
+ const fitted = fitPopover(box, map, width, align);
20
+ const maxHeight = bottom - box.top - POPOVER_TOP - POPOVER_BOTTOM_ROOM;
21
+ return align === "start"
22
+ ? { left: fitted.shift, width: fitted.width, maxHeight }
23
+ : { right: -fitted.shift, width: fitted.width, maxHeight };
24
+ }
25
+
26
+ /**
27
+ * Places an open popover again whenever the window resizes: VS Code's panel can be dragged narrower with a popover
28
+ * open, and one placed for the old width would run past the Map's edge, or keep a height cap it no longer needs.
29
+ */
30
+ export function usePlaceOnResize(open: boolean, place: () => void): void {
31
+ useEffect(() => {
32
+ if (!open) return;
33
+ window.addEventListener("resize", place);
34
+ return () => window.removeEventListener("resize", place);
35
+ }, [open, place]);
36
+ }