@sleep2agi/agent-network-dashboard 0.4.6-preview.13 → 0.4.6-preview.131

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 (335) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +41 -41
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/routes-manifest.json +6 -0
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +2 -2
  17. package/.next/server/app/_not-found.rsc +12 -12
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  19. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  20. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  24. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  25. package/.next/server/app/admin.html +2 -2
  26. package/.next/server/app/admin.rsc +14 -14
  27. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  28. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  29. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  30. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  31. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  32. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  33. package/.next/server/app/api/hub/servers/route/app-paths-manifest.json +3 -0
  34. package/.next/server/app/api/hub/servers/route/build-manifest.json +9 -0
  35. package/.next/server/app/api/hub/servers/route/server-reference-manifest.json +4 -0
  36. package/.next/server/app/api/hub/servers/route.js +7 -0
  37. package/.next/server/app/api/hub/servers/route.js.map +5 -0
  38. package/.next/server/app/api/hub/servers/route.js.nft.json +1 -0
  39. package/.next/server/app/api/hub/servers/route_client-reference-manifest.js +3 -0
  40. package/.next/server/app/index.html +2 -2
  41. package/.next/server/app/index.rsc +14 -14
  42. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  43. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  44. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  45. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  46. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  47. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/login.html +2 -2
  49. package/.next/server/app/login.rsc +14 -14
  50. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  55. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  56. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/logs.html +2 -2
  58. package/.next/server/app/logs.rsc +14 -14
  59. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  60. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  61. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  62. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  64. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  65. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app/messages.html +2 -2
  67. package/.next/server/app/messages.rsc +14 -14
  68. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  69. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  70. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  71. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  73. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  74. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/node.html +2 -2
  76. package/.next/server/app/node.rsc +14 -14
  77. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  78. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  79. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  80. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  82. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  83. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/nodes.html +2 -2
  85. package/.next/server/app/nodes.rsc +14 -14
  86. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  87. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  88. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  89. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  90. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  91. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  92. package/.next/server/app/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/server-logs.html +2 -2
  95. package/.next/server/app/server-logs.rsc +14 -14
  96. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  97. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  98. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  99. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  100. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  101. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  102. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  103. package/.next/server/app/settings/networks.html +2 -2
  104. package/.next/server/app/settings/networks.rsc +14 -14
  105. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  106. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  107. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  108. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  109. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  110. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  111. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  112. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  113. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  114. package/.next/server/app/settings/tokens.html +2 -2
  115. package/.next/server/app/settings/tokens.rsc +14 -14
  116. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  117. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  118. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  119. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  120. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  121. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  122. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  123. package/.next/server/app/settings.html +2 -2
  124. package/.next/server/app/settings.rsc +14 -14
  125. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  126. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  127. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  128. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  130. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  131. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  132. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  133. package/.next/server/app/tasks.html +2 -2
  134. package/.next/server/app/tasks.rsc +14 -14
  135. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  136. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  137. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  138. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  139. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  140. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  141. package/.next/server/app-paths-manifest.json +1 -0
  142. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js +3 -0
  143. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js.map +1 -0
  144. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +3 -0
  145. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -0
  146. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
  147. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
  148. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  149. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  150. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
  151. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
  152. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -1
  153. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  154. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  155. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  156. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +1 -1
  157. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -1
  158. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  159. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  160. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
  161. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
  162. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
  163. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
  164. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
  165. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
  166. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
  167. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
  168. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
  169. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
  170. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
  171. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
  172. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  173. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  174. package/.next/server/middleware-build-manifest.js +3 -3
  175. package/.next/server/pages/404.html +2 -2
  176. package/.next/server/pages/500.html +1 -1
  177. package/.next/server/server-reference-manifest.js +1 -1
  178. package/.next/server/server-reference-manifest.json +1 -1
  179. package/.next/static/chunks/0-mpa_947ipeq.js +1 -0
  180. package/.next/static/chunks/01lmbsd37fybu.js +1 -0
  181. package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
  182. package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
  183. package/.next/static/chunks/05uk96gc~9mni.js +1 -0
  184. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  185. package/.next/static/chunks/09pxnkf-1.1fj.js +1 -0
  186. package/.next/static/chunks/0eo167c-wlmfz.js +4 -0
  187. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  188. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  189. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  190. package/.next/static/chunks/0yv1jpx~5v038.js +7 -0
  191. package/.next/static/chunks/10tq~cwj-obp4.js +1 -0
  192. package/.next/static/chunks/1450fa_~v9qpc.css +2 -0
  193. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  194. package/.next/trace +2 -2
  195. package/.next/trace-build +1 -1
  196. package/.next/types/routes.d.ts +2 -1
  197. package/.next/types/validator.ts +9 -0
  198. package/app/api/hub/servers/route.ts +39 -0
  199. package/app/components/ChatPopover.tsx +38 -2
  200. package/app/components/CommandPalette.tsx +107 -0
  201. package/app/components/HelpOverlay.tsx +87 -0
  202. package/app/components/ServersDrawer.tsx +64 -27
  203. package/app/components/TopoGraph.tsx +2419 -163
  204. package/app/components/utils.ts +8 -7
  205. package/app/globals.css +19 -1
  206. package/app/lib/time.ts +36 -0
  207. package/package.json +1 -1
  208. package/scripts/help-overlay-topo-test.mjs +44 -0
  209. package/scripts/servers-drawer-live-test.mjs +96 -0
  210. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  211. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  212. package/scripts/topo-active-links-chip-test.mjs +73 -0
  213. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  214. package/scripts/topo-arrival-ping-test.mjs +87 -0
  215. package/scripts/topo-arrow-tier-test.mjs +61 -0
  216. package/scripts/topo-auto-fit-test.mjs +73 -0
  217. package/scripts/topo-chat-focus-ring-test.mjs +77 -0
  218. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  219. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  220. package/scripts/topo-click-ripple-test.mjs +66 -0
  221. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  222. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  223. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  224. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  225. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  226. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  227. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  228. package/scripts/topo-dispatch-pulse-test.mjs +83 -0
  229. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  230. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  231. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  232. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  233. package/scripts/topo-edge-focus-test.mjs +113 -0
  234. package/scripts/topo-edge-hover-test.mjs +93 -0
  235. package/scripts/topo-edge-pill-test.mjs +105 -0
  236. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  237. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  238. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  239. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  240. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  241. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  242. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  243. package/scripts/topo-fit-key-test.mjs +106 -0
  244. package/scripts/topo-freshness-chip-test.mjs +74 -0
  245. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  246. package/scripts/topo-group-count-test.mjs +68 -0
  247. package/scripts/topo-group-hover-test.mjs +82 -0
  248. package/scripts/topo-group-label-hover-test.mjs +119 -0
  249. package/scripts/topo-group-label-tint-test.mjs +99 -0
  250. package/scripts/topo-group-label-title-test.mjs +83 -0
  251. package/scripts/topo-group-pin-test.mjs +119 -0
  252. package/scripts/topo-group-status-pips-test.mjs +103 -0
  253. package/scripts/topo-groupbox-live-test.mjs +100 -0
  254. package/scripts/topo-groupbox-test.mjs +4 -2
  255. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  256. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  257. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  258. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  259. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  260. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  261. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  262. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  263. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  264. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  265. package/scripts/topo-hub-busyness-test.mjs +100 -0
  266. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  267. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  268. package/scripts/topo-hub-title-test.mjs +93 -0
  269. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  270. package/scripts/topo-label-lift-test.mjs +119 -0
  271. package/scripts/topo-label-scale-test.mjs +78 -0
  272. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  273. package/scripts/topo-layout-key-test.mjs +91 -0
  274. package/scripts/topo-legend-count-test.mjs +88 -0
  275. package/scripts/topo-legend-hover-test.mjs +113 -0
  276. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  277. package/scripts/topo-legend-pin-test.mjs +128 -0
  278. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  279. package/scripts/topo-minimap-test.mjs +68 -0
  280. package/scripts/topo-mobile-chips-test.mjs +103 -0
  281. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  282. package/scripts/topo-node-title-test.mjs +101 -0
  283. package/scripts/topo-offline-dim-test.mjs +70 -0
  284. package/scripts/topo-overlap-test.mjs +32 -5
  285. package/scripts/topo-pan-cursor-test.mjs +62 -0
  286. package/scripts/topo-panel-elevation-test.mjs +76 -0
  287. package/scripts/topo-particle-stagger-test.mjs +82 -0
  288. package/scripts/topo-pin-intersection-test.mjs +121 -0
  289. package/scripts/topo-pin-persistence-test.mjs +105 -0
  290. package/scripts/topo-popover-meta-test.mjs +78 -0
  291. package/scripts/topo-pressure-bar-test.mjs +69 -0
  292. package/scripts/topo-pressure-hover-test.mjs +123 -0
  293. package/scripts/topo-pressure-pin-test.mjs +119 -0
  294. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  295. package/scripts/topo-pulse-rate-test.mjs +71 -0
  296. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  297. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  298. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  299. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  300. package/scripts/topo-recent-row-ts-test.mjs +92 -0
  301. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  302. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  303. package/scripts/topo-reduced-motion-test.mjs +77 -0
  304. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  305. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  306. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  307. package/scripts/topo-tier-ring-test.mjs +69 -0
  308. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  309. package/scripts/topo-tier-stagger-test.mjs +89 -0
  310. package/scripts/topo-tz-ghost-test.mjs +69 -0
  311. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  312. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  313. package/scripts/topo-vendor-pill-test.mjs +125 -0
  314. package/scripts/topo-vendor-pin-test.mjs +125 -0
  315. package/scripts/topo-wheel-zoom-test.mjs +79 -0
  316. package/scripts/topo-working-halo-breath-test.mjs +86 -0
  317. package/scripts/topo-zoom-keys-test.mjs +89 -0
  318. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  319. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  320. package/.next/static/chunks/03u_oa2o2jrr~.js +0 -1
  321. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  322. package/.next/static/chunks/0dttftqqehpdj.js +0 -1
  323. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  324. package/.next/static/chunks/0fqy_0wj_1xa7.js +0 -7
  325. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  326. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  327. package/.next/static/chunks/0q_z8ad2uojv5.js +0 -1
  328. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  329. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  330. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  331. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  332. package/.next/static/chunks/17h~0~zsxjx6x.css +0 -2
  333. /package/.next/static/{nhazRrlYzcsAAYMwx8NFz → A3ssjbOXf7a3BaCWBznPU}/_buildManifest.js +0 -0
  334. /package/.next/static/{nhazRrlYzcsAAYMwx8NFz → A3ssjbOXf7a3BaCWBznPU}/_clientMiddlewareManifest.js +0 -0
  335. /package/.next/static/{nhazRrlYzcsAAYMwx8NFz → A3ssjbOXf7a3BaCWBznPU}/_ssgManifest.js +0 -0
@@ -0,0 +1,64 @@
1
+ /* Round 87 verification: HelpOverlay catches up on R83 (pressure-bar
2
+ * segment hover preview) + R86 (group-label hover preview). Continues
3
+ * the R59/R62/R65/R70/R78/R81 docs-catch-up cadence (~5 rounds).
4
+ *
5
+ * Stacks five generations of help-overlay assertions — any future
6
+ * regression that drops an entry will be caught by one of:
7
+ * R87 (this) → R81 → R78 → R74 → R70 → R65 → R59
8
+ */
9
+ import { chromium } from 'playwright';
10
+ import { readFileSync } from 'node:fs';
11
+
12
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
13
+ const browser = await chromium.launch({ headless: true });
14
+
15
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
16
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
17
+ await ctx.addInitScript(() => {
18
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
19
+ });
20
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
21
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
22
+ const page = await ctx.newPage();
23
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
24
+ await page.waitForTimeout(800);
25
+
26
+ await page.keyboard.press('?');
27
+ await page.waitForSelector('[role="dialog"][aria-label="Keyboard shortcuts"]', { timeout: 5000 });
28
+
29
+ const items = await page.evaluate(() => {
30
+ const dialog = document.querySelector('[role="dialog"][aria-label="Keyboard shortcuts"]');
31
+ const groups = [...(dialog?.querySelectorAll('div') || [])];
32
+ let active = null;
33
+ for (const g of groups) {
34
+ if (g.textContent && /^Topology canvas$/i.test(g.textContent.trim())) {
35
+ active = g.parentElement;
36
+ break;
37
+ }
38
+ }
39
+ return active ? [...active.querySelectorAll('li')].map(li => (li.textContent || '').trim()) : [];
40
+ });
41
+
42
+ await browser.close();
43
+
44
+ const has = (s) => items.some(t => t.includes(s));
45
+ const results = {
46
+ r87_segHover: has('Preview a status filter — hover a pressure-bar segment (R83)'),
47
+ r87_groupLabelHover: has('Preview a team focus — hover a group label (R86)'),
48
+ // R81 priors
49
+ r81_workingChip: has('Highlight working nodes — chip-row sibling of legend (R79)'),
50
+ r81_vendorChip: has('Highlight one vendor — hover A/O/书/? in the chip row (R80)'),
51
+ // R78 priors
52
+ r78_pillBodyClick: has('Clear filter — click anywhere on the pill body (R73)'),
53
+ r78_activeLinks: has('Brighten all active flows — hover'),
54
+ // R74 / R70 / R65 / R59 priors — five generations of docs catch-up
55
+ r74_cmdkLayout: has('Toggle layout / Fit canvas via palette (R74)'),
56
+ r70_cmdkPin: has('(R69)'),
57
+ r65_groupClick: has('Pin a team focus (R63)'),
58
+ r62_pinChip: has('Pin a status filter (R60/R61)'),
59
+ r59_clickHub: has('Fit topology to canvas (R52)'),
60
+ };
61
+ const ok = Object.values(results).every(Boolean);
62
+ console.log(`${ok ? '✅' : '❌'} help overlay R87:`, JSON.stringify(results),
63
+ `\n items=`, items);
64
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,65 @@
1
+ /* Round 91 verification: HelpOverlay catches up on R88 vendor click-
2
+ * to-pin and R90 granular Cmd+K vendor-clear. Continues the
3
+ * R59/R62/R65/R70/R78/R81/R87 docs-catch-up cadence.
4
+ *
5
+ * Seven generations of help-overlay assertions stacked here — any
6
+ * future regression that drops an entry will be caught by one of
7
+ * R91 → R87 → R81 → R78 → R74 → R70 → R65 → R59.
8
+ */
9
+ import { chromium } from 'playwright';
10
+ import { readFileSync } from 'node:fs';
11
+
12
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
13
+ const browser = await chromium.launch({ headless: true });
14
+
15
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
16
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
17
+ await ctx.addInitScript(() => {
18
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
19
+ });
20
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
21
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
22
+ const page = await ctx.newPage();
23
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
24
+ await page.waitForTimeout(800);
25
+
26
+ await page.keyboard.press('?');
27
+ await page.waitForSelector('[role="dialog"][aria-label="Keyboard shortcuts"]', { timeout: 5000 });
28
+
29
+ const items = await page.evaluate(() => {
30
+ const dialog = document.querySelector('[role="dialog"][aria-label="Keyboard shortcuts"]');
31
+ const groups = [...(dialog?.querySelectorAll('div') || [])];
32
+ let active = null;
33
+ for (const g of groups) {
34
+ if (g.textContent && /^Topology canvas$/i.test(g.textContent.trim())) {
35
+ active = g.parentElement;
36
+ break;
37
+ }
38
+ }
39
+ return active ? [...active.querySelectorAll('li')].map(li => (li.textContent || '').trim()) : [];
40
+ });
41
+
42
+ await browser.close();
43
+
44
+ const has = (s) => items.some(t => t.includes(s));
45
+ const results = {
46
+ r91_vendorPin: has('Pin a vendor filter — click A/O/书/? in the chip row (R88)'),
47
+ r91_cmdkVendorClear: has('Clear vendor pin only via palette → "Clear topology vendor filter" (R90)'),
48
+ // R87 priors (hover preview pair)
49
+ r87_segHover: has('Preview a status filter — hover a pressure-bar segment (R83)'),
50
+ r87_groupLabelHover: has('Preview a team focus — hover a group label (R86)'),
51
+ // R81 priors (chip-row hover pair)
52
+ r81_workingChip: has('Highlight working nodes — chip-row sibling of legend (R79)'),
53
+ r81_vendorChip: has('Highlight one vendor — hover A/O/书/? in the chip row (R80)'),
54
+ // R78 / R74 / R70 / R65 / R59 priors — seven generations
55
+ r78_pillBodyClick: has('Clear filter — click anywhere on the pill body (R73)'),
56
+ r74_cmdkLayout: has('Toggle layout / Fit canvas via palette (R74)'),
57
+ r70_cmdkPin: has('(R69)'),
58
+ r65_groupClick: has('Pin a team focus (R63)'),
59
+ r62_pinChip: has('Pin a status filter (R60/R61)'),
60
+ r59_clickHub: has('Fit topology to canvas (R52)'),
61
+ };
62
+ const ok = Object.values(results).every(Boolean);
63
+ console.log(`${ok ? '✅' : '❌'} help overlay R91:`, JSON.stringify(results),
64
+ `\n items=`, items);
65
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,100 @@
1
+ /* Round 84 verification: hub-halo breath amplitude + tempo reflect
2
+ * `workingCount`. Buckets: 0 / 1-2 / 3-5 / 6+. An idle fleet keeps
3
+ * the original 0.32→0.52 (light) / 0.08→0.16 (dark) cycle over 4s;
4
+ * a busy fleet pulses faster with a higher peak. Pure visual breath
5
+ * — geometry unchanged, overlap test untouched.
6
+ *
7
+ * Drive workingCount via mocked /api/hub/status sessions, then read
8
+ * the SMIL `<animate>` attributes off the hub halo. The bucket gets
9
+ * surfaced as `data-hub-busyness=0..3` for stable assertion without
10
+ * parsing values strings.
11
+ */
12
+ import { chromium } from 'playwright';
13
+ import { readFileSync } from 'node:fs';
14
+
15
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
16
+ const browser = await chromium.launch({ headless: true });
17
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
18
+
19
+ const probe = async (workers) => {
20
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1200 } });
21
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
22
+ await ctx.addInitScript(() => {
23
+ try {
24
+ localStorage.setItem('anet-theme', 'cyber');
25
+ localStorage.setItem('anet-topo-layout', 'ring');
26
+ sessionStorage.setItem('anet_v3_auth', '1');
27
+ } catch {}
28
+ });
29
+ await ctx.route('**/api/hub/status*', async (route) => {
30
+ const r = await route.fetch();
31
+ const b = await r.json();
32
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
33
+ const sessions = [];
34
+ for (let i = 0; i < workers; i++) {
35
+ sessions.push({ alias: `wkr${i}`, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
36
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh });
37
+ }
38
+ sessions.push({ alias: 'idl', status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
39
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh });
40
+ await route.fulfill({ response: r, json: { ...b, sessions } });
41
+ });
42
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
46
+ await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n,
47
+ workers + 1, { timeout: 30000 });
48
+ await page.waitForTimeout(400);
49
+
50
+ const data = await page.evaluate(() => {
51
+ const halo = document.querySelector('[data-hub-busyness]');
52
+ const ani = halo?.querySelector('animate');
53
+ return {
54
+ busy: halo?.getAttribute('data-hub-busyness'),
55
+ values: ani?.getAttribute('values') || null,
56
+ dur: ani?.getAttribute('dur') || null,
57
+ };
58
+ });
59
+ await ctx.close();
60
+ return data;
61
+ };
62
+
63
+ // Helper: parse "trough;peak;trough" into [trough, peak].
64
+ const parseValues = (v) => v ? v.split(';').map(Number) : [];
65
+ const peakOf = (v) => Math.max(...parseValues(v));
66
+ const durOf = (s) => s ? parseFloat(s.replace('s', '')) : 0;
67
+
68
+ const b0 = await probe(0); // idle only
69
+ const b1 = await probe(1); // 1 worker → bucket 1
70
+ const b2 = await probe(4); // 4 workers → bucket 2
71
+ const b3 = await probe(7); // 7 workers → bucket 3
72
+ await browser.close();
73
+
74
+ const results = {
75
+ b0_bucket: b0.busy === '0',
76
+ b1_bucket: b1.busy === '1',
77
+ b2_bucket: b2.busy === '2',
78
+ b3_bucket: b3.busy === '3',
79
+ // Peak climbs with bucket — dark theme values.
80
+ peak_monotonic: peakOf(b0.values) < peakOf(b1.values) &&
81
+ peakOf(b1.values) < peakOf(b2.values) &&
82
+ peakOf(b2.values) < peakOf(b3.values),
83
+ // Duration shortens with bucket.
84
+ dur_monotonic: durOf(b0.dur) > durOf(b1.dur) &&
85
+ durOf(b1.dur) > durOf(b2.dur) &&
86
+ durOf(b2.dur) > durOf(b3.dur),
87
+ // Idle baseline preserved.
88
+ b0_idleDur_4s: durOf(b0.dur) === 4,
89
+ // Busy cap respected.
90
+ b3_capped_24s: durOf(b3.dur) === 2.4,
91
+ // All have the same trough (preserving the rest baseline).
92
+ trough_constant: parseValues(b0.values)[0] === parseValues(b3.values)[0],
93
+ };
94
+ const ok = Object.values(results).every(Boolean);
95
+ console.log(`${ok ? '✅' : '❌'} hub busyness:`, JSON.stringify(results),
96
+ `\n b0=`, b0,
97
+ `\n b1=`, b1,
98
+ `\n b2=`, b2,
99
+ `\n b3=`, b3);
100
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,137 @@
1
+ /* Round 52 verification: clicking the central hub triggers fitView() and
2
+ * shows a click-ripple. The hub was previously decoration; now it's the
3
+ * "re-center" affordance for users who don't know the `f` shortcut.
4
+ *
5
+ * Steps:
6
+ * - Zoom + pan the topology to a non-default view (Cmd+= twice + drag).
7
+ * Confirm the zoom group's transform shifted off identity.
8
+ * - Click the hub. Assert:
9
+ * 1. the zoom group's transform returns to a fitted state (zoom and
10
+ * translate change), i.e. the view changed back toward identity-
11
+ * or-fit;
12
+ * 2. a transient ripple <circle> shows up at the hub center, then
13
+ * vanishes within ~800ms.
14
+ * - Confirm the hub's <title> mentions "click to fit view".
15
+ */
16
+ import { chromium } from 'playwright';
17
+ import { readFileSync } from 'node:fs';
18
+
19
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
20
+ const browser = await chromium.launch({ headless: true });
21
+
22
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1500 } });
23
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
24
+ await ctx.addInitScript(() => {
25
+ try {
26
+ localStorage.setItem('anet-theme', 'cyber');
27
+ localStorage.setItem('anet-topo-layout', 'ring');
28
+ sessionStorage.setItem('anet_v3_auth', '1');
29
+ } catch {}
30
+ });
31
+
32
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
33
+ await ctx.route('**/api/hub/status*', async (route) => {
34
+ const r = await route.fetch();
35
+ const b = await r.json();
36
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
37
+ const sessions = ['alpha', 'beta', 'gamma'].map(a => ({
38
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
39
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
40
+ }));
41
+ await route.fulfill({ response: r, json: { ...b, sessions } });
42
+ });
43
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
44
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForSelector('g[data-topo-hub]', { timeout: 30000 });
48
+ await page.waitForTimeout(500);
49
+
50
+ // Read the hub <title> to verify the hint text.
51
+ const hubTitle = await page.evaluate(() => {
52
+ return document.querySelector('g[data-topo-hub] title')?.textContent || '';
53
+ });
54
+
55
+ // Find the zoom/pan wrapper <g> (the one whose transform attribute holds
56
+ // the scale + translate string). The TopoGraph wraps all canvas content
57
+ // in a <g> with transform="translate(x, y) scale(zoom)" at the panel root.
58
+ // Grab the first child <g> under the SVG that carries a `transform` with
59
+ // a `scale(` token — that's our zoom group.
60
+ const readTransform = () => page.evaluate(() => {
61
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
62
+ for (const g of svg.querySelectorAll(':scope > g')) {
63
+ const t = g.getAttribute('transform') || '';
64
+ if (t.includes('scale(')) return t;
65
+ }
66
+ return null;
67
+ });
68
+
69
+ const baseTransform = await readTransform();
70
+
71
+ // Nudge zoom by simulating Ctrl+= twice via keyboard shortcut. The
72
+ // keymap maps '+' to zoomBy; press 'Equal' with the right modifier so
73
+ // browsers see '+'.
74
+ await page.keyboard.press('+');
75
+ await page.keyboard.press('+');
76
+ await page.keyboard.press('+');
77
+ await page.waitForTimeout(150);
78
+ const zoomedTransform = await readTransform();
79
+
80
+ // Confirm the zoom transform actually changed.
81
+ const zoomChanged = baseTransform !== zoomedTransform;
82
+
83
+ // Click the hub center. Locator-based click hits the union bbox edge and
84
+ // gets intercepted by the SVG panel rect (which must keep pointer events
85
+ // for canvas pan). Sample the inner core circle (r=10 at viewBox 500,330)
86
+ // via getScreenCTM and click that on-screen point directly.
87
+ const hubCenter = await page.evaluate(() => {
88
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
89
+ const core = [...svg.querySelectorAll('g[data-topo-hub] circle')].find(
90
+ c => c.getAttribute('r') === '10'
91
+ );
92
+ if (!core) return null;
93
+ const ctm = core.getScreenCTM();
94
+ const cx = +core.getAttribute('cx');
95
+ const cy = +core.getAttribute('cy');
96
+ return { x: cx * ctm.a + cy * ctm.c + ctm.e, y: cx * ctm.b + cy * ctm.d + ctm.f };
97
+ });
98
+ if (!hubCenter) { console.log('❌ hub core circle not found'); process.exit(1); }
99
+ await page.mouse.click(hubCenter.x, hubCenter.y);
100
+ await page.waitForTimeout(150);
101
+ const afterClickTransform = await readTransform();
102
+ const ripplePresent = await page.evaluate(() => {
103
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
104
+ // The click ripple is a <circle> with stroke-width="2" + an <animate>.
105
+ const ripples = [...svg.querySelectorAll('circle')].filter(c => {
106
+ const sw = c.getAttribute('stroke-width');
107
+ return sw === '2' && c.querySelector('animate[attributeName="r"]');
108
+ });
109
+ return ripples.length > 0;
110
+ });
111
+
112
+ await page.waitForTimeout(800);
113
+ const rippleGone = await page.evaluate(() => {
114
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
115
+ const ripples = [...svg.querySelectorAll('circle')].filter(c => {
116
+ const sw = c.getAttribute('stroke-width');
117
+ return sw === '2' && c.querySelector('animate[attributeName="r"]');
118
+ });
119
+ return ripples.length === 0;
120
+ });
121
+
122
+ await browser.close();
123
+
124
+ const results = {
125
+ hubTitleHasFitHint: /click to fit view/i.test(hubTitle),
126
+ zoomShifted: zoomChanged,
127
+ hubClickRestoredView: afterClickTransform !== zoomedTransform,
128
+ ripplePresentDuringClick: ripplePresent,
129
+ rippleClearsAfter600ms: rippleGone,
130
+ };
131
+ const ok = Object.values(results).every(Boolean);
132
+ console.log(`${ok ? '✅' : '❌'} hub click fit:`, JSON.stringify(results),
133
+ `\n hubTitle=${JSON.stringify(hubTitle)}`,
134
+ `\n base =${baseTransform}`,
135
+ `\n zoomed =${zoomedTransform}`,
136
+ `\n fitBack =${afterClickTransform}`);
137
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,82 @@
1
+ /* Round 115 verification: hub center hover-state visual feedback.
2
+ * R52 made the hub clickable (fit view) + cursor:pointer, but
3
+ * hovering showed nothing. Now a thin r=14 stroke ring fades in on
4
+ * hover and out on leave. Sits inside the existing r=18 halo so
5
+ * overlap footprint unchanged.
6
+ *
7
+ * Probes:
8
+ * - baseline: ring opacity 0, data-topo-hub-hovered="false"
9
+ * - hover hub: ring opacity > 0, hovered="true"
10
+ * - mouseleave: opacity 0, hovered="false"
11
+ */
12
+ import { chromium } from 'playwright';
13
+ import { readFileSync } from 'node:fs';
14
+
15
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
16
+ const browser = await chromium.launch({ headless: true });
17
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
18
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
19
+ await ctx.addInitScript(() => {
20
+ try {
21
+ localStorage.setItem('anet-theme', 'cyber');
22
+ localStorage.setItem('anet-topo-layout', 'ring');
23
+ sessionStorage.setItem('anet_v3_auth', '1');
24
+ } catch {}
25
+ });
26
+
27
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
28
+ await ctx.route('**/api/hub/status*', async (route) => {
29
+ const r = await route.fetch();
30
+ const b = await r.json();
31
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
32
+ const mk = (alias) => ({
33
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
34
+ network_id: nid, project_dir: null,
35
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
36
+ });
37
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta'), mk('gamma')] } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
40
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
41
+ const page = await ctx.newPage();
42
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
43
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
44
+ await page.waitForSelector('[data-topo-hub]', { timeout: 10000 });
45
+ await page.waitForTimeout(400);
46
+
47
+ const read = () => page.evaluate(() => {
48
+ const hub = document.querySelector('[data-topo-hub]');
49
+ const ring = document.querySelector('[data-topo-hub-hover-ring]');
50
+ return {
51
+ hovered: hub?.getAttribute('data-topo-hub-hovered') || '',
52
+ ringOpacity: +(ring?.getAttribute('opacity') || '0'),
53
+ };
54
+ });
55
+
56
+ const before = await read();
57
+
58
+ await page.locator('[data-topo-hub]').hover();
59
+ await page.waitForTimeout(300);
60
+ const onHover = await read();
61
+
62
+ // Move pointer far away.
63
+ await page.mouse.move(10, 10);
64
+ await page.waitForTimeout(300);
65
+ const afterLeave = await read();
66
+
67
+ await browser.close();
68
+
69
+ const results = {
70
+ before_notHovered: before.hovered === 'false',
71
+ before_ringHidden: before.ringOpacity === 0,
72
+ hover_attrTrue: onHover.hovered === 'true',
73
+ hover_ringVisible: onHover.ringOpacity > 0.5,
74
+ leave_attrFalse: afterLeave.hovered === 'false',
75
+ leave_ringHidden: afterLeave.ringOpacity === 0,
76
+ };
77
+ const ok = Object.values(results).every(Boolean);
78
+ console.log(`${ok ? '✅' : '❌'} hub-hover ring:`, JSON.stringify(results),
79
+ `\n before=`, before,
80
+ `\n onHover=`, onHover,
81
+ `\n afterLeave=`, afterLeave);
82
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,93 @@
1
+ /* Round 43 verification: central hub carries a `<title>` summary in ring
2
+ * layout. Assertions:
3
+ * - hub renders in ring layout (data-topo-hub group present)
4
+ * - <title> reads "Network hub · N sessions · …" with the right counts
5
+ * - zero-count clauses (online/working/links) drop cleanly
6
+ * - grid layout doesn't render the hub at all */
7
+ import { chromium } from 'playwright';
8
+ import { readFileSync } from 'node:fs';
9
+
10
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
11
+ const browser = await chromium.launch({ headless: true });
12
+
13
+ async function probe({ layout, sessionsSpec, msgs = [] }) {
14
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1000 } });
15
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
16
+ await ctx.addInitScript((lay) => {
17
+ try {
18
+ localStorage.setItem('anet-theme', 'cyber');
19
+ localStorage.setItem('anet-topo-layout', lay);
20
+ sessionStorage.setItem('anet_v3_auth', '1');
21
+ } catch {}
22
+ }, layout);
23
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
24
+ await ctx.route('**/api/hub/status*', async (route) => {
25
+ const r = await route.fetch();
26
+ const b = await r.json();
27
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
28
+ const sessions = sessionsSpec.map(s => ({
29
+ alias: s.alias, status: s.status, network_id: nid, project_dir: null,
30
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
31
+ }));
32
+ await route.fulfill({ response: r, json: { ...b, sessions } });
33
+ });
34
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
35
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
36
+ const page = await ctx.newPage();
37
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
38
+ await page.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 30000 });
39
+ await page.waitForTimeout(500);
40
+ const data = await page.evaluate(() => {
41
+ const hub = document.querySelector('g[data-topo-hub]');
42
+ const title = hub?.querySelector('title');
43
+ return { hubPresent: !!hub, titleText: title?.textContent || null };
44
+ });
45
+ await ctx.close();
46
+ return data;
47
+ }
48
+
49
+ // Ring + mixed-status fleet → full summary line
50
+ const ringFull = await probe({
51
+ layout: 'ring',
52
+ sessionsSpec: [
53
+ { alias: 'a', status: 'working' },
54
+ { alias: 'b', status: 'idle' },
55
+ { alias: 'c', status: 'idle' },
56
+ { alias: 'd', status: 'offline' },
57
+ ],
58
+ msgs: [
59
+ { from_alias: 'a', to_alias: 'b', content: 'm', created_at: new Date().toISOString() },
60
+ ],
61
+ });
62
+ // Ring + all-offline → online/working/links clauses drop
63
+ const ringOfflineOnly = await probe({
64
+ layout: 'ring',
65
+ sessionsSpec: [
66
+ { alias: 'x', status: 'offline' },
67
+ { alias: 'y', status: 'offline' },
68
+ ],
69
+ });
70
+ // Grid → no hub at all
71
+ const grid = await probe({
72
+ layout: 'grid',
73
+ sessionsSpec: [{ alias: 'a', status: 'idle' }, { alias: 'b', status: 'idle' }],
74
+ });
75
+
76
+ await browser.close();
77
+ // R52 appended "\nclick to fit view" as a second line to the hub
78
+ // <title> so the click-to-fit affordance is discoverable on hover.
79
+ // Compare on the first line only; assert the hint is present too.
80
+ const firstLine = (s) => (s || '').split('\n')[0];
81
+ const results = {
82
+ ringHubPresent: ringFull.hubPresent === true,
83
+ ringFullSummary: firstLine(ringFull.titleText) === 'Network hub · 4 sessions · 3 online · 1 working · 1 active link',
84
+ ringOfflineSummary: ringOfflineOnly.hubPresent === true && firstLine(ringOfflineOnly.titleText) === 'Network hub · 2 sessions',
85
+ ringHintsAtFit: /click to fit view/i.test(ringFull.titleText || ''),
86
+ gridHasNoHub: grid.hubPresent === false,
87
+ };
88
+ const ok = Object.values(results).every(Boolean);
89
+ console.log(`${ok ? '✅' : '❌'} hub title:`, JSON.stringify(results),
90
+ `\n ringFull=${JSON.stringify(ringFull.titleText)}`,
91
+ `\n ringOffline=${JSON.stringify(ringOfflineOnly.titleText)}`,
92
+ `\n gridHub=${grid.hubPresent}`);
93
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,103 @@
1
+ /* Round 53 verification: edges between two members of the hovered prefix
2
+ * group brighten to 1.3× even if neither endpoint is the exact hovered
3
+ * alias. Edges leaving the team still dim per R40.
4
+ *
5
+ * Sessions:
6
+ * alpha1, alpha2, alpha3 — share prefix "alpha" → same group
7
+ * beta — singleton
8
+ * Messages:
9
+ * alpha1 → alpha2 (touches alpha1 directly → 1.7× per R40)
10
+ * alpha2 → alpha3 (BOTH in alpha group but neither is hovered → 1.3× per R53)
11
+ * alpha1 → beta (touches alpha1, beta is outside → 1.7× per R40)
12
+ * alpha2 → beta (in-group endpoint, but beta is outside → 0.35× per R40)
13
+ *
14
+ * Hover alpha1 and read the visible flow paths' opacity to validate the
15
+ * 4-tier ladder.
16
+ */
17
+ import { chromium } from 'playwright';
18
+ import { readFileSync } from 'node:fs';
19
+
20
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
21
+ const browser = await chromium.launch({ headless: true });
22
+
23
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
24
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
25
+ await ctx.addInitScript(() => {
26
+ try {
27
+ localStorage.setItem('anet-theme', 'cyber');
28
+ localStorage.setItem('anet-topo-layout', 'grid');
29
+ sessionStorage.setItem('anet_v3_auth', '1');
30
+ } catch {}
31
+ });
32
+
33
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
34
+ await ctx.route('**/api/hub/status*', async (route) => {
35
+ const r = await route.fetch();
36
+ const b = await r.json();
37
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
38
+ const sessions = ['alpha1', 'alpha2', 'alpha3', 'beta'].map(a => ({
39
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
40
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
41
+ }));
42
+ await route.fulfill({ response: r, json: { ...b, sessions } });
43
+ });
44
+ const now = new Date().toISOString();
45
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
46
+ { from_alias: 'alpha1', to_alias: 'alpha2', content: 'm', created_at: now },
47
+ { from_alias: 'alpha2', to_alias: 'alpha3', content: 'm', created_at: now },
48
+ { from_alias: 'alpha1', to_alias: 'beta', content: 'm', created_at: now },
49
+ { from_alias: 'alpha2', to_alias: 'beta', content: 'm', created_at: now },
50
+ ] } }));
51
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
52
+ const page = await ctx.newPage();
53
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
54
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
55
+ await page.waitForTimeout(600);
56
+
57
+ const readEdgeOpacities = () => page.evaluate(() => {
58
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
59
+ const out = {};
60
+ for (const g of svg.querySelectorAll(':scope > g > g')) {
61
+ const t = g.querySelector('path[data-edge-hitbox] title');
62
+ if (!t) continue;
63
+ const route = (t.textContent || '').split('\n')[0];
64
+ const base = [...g.querySelectorAll(':scope > path')].find(
65
+ p => !p.hasAttribute('data-edge-hitbox') && p.hasAttribute('marker-end')
66
+ );
67
+ if (base) out[route] = +base.getAttribute('opacity');
68
+ }
69
+ return out;
70
+ });
71
+
72
+ const before = await readEdgeOpacities();
73
+
74
+ // Hover alpha1.
75
+ await page.locator('g[data-node="alpha1"]').hover();
76
+ await page.waitForTimeout(300);
77
+ const after = await readEdgeOpacities();
78
+
79
+ await browser.close();
80
+
81
+ const get = (m, r) => m[r];
82
+ const baseRef = get(before, 'alpha1 → alpha2'); // pick any
83
+ const exactTouch = get(after, 'alpha1 → alpha2'); // R40 1.7×
84
+ const inGroupOnly = get(after, 'alpha2 → alpha3'); // R53 1.3×
85
+ const externalTouch = get(after, 'alpha1 → beta'); // R40 1.7×
86
+ const externalAway = get(after, 'alpha2 → beta'); // R40 0.35×
87
+
88
+ const results = {
89
+ baselineAllEqual: Object.values(before).every(o => Math.abs(o - baseRef) < 0.001),
90
+ exactTouchBrightest: exactTouch > baseRef * 1.5,
91
+ inGroupBoosted: inGroupOnly > baseRef * 1.15 && inGroupOnly < exactTouch,
92
+ externalEndpointStillBright: externalTouch > baseRef * 1.5,
93
+ externalDims: externalAway < baseRef * 0.5,
94
+ hierarchyOrdered: exactTouch > inGroupOnly && inGroupOnly > externalAway,
95
+ };
96
+ const ok = Object.values(results).every(Boolean);
97
+ console.log(`${ok ? '✅' : '❌'} in-group edge boost:`, JSON.stringify(results),
98
+ `\n baseline=${baseRef}`,
99
+ `\n α1→α2 (exact)=${exactTouch}`,
100
+ `\n α2→α3 (in-group)=${inGroupOnly}`,
101
+ `\n α1→β (exact, out)=${externalTouch}`,
102
+ `\n α2→β (leaving)=${externalAway}`);
103
+ process.exit(ok ? 0 : 1);