@sleep2agi/agent-network-dashboard 0.4.6-preview.14 → 0.4.6-preview.141

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 (345) 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/05b69-7w4fvbn.css +2 -0
  184. package/.next/static/chunks/05uk96gc~9mni.js +1 -0
  185. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  186. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  187. package/.next/static/chunks/0n30l6ta_rwr6.js +7 -0
  188. package/.next/static/chunks/0n7d-22onq9b..js +1 -0
  189. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  190. package/.next/static/chunks/0uuulzr2axmob.js +4 -0
  191. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  192. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  193. package/.next/static/chunks/17t-f~8myhwyf.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 +96 -0
  202. package/app/components/ServersDrawer.tsx +64 -27
  203. package/app/components/TopoGraph.tsx +2685 -192
  204. package/app/components/utils.ts +8 -7
  205. package/app/globals.css +27 -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-hot-test.mjs +115 -0
  231. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  232. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  233. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  234. package/scripts/topo-edge-focus-test.mjs +113 -0
  235. package/scripts/topo-edge-hover-test.mjs +93 -0
  236. package/scripts/topo-edge-pill-test.mjs +105 -0
  237. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  238. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  239. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  240. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  241. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  242. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  243. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  244. package/scripts/topo-fit-key-test.mjs +106 -0
  245. package/scripts/topo-freshness-chip-test.mjs +74 -0
  246. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  247. package/scripts/topo-group-count-test.mjs +68 -0
  248. package/scripts/topo-group-hover-test.mjs +82 -0
  249. package/scripts/topo-group-label-hover-test.mjs +119 -0
  250. package/scripts/topo-group-label-tint-test.mjs +99 -0
  251. package/scripts/topo-group-label-title-test.mjs +83 -0
  252. package/scripts/topo-group-pin-test.mjs +119 -0
  253. package/scripts/topo-group-status-pips-test.mjs +103 -0
  254. package/scripts/topo-groupbox-live-test.mjs +100 -0
  255. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  256. package/scripts/topo-groupbox-test.mjs +4 -2
  257. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  258. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  259. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  260. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  261. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  262. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  263. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  264. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  265. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  266. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  267. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  268. package/scripts/topo-hub-busyness-test.mjs +100 -0
  269. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  270. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  271. package/scripts/topo-hub-title-test.mjs +93 -0
  272. package/scripts/topo-hub-working-count-test.mjs +99 -0
  273. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  274. package/scripts/topo-label-lift-test.mjs +119 -0
  275. package/scripts/topo-label-scale-test.mjs +78 -0
  276. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  277. package/scripts/topo-layout-key-test.mjs +91 -0
  278. package/scripts/topo-legend-count-test.mjs +88 -0
  279. package/scripts/topo-legend-hover-test.mjs +113 -0
  280. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  281. package/scripts/topo-legend-pin-test.mjs +128 -0
  282. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  283. package/scripts/topo-minimap-test.mjs +68 -0
  284. package/scripts/topo-mobile-chips-test.mjs +103 -0
  285. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  286. package/scripts/topo-node-title-test.mjs +101 -0
  287. package/scripts/topo-offline-dim-test.mjs +70 -0
  288. package/scripts/topo-orbit-busyness-test.mjs +96 -0
  289. package/scripts/topo-overlap-test.mjs +32 -5
  290. package/scripts/topo-pan-cursor-test.mjs +62 -0
  291. package/scripts/topo-panel-elevation-test.mjs +76 -0
  292. package/scripts/topo-particle-stagger-test.mjs +82 -0
  293. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  294. package/scripts/topo-pin-intersection-test.mjs +121 -0
  295. package/scripts/topo-pin-persistence-test.mjs +105 -0
  296. package/scripts/topo-popover-meta-test.mjs +78 -0
  297. package/scripts/topo-pressure-bar-test.mjs +69 -0
  298. package/scripts/topo-pressure-hover-test.mjs +123 -0
  299. package/scripts/topo-pressure-pin-test.mjs +119 -0
  300. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  301. package/scripts/topo-pulse-rate-test.mjs +71 -0
  302. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  303. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  304. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  305. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  306. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  307. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  308. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  309. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  310. package/scripts/topo-recent-row-ts-test.mjs +92 -0
  311. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  312. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  313. package/scripts/topo-reduced-motion-test.mjs +77 -0
  314. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  315. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  316. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  317. package/scripts/topo-tier-ring-test.mjs +69 -0
  318. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  319. package/scripts/topo-tier-stagger-test.mjs +89 -0
  320. package/scripts/topo-tz-ghost-test.mjs +69 -0
  321. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  322. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  323. package/scripts/topo-vendor-pill-test.mjs +125 -0
  324. package/scripts/topo-vendor-pin-test.mjs +125 -0
  325. package/scripts/topo-wheel-zoom-test.mjs +79 -0
  326. package/scripts/topo-working-halo-breath-test.mjs +86 -0
  327. package/scripts/topo-zoom-keys-test.mjs +89 -0
  328. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  329. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  330. package/.next/static/chunks/02zwa_ptoy2yo.js +0 -1
  331. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  332. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  333. package/.next/static/chunks/0fqy_0wj_1xa7.js +0 -7
  334. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  335. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  336. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  337. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  338. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  339. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  340. package/.next/static/chunks/13vw-ubg-9pjp.css +0 -2
  341. package/.next/static/chunks/16az6n8n64jfa.js +0 -1
  342. package/.next/static/chunks/17pn74kydl5uf.js +0 -1
  343. /package/.next/static/{JubSBXTiy21sqFZKJStnh → giNWS4tIwfQsKvMuZsBnM}/_buildManifest.js +0 -0
  344. /package/.next/static/{JubSBXTiy21sqFZKJStnh → giNWS4tIwfQsKvMuZsBnM}/_clientMiddlewareManifest.js +0 -0
  345. /package/.next/static/{JubSBXTiy21sqFZKJStnh → giNWS4tIwfQsKvMuZsBnM}/_ssgManifest.js +0 -0
@@ -0,0 +1,95 @@
1
+ /* Round 105 verification: legend rows in the SVG panel pick up a
2
+ * row-background tint on hover and pin, mirroring R104's treatment
3
+ * on the recent-signal panel. The tint uses the ROW'S OWN swatch
4
+ * colour (working=green, idle=teal, offline=slate) so the user's
5
+ * eye associates the highlight with its swatch instead of a generic
6
+ * accent.
7
+ *
8
+ * Verifies idle / hover / pinned states via the new data-legend-
9
+ * row-tinted attribute + actual fill/opacity. Pin is stronger than
10
+ * hover; idle is transparent.
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
+
18
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try {
22
+ localStorage.setItem('anet-theme', 'cyber');
23
+ sessionStorage.setItem('anet_v3_auth', '1');
24
+ sessionStorage.removeItem('anet-topo-pinned-status');
25
+ } catch {}
26
+ });
27
+
28
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
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 mk = (alias, status) => ({
34
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
35
+ network_id: nid, project_dir: null,
36
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
37
+ });
38
+ await route.fulfill({ response: r, json: { ...b, sessions: [
39
+ mk('wkr1', 'working'),
40
+ mk('idl1', 'idle'),
41
+ mk('off1', 'offline'),
42
+ ] } });
43
+ });
44
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
45
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
46
+ const page = await ctx.newPage();
47
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
48
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
49
+ await page.waitForTimeout(400);
50
+
51
+ const readRow = (key) => page.evaluate((k) => {
52
+ const g = document.querySelector(`[data-legend-status="${k}"]`);
53
+ if (!g) return null;
54
+ const rect = g.querySelector('rect');
55
+ return {
56
+ tinted: rect?.getAttribute('data-legend-row-tinted') || '',
57
+ fill: rect?.getAttribute('fill') || '',
58
+ opacity: rect?.getAttribute('opacity') || '',
59
+ };
60
+ }, key);
61
+
62
+ const before = await Promise.all(['working', 'idle', 'offline'].map(readRow));
63
+
64
+ // Hover working.
65
+ await page.locator('[data-legend-status="working"]').hover();
66
+ await page.waitForTimeout(250);
67
+ const onHoverWorking = await Promise.all(['working', 'idle', 'offline'].map(readRow));
68
+
69
+ // Move away — clears hover.
70
+ await page.mouse.move(10, 10);
71
+ await page.waitForTimeout(250);
72
+
73
+ // Pin idle.
74
+ await page.locator('[data-legend-status="idle"]').click();
75
+ await page.waitForTimeout(250);
76
+ const onPinIdle = await Promise.all(['working', 'idle', 'offline'].map(readRow));
77
+
78
+ await browser.close();
79
+
80
+ const opacity = (s) => parseFloat(s) || 0;
81
+ const results = {
82
+ before_allTransparent: before.every(r => r?.tinted === 'none' && r?.fill === 'transparent'),
83
+ hoverWorking_workingTinted: onHoverWorking[0]?.tinted === 'hover' && onHoverWorking[0]?.fill !== 'transparent',
84
+ hoverWorking_othersUnchanged: onHoverWorking.slice(1).every(r => r?.tinted === 'none'),
85
+ hoverWorking_opacityLow: opacity(onHoverWorking[0]?.opacity || '') > 0 && opacity(onHoverWorking[0]?.opacity || '') < 0.2,
86
+ pinIdle_idlePinned: onPinIdle[1]?.tinted === 'pinned' && onPinIdle[1]?.fill !== 'transparent',
87
+ pinIdle_pinStrongerThanHover: opacity(onPinIdle[1]?.opacity || '') > opacity(onHoverWorking[0]?.opacity || ''),
88
+ pinIdle_otherRowsClean: onPinIdle.filter((_, i) => i !== 1).every(r => r?.tinted === 'none'),
89
+ };
90
+ const ok = Object.values(results).every(Boolean);
91
+ console.log(`${ok ? '✅' : '❌'} legend-row tint:`, JSON.stringify(results),
92
+ `\n before=`, before,
93
+ `\n onHoverWorking=`, onHoverWorking,
94
+ `\n onPinIdle=`, onPinIdle);
95
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,68 @@
1
+ /* Round 30 verification: minimap.
2
+ * - not mounted at default view (1×, centered)
3
+ * - mounted when zoomed in (one node dot per session, viewport rect)
4
+ * - click recenters the canvas (view.x / view.y change) */
5
+ import { chromium } from 'playwright';
6
+ import { readFileSync } from 'node:fs';
7
+
8
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
9
+ const browser = await chromium.launch({ headless: true });
10
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
11
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
12
+ await ctx.addInitScript(() => {
13
+ try {
14
+ localStorage.setItem('anet-theme', 'cyber');
15
+ localStorage.setItem('anet-topo-layout', 'grid');
16
+ localStorage.setItem('anet-topo-view', JSON.stringify({ zoom: 1, x: 0, y: 0 }));
17
+ sessionStorage.setItem('anet_v3_auth', '1');
18
+ } catch {}
19
+ });
20
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
21
+ await ctx.route('**/api/hub/status*', async (route) => {
22
+ const r = await route.fetch();
23
+ const b = await r.json();
24
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
25
+ const sessions = ['A站', 'B站', 'C站', 'D站', 'E站', 'F站'].flatMap(g =>
26
+ Array.from({ length: 3 }, (_, i) => ({
27
+ alias: `${g}${i + 1}`, status: 'idle', network_id: nid, project_dir: null,
28
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
29
+ })),
30
+ );
31
+ await route.fulfill({ response: r, json: { ...b, sessions } });
32
+ });
33
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
34
+ const page = await ctx.newPage();
35
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
36
+ await page.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 30000 });
37
+ await page.waitForTimeout(500);
38
+
39
+ const minimap = '[data-topo-minimap]';
40
+ const notMountedAtDefault = (await page.locator(minimap).count()) === 0;
41
+
42
+ // Zoom in via `+` twice → view changes → minimap mounts
43
+ await page.keyboard.press('=');
44
+ await page.keyboard.press('=');
45
+ await page.waitForTimeout(200);
46
+ const mountedAfterZoom = (await page.locator(minimap).count()) === 1;
47
+ const dotCount = await page.locator(`${minimap} svg circle`).count();
48
+ const hasViewportRect = await page.locator(`${minimap} svg rect`).count() > 0;
49
+
50
+ // Click on the minimap's far left → expect view.x to become more positive (canvas pans right)
51
+ const beforeView = await page.evaluate(() => JSON.parse(localStorage.getItem('anet-topo-view')));
52
+ const mmBox = await page.locator(minimap).boundingBox();
53
+ await page.mouse.click(mmBox.x + 10, mmBox.y + mmBox.height / 2);
54
+ await page.waitForTimeout(200);
55
+ const afterView = await page.evaluate(() => JSON.parse(localStorage.getItem('anet-topo-view')));
56
+ const clickRecenters = afterView.x !== beforeView.x || afterView.y !== beforeView.y;
57
+
58
+ await browser.close();
59
+ const results = {
60
+ notMountedAtDefault,
61
+ mountedAfterZoom,
62
+ oneDotPerSession: dotCount === 18, // 6 groups × 3 = 18 nodes
63
+ viewportRectPresent: hasViewportRect,
64
+ clickRecenters,
65
+ };
66
+ const ok = Object.values(results).every(Boolean);
67
+ console.log(`${ok ? '✅' : '❌'} minimap:`, JSON.stringify(results), `dots=${dotCount} view before=`, beforeView, `after=`, afterView);
68
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,103 @@
1
+ /* Round 47 verification: mobile chip collapse.
2
+ * - At narrow viewport (375px), pressure / vendor-dist / active-links /
3
+ * freshness chips are display:none; only essential chips render.
4
+ * - At sm+ (≥640px), the full chip row is visible.
5
+ * - Essential chips (Ring/Grid toggle + working + online) render at both. */
6
+ import { chromium } from 'playwright';
7
+ import { readFileSync } from 'node:fs';
8
+
9
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
10
+ const browser = await chromium.launch({ headless: true });
11
+
12
+ async function probe(width) {
13
+ const ctx = await browser.newContext({ viewport: { width, height: 900 } });
14
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
15
+ await ctx.addInitScript(() => {
16
+ try {
17
+ localStorage.setItem('anet-theme', 'cyber');
18
+ localStorage.setItem('anet-topo-layout', 'grid');
19
+ sessionStorage.setItem('anet_v3_auth', '1');
20
+ } catch {}
21
+ });
22
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
23
+ await ctx.route('**/api/hub/status*', async (route) => {
24
+ const r = await route.fetch();
25
+ const b = await r.json();
26
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
27
+ const sessions = [
28
+ { alias: 'wkr', status: 'working', network_id: nid, project_dir: null,
29
+ model: 'claude-opus-4', runtime: 'cli-claude-code',
30
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh },
31
+ { alias: 'idl', status: 'idle', network_id: nid, project_dir: null,
32
+ model: 'gpt-5', runtime: 'codex-cli',
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh },
34
+ ];
35
+ await route.fulfill({ response: r, json: { ...b, sessions } });
36
+ });
37
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
38
+ { from_alias: 'wkr', to_alias: 'idl', content: 'm', created_at: new Date().toISOString() },
39
+ ] } }));
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
+ // The Overview page auto-hides the topology when window.innerWidth < 1024
44
+ // (the lg breakpoint). On narrow viewports the user clicks "Show
45
+ // Topology" to surface it; mimic that here so the chip-row tests reach
46
+ // the actual code path. Wait for SWR to deliver sessions first so the
47
+ // button renders before we look for it.
48
+ await page.waitForTimeout(1500);
49
+ const showBtn = page.locator('button', { hasText: /Show Topology/i });
50
+ if (await showBtn.count() > 0) await showBtn.first().click();
51
+ await page.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 30000 });
52
+ await page.waitForTimeout(600);
53
+ const visible = (sel) => page.evaluate(s => {
54
+ const el = document.querySelector(s);
55
+ if (!el) return null;
56
+ const r = el.getBoundingClientRect();
57
+ return r.width > 0 && r.height > 0;
58
+ }, sel);
59
+ const result = {
60
+ workingChipVisible: await page.evaluate(() => {
61
+ // "{N} working" chip — find the green-tinted one
62
+ const spans = [...document.querySelectorAll('span')];
63
+ const chip = spans.find(s => /^\d+ working$/.test((s.textContent || '').trim()));
64
+ if (!chip) return false;
65
+ const r = chip.getBoundingClientRect();
66
+ return r.width > 0 && r.height > 0;
67
+ }),
68
+ onlineChipVisible: await page.evaluate(() => {
69
+ const spans = [...document.querySelectorAll('span')];
70
+ const chip = spans.find(s => /^\d+ online$/.test((s.textContent || '').trim()));
71
+ if (!chip) return false;
72
+ const r = chip.getBoundingClientRect();
73
+ return r.width > 0 && r.height > 0;
74
+ }),
75
+ pressureVisible: await visible('[data-fleet-pressure]'),
76
+ activeLinksVisible: await page.evaluate(() => {
77
+ const spans = [...document.querySelectorAll('span')];
78
+ const chip = spans.find(s => /active link/.test(s.textContent || ''));
79
+ if (!chip) return false;
80
+ const r = chip.getBoundingClientRect();
81
+ return r.width > 0 && r.height > 0;
82
+ }),
83
+ freshnessVisible: await visible('[data-freshness-chip]'),
84
+ };
85
+ await ctx.close();
86
+ return result;
87
+ }
88
+
89
+ const mobile = await probe(375);
90
+ const desktop = await probe(900);
91
+ await browser.close();
92
+
93
+ const results = {
94
+ mobileEssentialsVisible: mobile.workingChipVisible && mobile.onlineChipVisible,
95
+ mobilePressureHidden: mobile.pressureVisible === false,
96
+ mobileActiveLinksHidden: mobile.activeLinksVisible === false,
97
+ mobileFreshnessHidden: mobile.freshnessVisible === false,
98
+ desktopAllVisible: desktop.workingChipVisible && desktop.onlineChipVisible && desktop.pressureVisible && desktop.activeLinksVisible && desktop.freshnessVisible,
99
+ };
100
+ const ok = Object.values(results).every(Boolean);
101
+ console.log(`${ok ? '✅' : '❌'} mobile chips:`, JSON.stringify(results),
102
+ `\n mobile=`, mobile, `\n desktop=`, desktop);
103
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,85 @@
1
+ /* Round 51 verification: hovered node <g> lifts -2px via CSS transform.
2
+ * - At rest, every node's CSS transform is "none" (or matrix with 0 ty).
3
+ * - On hover, the hovered <g> reports `translateY(-2px)` (matrix(1,0,0,1,0,-2))
4
+ * via getComputedStyle.transform, while siblings stay at identity.
5
+ * - Release: returns to identity.
6
+ * - With prefers-reduced-motion forced, NO lift even on hover.
7
+ */
8
+ import { chromium } from 'playwright';
9
+ import { readFileSync } from 'node:fs';
10
+
11
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
12
+ const browser = await chromium.launch({ headless: true });
13
+
14
+ async function probe(reducedMotion) {
15
+ const ctx = await browser.newContext({
16
+ viewport: { width: 1280, height: 900 },
17
+ reducedMotion: reducedMotion ? 'reduce' : 'no-preference',
18
+ });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try {
22
+ localStorage.setItem('anet-theme', 'cyber');
23
+ localStorage.setItem('anet-topo-layout', 'ring');
24
+ sessionStorage.setItem('anet_v3_auth', '1');
25
+ } catch {}
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 sessions = ['alpha', 'beta', 'gamma'].map(a => ({
33
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ }));
36
+ await route.fulfill({ response: r, json: { ...b, sessions } });
37
+ });
38
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
39
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
40
+ const page = await ctx.newPage();
41
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
42
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
43
+ await page.waitForTimeout(500);
44
+
45
+ const readTy = (alias) => page.evaluate(a => {
46
+ const el = document.querySelector(`g[data-node="${a}"]`);
47
+ if (!el) return null;
48
+ const m = getComputedStyle(el).transform;
49
+ if (!m || m === 'none') return 0;
50
+ // matrix(a, b, c, d, tx, ty) — extract ty
51
+ const parts = m.match(/matrix\(([^)]+)\)/);
52
+ if (!parts) return null;
53
+ const arr = parts[1].split(',').map(s => parseFloat(s.trim()));
54
+ return arr[5] ?? null;
55
+ }, alias);
56
+
57
+ const restAlpha = await readTy('alpha');
58
+ const restBeta = await readTy('beta');
59
+ await page.locator('g[data-node="alpha"]').hover({ force: true });
60
+ await page.waitForTimeout(300);
61
+ const hoveredAlpha = await readTy('alpha');
62
+ const hoveredBeta = await readTy('beta');
63
+ await page.mouse.move(10, 10);
64
+ await page.waitForTimeout(300);
65
+ const releasedAlpha = await readTy('alpha');
66
+
67
+ await ctx.close();
68
+ return { restAlpha, restBeta, hoveredAlpha, hoveredBeta, releasedAlpha };
69
+ }
70
+
71
+ const normal = await probe(false);
72
+ const reduced = await probe(true);
73
+ await browser.close();
74
+
75
+ const results = {
76
+ normal_restAtZero: normal.restAlpha === 0 && normal.restBeta === 0,
77
+ normal_hoveredLifts: normal.hoveredAlpha === -2,
78
+ normal_siblingStays: normal.hoveredBeta === 0,
79
+ normal_released: normal.releasedAlpha === 0,
80
+ reduced_noLift: reduced.hoveredAlpha === 0,
81
+ };
82
+ const ok = Object.values(results).every(Boolean);
83
+ console.log(`${ok ? '✅' : '❌'} node hover lift:`, JSON.stringify(results),
84
+ `\n normal=`, normal, `\n reduced=`, reduced);
85
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,101 @@
1
+ /* Round 98 verification: node <title> tooltips now include status,
2
+ * group membership (when applicable), and per-node flow counts
3
+ * (inbound/outbound) alongside the existing identity/cwd/last-seen
4
+ * lines. Same info-density spirit as R97 pill tooltips — hover any
5
+ * node and the answer is right there.
6
+ *
7
+ * Fleet:
8
+ * - alpha1 (working, claude) + alpha2 (idle, claude) → alpha group of 2
9
+ * - solo (working, gpt-5) — no group (singleton)
10
+ * - off1 (offline, claude) — has last_seen_at older
11
+ *
12
+ * Flows:
13
+ * alpha1 → solo × 3
14
+ * solo → alpha1 × 1
15
+ * ⇒ alpha1: 1 in / 3 out; solo: 3 in / 1 out; alpha2/off1: no flows
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: 1400, height: 1500 } });
24
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
25
+ await ctx.addInitScript(() => {
26
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
27
+ });
28
+
29
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
30
+ const stale = new Date(Date.now() - 20 * 60 * 1000).toISOString();
31
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ const mk = (alias, status, model, ts) => ({
36
+ alias, status, model, runtime: 'cli-claude-code',
37
+ network_id: nid, project_dir: null,
38
+ created_at: ts, updated_at: ts, last_seen_at: ts,
39
+ });
40
+ const sessions = [
41
+ mk('alpha1', 'working', 'claude-opus-4', fresh),
42
+ mk('alpha2', 'idle', 'claude-opus-4', fresh),
43
+ mk('solo', 'working', 'gpt-5', fresh),
44
+ mk('off1', 'offline', 'claude-opus-4', stale),
45
+ ];
46
+ await route.fulfill({ response: r, json: { ...b, sessions } });
47
+ });
48
+
49
+ const now = Date.now();
50
+ const mkMsg = (id, from_alias, to_alias, ageMs) => ({
51
+ id, from_alias, to_alias, content: 'hi', network_id: 'default',
52
+ created_at: new Date(now - ageMs).toISOString(),
53
+ });
54
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
55
+ mkMsg('m1', 'alpha1', 'solo', 30 * 1000),
56
+ mkMsg('m2', 'alpha1', 'solo', 60 * 1000),
57
+ mkMsg('m3', 'alpha1', 'solo', 90 * 1000),
58
+ mkMsg('m4', 'solo', 'alpha1', 45 * 1000),
59
+ ] } }));
60
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
61
+ const page = await ctx.newPage();
62
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
63
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
64
+ await page.waitForTimeout(400);
65
+
66
+ const titles = await page.evaluate(() => {
67
+ const out = {};
68
+ for (const alias of ['alpha1', 'alpha2', 'solo', 'off1']) {
69
+ const g = document.querySelector(`g[data-node="${alias}"]`);
70
+ out[alias] = g?.querySelector('title')?.textContent || '';
71
+ }
72
+ return out;
73
+ });
74
+
75
+ await browser.close();
76
+
77
+ const results = {
78
+ // Status line is the heading.
79
+ alpha1_statusHeading: /^alpha1 · working/m.test(titles.alpha1),
80
+ alpha2_statusHeading: /^alpha2 · idle/m.test(titles.alpha2),
81
+ solo_statusHeading: /^solo · working/m.test(titles.solo),
82
+ off1_statusHeading: /^off1 · offline/m.test(titles.off1),
83
+ // Group line only on multi-member band.
84
+ alpha1_groupShown: /group: alpha · 2/.test(titles.alpha1),
85
+ alpha2_groupShown: /group: alpha · 2/.test(titles.alpha2),
86
+ solo_groupSkipped: !/group:/.test(titles.solo),
87
+ // Flow counts on participating nodes only.
88
+ alpha1_flowsCorrect: /flows: 1 in \/ 3 out/.test(titles.alpha1),
89
+ solo_flowsCorrect: /flows: 3 in \/ 1 out/.test(titles.solo),
90
+ alpha2_noFlows: !/flows:/.test(titles.alpha2),
91
+ off1_lastSeen: /last seen:/.test(titles.off1),
92
+ // Identity line still present.
93
+ alpha1_hasIdentity: /claude/i.test(titles.alpha1),
94
+ };
95
+ const ok = Object.values(results).every(Boolean);
96
+ console.log(`${ok ? '✅' : '❌'} node titles:`, JSON.stringify(results),
97
+ `\n alpha1:\n${titles.alpha1}`,
98
+ `\n alpha2:\n${titles.alpha2}`,
99
+ `\n solo:\n${titles.solo}`,
100
+ `\n off1:\n${titles.off1}`);
101
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,70 @@
1
+ /* Round 17 verification: offline nodes render at opacity 0.6 at rest,
2
+ * online stay at 1, and the chat-focused node (even if offline) jumps
3
+ * to 1 when its ChatPopover opens. */
4
+ import { chromium } from 'playwright';
5
+ import { readFileSync } from 'node:fs';
6
+
7
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
8
+ const browser = await chromium.launch({ headless: true });
9
+
10
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
11
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
12
+ await ctx.addInitScript(() => {
13
+ try {
14
+ localStorage.setItem('anet-theme', 'cyber');
15
+ localStorage.removeItem('anet-brand');
16
+ localStorage.removeItem('anet-topo-view');
17
+ localStorage.setItem('anet-topo-layout', 'grid');
18
+ sessionStorage.setItem('anet_v3_auth', '1');
19
+ } catch {}
20
+ });
21
+ // fresh-enough so the ghost-age-out filter doesn't drop the offline node
22
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
23
+ await ctx.route('**/api/hub/status*', async (route) => {
24
+ const r = await route.fetch();
25
+ const b = await r.json();
26
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
27
+ const sessions = [
28
+ { alias: 'on1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
29
+ { alias: 'on2', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
30
+ { alias: 'off1', status: 'offline', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
31
+ { alias: 'off2', status: 'offline', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
32
+ ];
33
+ await route.fulfill({ response: r, json: { ...b, sessions } });
34
+ });
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.waitForFunction(() => {
39
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
40
+ return !!svg && svg.querySelectorAll('g[data-node]').length === 4;
41
+ }, { timeout: 30000 }).catch(() => {});
42
+ await page.waitForTimeout(500);
43
+
44
+ const opacityOf = (alias) => page.$eval(`g[data-node="${alias}"]`, el =>
45
+ parseFloat(getComputedStyle(el).opacity || el.style.opacity || '1'));
46
+
47
+ const restOn1 = await opacityOf('on1');
48
+ const restOn2 = await opacityOf('on2');
49
+ const restOff1 = await opacityOf('off1');
50
+ const restOff2 = await opacityOf('off2');
51
+
52
+ // Click an offline node — the chat popover should open and that node
53
+ // should jump to full opacity.
54
+ await page.locator('g[data-node="off1"]').click();
55
+ await page.waitForTimeout(300);
56
+ const focusedOff1 = await opacityOf('off1');
57
+ const otherOff2 = await opacityOf('off2');
58
+
59
+ await browser.close();
60
+ const results = {
61
+ onlineFullOpacity: Math.abs(restOn1 - 1) < 0.01 && Math.abs(restOn2 - 1) < 0.01,
62
+ offlineDimmedAtRest: Math.abs(restOff1 - 0.6) < 0.01 && Math.abs(restOff2 - 0.6) < 0.01,
63
+ chatFocusOfflineRestoresOpacity: Math.abs(focusedOff1 - 1) < 0.01,
64
+ otherOfflineStaysDimmed: Math.abs(otherOff2 - 0.6) < 0.01,
65
+ };
66
+ const ok = Object.values(results).every(Boolean);
67
+ console.log(`${ok ? '✅' : '❌'} offline dim:`, JSON.stringify(results),
68
+ `rest=on1:${restOn1} on2:${restOn2} off1:${restOff1} off2:${restOff2}`,
69
+ `clicked off1=${focusedOff1} off2=${otherOff2}`);
70
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,96 @@
1
+ /* Round 131 verification: outer-ring orbit period buckets on
2
+ * workingCount, mirroring R84's hub-breath cadence. Idle fleet
3
+ * keeps the original 16s "calm sweep"; as work piles up the
4
+ * orbit accelerates to 14 / 12 / 10s. Both motion layers now
5
+ * read the same underlying "is the network busy" signal.
6
+ *
7
+ * Bucket boundaries identical to R84 (line ~2702):
8
+ * workingCount === 0 → bucket 0, dur 16s
9
+ * workingCount in [1, 2] → bucket 1, dur 14s
10
+ * workingCount in [3, 5] → bucket 2, dur 12s
11
+ * workingCount >= 6 → bucket 3, dur 10s
12
+ *
13
+ * R50 only renders on cyber theme (light is skipped to keep the
14
+ * white surface clean). All probes use cyber.
15
+ *
16
+ * Probe fleet sizes pick one representative per bucket.
17
+ */
18
+ import { chromium } from 'playwright';
19
+ import { readFileSync } from 'node:fs';
20
+
21
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
22
+
23
+ async function probe(workingCount, totalCount) {
24
+ const browser = await chromium.launch({ headless: true });
25
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
26
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
27
+ await ctx.addInitScript(() => {
28
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
29
+ });
30
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
31
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ const sessions = [];
36
+ for (let i = 0; i < totalCount; i++) {
37
+ sessions.push({
38
+ alias: `node${i}`,
39
+ status: i < workingCount ? 'working' : 'idle',
40
+ model: 'claude-opus-4', runtime: 'cli-claude-code',
41
+ network_id: nid, project_dir: null,
42
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
43
+ });
44
+ }
45
+ await route.fulfill({ response: r, json: { ...b, sessions } });
46
+ });
47
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
48
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
49
+
50
+ const page = await ctx.newPage();
51
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
52
+ await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n, totalCount, { timeout: 30000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ const out = await page.evaluate(() => {
56
+ const orbits = [...document.querySelectorAll('[data-topo-orbit-bucket]')];
57
+ return orbits.map(g => ({
58
+ bucket: g.getAttribute('data-topo-orbit-bucket'),
59
+ dur: g.getAttribute('data-topo-orbit-dur'),
60
+ animDur: g.querySelector('animateTransform')?.getAttribute('dur'),
61
+ }));
62
+ });
63
+
64
+ await browser.close();
65
+ return out;
66
+ }
67
+
68
+ // Pick one from each bucket
69
+ const b0 = await probe(0, 4); // workingCount=0 → bucket 0, dur 16
70
+ const b1 = await probe(2, 4); // workingCount=2 → bucket 1, dur 14
71
+ const b2 = await probe(4, 5); // workingCount=4 → bucket 2, dur 12
72
+ const b3 = await probe(7, 8); // workingCount=7 → bucket 3, dur 10
73
+
74
+ const allConsistent = (orbits, bucket, dur) =>
75
+ orbits.length === 4 &&
76
+ orbits.every(o => o.bucket === String(bucket) && o.dur === String(dur) && o.animDur === `${dur}s`);
77
+
78
+ const results = {
79
+ b0_fourOrbits: b0.length === 4,
80
+ b0_bucket0_16s: allConsistent(b0, 0, 16),
81
+ b1_bucket1_14s: allConsistent(b1, 1, 14),
82
+ b2_bucket2_12s: allConsistent(b2, 2, 12),
83
+ b3_bucket3_10s: allConsistent(b3, 3, 10),
84
+ // dur strictly decreases as busy increases
85
+ dur_monotonic:
86
+ Number(b0[0]?.dur) > Number(b1[0]?.dur) &&
87
+ Number(b1[0]?.dur) > Number(b2[0]?.dur) &&
88
+ Number(b2[0]?.dur) > Number(b3[0]?.dur),
89
+ };
90
+ const ok = Object.values(results).every(Boolean);
91
+ console.log(`${ok ? '✅' : '❌'} orbit busyness:`, JSON.stringify(results),
92
+ `\n b0(0w/4)=`, b0,
93
+ `\n b1(2w/4)=`, b1,
94
+ `\n b2(4w/5)=`, b2,
95
+ `\n b3(7w/8)=`, b3);
96
+ process.exit(ok ? 0 : 1);