@sleep2agi/agent-network-dashboard 0.4.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +31 -31
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/routes-manifest.json +16 -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/server/[hostname]/agents/route/app-paths-manifest.json +3 -0
  34. package/.next/server/app/api/hub/server/[hostname]/agents/route/build-manifest.json +9 -0
  35. package/.next/server/app/api/hub/server/[hostname]/agents/route/server-reference-manifest.json +4 -0
  36. package/.next/server/app/api/hub/server/[hostname]/agents/route.js +7 -0
  37. package/.next/server/app/api/hub/server/[hostname]/agents/route.js.map +5 -0
  38. package/.next/server/app/api/hub/server/[hostname]/agents/route.js.nft.json +1 -0
  39. package/.next/server/app/api/hub/server/[hostname]/agents/route_client-reference-manifest.js +3 -0
  40. package/.next/server/app/api/hub/server/[hostname]/health/route/app-paths-manifest.json +3 -0
  41. package/.next/server/app/api/hub/server/[hostname]/health/route/build-manifest.json +9 -0
  42. package/.next/server/app/api/hub/server/[hostname]/health/route/server-reference-manifest.json +4 -0
  43. package/.next/server/app/api/hub/server/[hostname]/health/route.js +7 -0
  44. package/.next/server/app/api/hub/server/[hostname]/health/route.js.map +5 -0
  45. package/.next/server/app/api/hub/server/[hostname]/health/route.js.nft.json +1 -0
  46. package/.next/server/app/api/hub/server/[hostname]/health/route_client-reference-manifest.js +3 -0
  47. package/.next/server/app/index.html +2 -2
  48. package/.next/server/app/index.rsc +14 -14
  49. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  50. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  55. package/.next/server/app/login.html +2 -2
  56. package/.next/server/app/login.rsc +14 -14
  57. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  58. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  59. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  60. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  61. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  62. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  63. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  64. package/.next/server/app/logs.html +2 -2
  65. package/.next/server/app/logs.rsc +14 -14
  66. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  67. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  68. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  69. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  70. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  71. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  72. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  73. package/.next/server/app/messages.html +2 -2
  74. package/.next/server/app/messages.rsc +14 -14
  75. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  76. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  77. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  78. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  79. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  80. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  81. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app/node.html +2 -2
  83. package/.next/server/app/node.rsc +14 -14
  84. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  85. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  86. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  87. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  88. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  89. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  90. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  91. package/.next/server/app/nodes.html +2 -2
  92. package/.next/server/app/nodes.rsc +14 -14
  93. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  94. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  95. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  96. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  97. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  98. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  99. package/.next/server/app/page_client-reference-manifest.js +1 -1
  100. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  101. package/.next/server/app/server-logs.html +2 -2
  102. package/.next/server/app/server-logs.rsc +14 -14
  103. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  104. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  105. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  106. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  107. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  108. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  109. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  110. package/.next/server/app/settings/networks.html +2 -2
  111. package/.next/server/app/settings/networks.rsc +14 -14
  112. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  113. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  114. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  115. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  116. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  117. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  118. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  119. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  120. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  121. package/.next/server/app/settings/tokens.html +2 -2
  122. package/.next/server/app/settings/tokens.rsc +14 -14
  123. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  124. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  125. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  126. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  127. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  128. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  129. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  130. package/.next/server/app/settings.html +2 -2
  131. package/.next/server/app/settings.rsc +14 -14
  132. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  133. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  134. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  135. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  136. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  137. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  138. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  139. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  140. package/.next/server/app/tasks.html +2 -2
  141. package/.next/server/app/tasks.rsc +14 -14
  142. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  143. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  144. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  145. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  146. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  147. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  148. package/.next/server/app-paths-manifest.json +2 -0
  149. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js +3 -0
  150. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js.map +1 -0
  151. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js +3 -0
  152. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js.map +1 -0
  153. package/.next/server/chunks/[root-of-the-server]__05nyivw._.js +3 -0
  154. package/.next/server/chunks/[root-of-the-server]__05nyivw._.js.map +1 -0
  155. package/.next/server/chunks/[root-of-the-server]__0floe3h._.js +3 -0
  156. package/.next/server/chunks/[root-of-the-server]__0floe3h._.js.map +1 -0
  157. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +1 -1
  158. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -1
  159. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -2
  160. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  161. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  162. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  163. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  164. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  165. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  166. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  167. package/.next/server/middleware-build-manifest.js +3 -3
  168. package/.next/server/pages/404.html +2 -2
  169. package/.next/server/pages/500.html +1 -1
  170. package/.next/server/server-reference-manifest.js +1 -1
  171. package/.next/server/server-reference-manifest.json +1 -1
  172. package/.next/static/chunks/0aauz~36q5n2a.css +2 -0
  173. package/.next/static/chunks/0f~0kgy2pycyc.js +1 -0
  174. package/.next/static/chunks/0l_~q07bhpkcx.js +7 -0
  175. package/.next/static/chunks/1175-dp18a1e_.js +1 -0
  176. package/.next/static/chunks/1552j0415oeal.js +4 -0
  177. package/.next/trace +2 -2
  178. package/.next/trace-build +1 -1
  179. package/.next/types/routes.d.ts +3 -1
  180. package/.next/types/validator.ts +18 -0
  181. package/app/api/hub/server/[hostname]/agents/route.ts +62 -0
  182. package/app/api/hub/server/[hostname]/health/route.ts +58 -0
  183. package/app/api/hub/servers/route.ts +88 -1
  184. package/app/components/CommandPalette.tsx +107 -0
  185. package/app/components/HelpOverlay.tsx +109 -1
  186. package/app/components/ServersDrawer.tsx +181 -5
  187. package/app/components/TopoGraph.tsx +6409 -286
  188. package/app/globals.css +110 -5
  189. package/app/lib/vendorIdentity.ts +14 -3
  190. package/docs/rfc/v0.10.0-hero3-dashboard-upgrade.md +230 -0
  191. package/package.json +2 -2
  192. package/public/sleep2agi-logo.svg +11 -0
  193. package/public/vendors/claude.svg +11 -0
  194. package/public/vendors/minimax.svg +11 -0
  195. package/public/vendors/openai.svg +12 -0
  196. package/screenshots/r293-p0/after-full.png +0 -0
  197. package/screenshots/r293-p0/after-topo.png +0 -0
  198. package/screenshots/r293-p0/baseline-full.png +0 -0
  199. package/screenshots/r293-p0/baseline-topo.png +0 -0
  200. package/screenshots/r293-p0/logo-integrated-full.png +0 -0
  201. package/screenshots/r293-p0/logo-integrated-topo.png +0 -0
  202. package/screenshots/r293-p0/servers-drawer-v0.10.0-full.png +0 -0
  203. package/screenshots/r293-p0/servers-drawer-v0.10.0.png +0 -0
  204. package/screenshots/r293-p0/wave1-after-full.png +0 -0
  205. package/screenshots/r293-p0/wave1-after-topo.png +0 -0
  206. package/scripts/p0-servers-drawer-screenshot.mjs +99 -0
  207. package/scripts/p0-topo-screenshot.mjs +82 -0
  208. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  209. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  210. package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
  211. package/scripts/topo-active-links-chip-self-hover-test.mjs +171 -0
  212. package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
  213. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  214. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  215. package/scripts/topo-active-pulse-ease-test.mjs +120 -0
  216. package/scripts/topo-brand-logo-transition-test.mjs +94 -0
  217. package/scripts/topo-brand-watermark-test.mjs +97 -0
  218. package/scripts/topo-canvas-aria-test.mjs +108 -0
  219. package/scripts/topo-canvas-brand-fade-test.mjs +87 -0
  220. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  221. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  222. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  223. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  224. package/scripts/topo-chip-focus-test.mjs +95 -0
  225. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  226. package/scripts/topo-chip-row-gap-test.mjs +90 -0
  227. package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
  228. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  229. package/scripts/topo-chip-unit-demotion-test.mjs +93 -0
  230. package/scripts/topo-chiprow-data-weight-test.mjs +94 -0
  231. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  232. package/scripts/topo-chrome-active-weight-test.mjs +104 -0
  233. package/scripts/topo-chrome-focus-ring-unify-test.mjs +100 -0
  234. package/scripts/topo-chrome-gap-test.mjs +73 -0
  235. package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
  236. package/scripts/topo-chrome-inactive-color-test.mjs +90 -0
  237. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  238. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  239. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  240. package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
  241. package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
  242. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  243. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  244. package/scripts/topo-chrome-zoom-readout-weight-test.mjs +81 -0
  245. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  246. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  247. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  248. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  249. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  250. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  251. package/scripts/topo-corner-radius-unify-test.mjs +103 -0
  252. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  253. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  254. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  255. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  256. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  257. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  258. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  259. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  260. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  261. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  262. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  263. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  264. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  265. package/scripts/topo-edge-fadein-test.mjs +106 -0
  266. package/scripts/topo-edge-focus-test.mjs +113 -0
  267. package/scripts/topo-edge-hover-test.mjs +13 -5
  268. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  269. package/scripts/topo-edge-pill-test.mjs +105 -0
  270. package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
  271. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  272. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  273. package/scripts/topo-empty-hint-tracking-test.mjs +84 -0
  274. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  275. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  276. package/scripts/topo-empty-state-recenter-test.mjs +107 -0
  277. package/scripts/topo-empty-subhint-tracking-test.mjs +87 -0
  278. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  279. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  280. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  281. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  282. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  283. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  284. package/scripts/topo-filter-pill-count-tabular-test.mjs +135 -0
  285. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  286. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  287. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  288. package/scripts/topo-freshness-chip-weight-test.mjs +90 -0
  289. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  290. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  291. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  292. package/scripts/topo-group-count-test.mjs +6 -1
  293. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  294. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  295. package/scripts/topo-group-label-hover-test.mjs +119 -0
  296. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  297. package/scripts/topo-group-label-tint-test.mjs +99 -0
  298. package/scripts/topo-group-label-title-test.mjs +83 -0
  299. package/scripts/topo-group-pin-test.mjs +119 -0
  300. package/scripts/topo-group-pip-fadein-test.mjs +104 -0
  301. package/scripts/topo-group-pip-singletier-drop-test.mjs +131 -0
  302. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  303. package/scripts/topo-group-status-pips-test.mjs +103 -0
  304. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  305. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  306. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  307. package/scripts/topo-groupbox-live-test.mjs +100 -0
  308. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  309. package/scripts/topo-groupbox-test.mjs +4 -2
  310. package/scripts/topo-header-gap-test.mjs +94 -0
  311. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  312. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  313. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  314. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  315. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  316. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  317. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  318. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  319. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  320. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  321. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  322. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  323. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  324. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  325. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  326. package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
  327. package/scripts/topo-hub-a11y-test.mjs +112 -0
  328. package/scripts/topo-hub-busyness-test.mjs +100 -0
  329. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  330. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  331. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  332. package/scripts/topo-hub-fadein-test.mjs +88 -0
  333. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  334. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  335. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  336. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  337. package/scripts/topo-hub-title-test.mjs +7 -2
  338. package/scripts/topo-hub-working-count-test.mjs +99 -0
  339. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  340. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  341. package/scripts/topo-kicker-color-test.mjs +93 -0
  342. package/scripts/topo-kicker-tracking-test.mjs +101 -0
  343. package/scripts/topo-kicker-weight-test.mjs +98 -0
  344. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  345. package/scripts/topo-label-lift-test.mjs +5 -1
  346. package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
  347. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  348. package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
  349. package/scripts/topo-layout-toggle-test.mjs +131 -0
  350. package/scripts/topo-layout-trailer-mr-test.mjs +82 -0
  351. package/scripts/topo-legend-compress-test.mjs +107 -0
  352. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  353. package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
  354. package/scripts/topo-legend-count-tabular-test.mjs +87 -0
  355. package/scripts/topo-legend-count-test.mjs +88 -0
  356. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  357. package/scripts/topo-legend-count-weight-test.mjs +86 -0
  358. package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
  359. package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
  360. package/scripts/topo-legend-hover-test.mjs +113 -0
  361. package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
  362. package/scripts/topo-legend-offline-label-simplify-test.mjs +86 -0
  363. package/scripts/topo-legend-panel-count-weight-test.mjs +89 -0
  364. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  365. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  366. package/scripts/topo-legend-pin-test.mjs +128 -0
  367. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  368. package/scripts/topo-legend-row-labels-simplify-test.mjs +80 -0
  369. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  370. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  371. package/scripts/topo-legend-row-title-test.mjs +102 -0
  372. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  373. package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
  374. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  375. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  376. package/scripts/topo-minimap-radius-test.mjs +88 -0
  377. package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
  378. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  379. package/scripts/topo-monogram-font-test.mjs +112 -0
  380. package/scripts/topo-monogram-stroke-test.mjs +101 -0
  381. package/scripts/topo-more-flows-letterspacing-test.mjs +111 -0
  382. package/scripts/topo-node-a11y-test.mjs +107 -0
  383. package/scripts/topo-node-alias-chat-pin-test.mjs +95 -0
  384. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  385. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  386. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  387. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  388. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  389. package/scripts/topo-node-title-peers-test.mjs +107 -0
  390. package/scripts/topo-node-title-test.mjs +101 -0
  391. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  392. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  393. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  394. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  395. package/scripts/topo-panel-count-unit-test.mjs +112 -0
  396. package/scripts/topo-panel-elevation-test.mjs +76 -0
  397. package/scripts/topo-panel-fadein-test.mjs +102 -0
  398. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  399. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  400. package/scripts/topo-panel-rx-test.mjs +90 -0
  401. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  402. package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
  403. package/scripts/topo-panel-title-tracking-test.mjs +78 -0
  404. package/scripts/topo-particle-stagger-test.mjs +82 -0
  405. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  406. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  407. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  408. package/scripts/topo-pin-intersection-test.mjs +121 -0
  409. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  410. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  411. package/scripts/topo-pin-persistence-test.mjs +105 -0
  412. package/scripts/topo-prefix-opacity-test.mjs +107 -0
  413. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  414. package/scripts/topo-pressure-hover-test.mjs +123 -0
  415. package/scripts/topo-pressure-pin-test.mjs +119 -0
  416. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  417. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  418. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  419. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  420. package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
  421. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  422. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  423. package/scripts/topo-recent-panel-count-weight-test.mjs +91 -0
  424. package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
  425. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  426. package/scripts/topo-recent-panel-hot-tabular-test.mjs +113 -0
  427. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  428. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  429. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  430. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  431. package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
  432. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  433. package/scripts/topo-recent-row-count-weight-test.mjs +112 -0
  434. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  435. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  436. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  437. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  438. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  439. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  440. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  441. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  442. package/scripts/topo-recent-row-title-test.mjs +112 -0
  443. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  444. package/scripts/topo-recent-row-ts-tabular-test.mjs +108 -0
  445. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  446. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  447. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  448. package/scripts/topo-reset-spin-test.mjs +120 -0
  449. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  450. package/scripts/topo-spokes-retired-test.mjs +93 -0
  451. package/scripts/topo-starfield-density-test.mjs +89 -0
  452. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  453. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  454. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  455. package/scripts/topo-svg-focus-test.mjs +112 -0
  456. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  457. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  458. package/scripts/topo-tier-guide-dashes-test.mjs +86 -0
  459. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  460. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  461. package/scripts/topo-tier-ring-test.mjs +69 -0
  462. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  463. package/scripts/topo-tier-stagger-test.mjs +89 -0
  464. package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
  465. package/scripts/topo-title-tracking-test.mjs +98 -0
  466. package/scripts/topo-titleblock-gap-test.mjs +86 -0
  467. package/scripts/topo-titleblock-margin-test.mjs +83 -0
  468. package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
  469. package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
  470. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  471. package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
  472. package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
  473. package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
  474. package/scripts/topo-vendor-chip-weight-test.mjs +87 -0
  475. package/scripts/topo-vendor-count-suffix-test.mjs +95 -0
  476. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  477. package/scripts/topo-vendor-pill-test.mjs +125 -0
  478. package/scripts/topo-vendor-pin-test.mjs +125 -0
  479. package/scripts/topo-view-smooth-test.mjs +131 -0
  480. package/scripts/topo-watermark-tracking-test.mjs +99 -0
  481. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  482. package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
  483. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  484. package/scripts/topo-wrapper-radius-test.mjs +73 -0
  485. package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
  486. package/.next/static/chunks/088ouqy7tekty.js +0 -2
  487. package/.next/static/chunks/0hyl7mdapsz38.js +0 -1
  488. package/.next/static/chunks/0mqwn-l7wtcdo.js +0 -7
  489. package/.next/static/chunks/127kw7~wsnyu7.css +0 -2
  490. package/.next/static/chunks/16yfxp9ak2gdm.js +0 -1
  491. /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_buildManifest.js +0 -0
  492. /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_clientMiddlewareManifest.js +0 -0
  493. /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_ssgManifest.js +0 -0
@@ -0,0 +1,177 @@
1
+ /* Round 201 verification: the chip-row "N working" / "N online" chips
2
+ * adopt deeper versions of their own identity colour on hover,
3
+ * mirroring R193 active-links chip pattern.
4
+ *
5
+ * Pre-R201 hover working chip → R55 canvas dim + chip-row highlight,
6
+ * but chip itself stayed at bg-{color}-500/10 — cause silent, effect
7
+ * loud. R201 deepens 10→15 bg and 20→30 border on hover (only when
8
+ * the chip has a target count > 0). The inline transition list is
9
+ * extended to cover background-color + border-color so the swap
10
+ * eases instead of snapping (Tailwind transition-colors on the
11
+ * className alone would be overridden by the existing R180 inline
12
+ * box-shadow transition).
13
+ *
14
+ * Test (cyber dark, 4 working sessions):
15
+ * 1. Working chip: clickable='true', className has hover:bg-green-500/15
16
+ * + hover:border-green-500/30, transition includes 'background-color'
17
+ * + 'border-color' (both 200ms), inline still has 'box-shadow 150ms'.
18
+ * 2. Online chip: same shape with cyan hue.
19
+ * 3. Hover the working chip — wait past 220ms → computed bg
20
+ * shifts to a more-saturated/darker green (RGB-or-OKLab signal).
21
+ * 4. Move away — bg returns to idle (with margin past 220ms).
22
+ * 5. The chip's R55 canvas-effect setHoveredStatus still fires —
23
+ * verified via hoveredStatus change reflected on the working
24
+ * legend row's data-legend-row-lifted attribute.
25
+ */
26
+ import { chromium } from 'playwright';
27
+ import { readFileSync } from 'node:fs';
28
+
29
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
30
+ const browser = await chromium.launch({ headless: true });
31
+
32
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
33
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
34
+ await ctx.addInitScript(() => {
35
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
36
+ });
37
+
38
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
39
+ await ctx.route('**/api/hub/status*', async (route) => {
40
+ const r = await route.fetch();
41
+ const b = await r.json();
42
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
43
+ const mk = (alias) => ({
44
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
45
+ network_id: nid, project_dir: null,
46
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
47
+ });
48
+ await route.fulfill({ response: r, json: { ...b, sessions: [
49
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
50
+ ] } });
51
+ });
52
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
53
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
54
+
55
+ const page = await ctx.newPage();
56
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
57
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
58
+ await page.waitForSelector('[data-working-chip]', { timeout: 10000 });
59
+ await page.waitForTimeout(300);
60
+
61
+ const probeChip = async (selector) => page.evaluate((sel) => {
62
+ const el = document.querySelector(sel);
63
+ if (!el) return null;
64
+ const cs = getComputedStyle(el);
65
+ return {
66
+ className: el.getAttribute('class') || '',
67
+ clickable: el.getAttribute('data-working-chip-clickable') ||
68
+ el.getAttribute('data-online-chip-clickable'),
69
+ bg: cs.backgroundColor,
70
+ borderColor: cs.borderTopColor,
71
+ transition: el.style.transition,
72
+ };
73
+ }, selector);
74
+
75
+ const idleWorking = await probeChip('[data-working-chip]');
76
+ const idleOnline = await probeChip('[data-online-chip]');
77
+
78
+ await page.hover('[data-working-chip]');
79
+ await page.waitForTimeout(250);
80
+ const hoverWorking = await probeChip('[data-working-chip]');
81
+
82
+ // Confirm R55 canvas effect still fires — hoveredStatus='working'
83
+ // lifts the legend working row.
84
+ const workingRowLifted = await page.evaluate(() =>
85
+ document.querySelector('[data-legend-status="working"]')
86
+ ?.getAttribute('data-legend-row-lifted'));
87
+
88
+ await page.mouse.move(0, 0);
89
+ await page.waitForTimeout(280);
90
+ const settledWorking = await probeChip('[data-working-chip]');
91
+
92
+ await page.hover('[data-online-chip]');
93
+ await page.waitForTimeout(250);
94
+ const hoverOnline = await probeChip('[data-online-chip]');
95
+
96
+ await page.mouse.move(0, 0);
97
+ await page.waitForTimeout(280);
98
+ const settledOnline = await probeChip('[data-online-chip]');
99
+
100
+ await browser.close();
101
+
102
+ // Tailwind 4 emits oklab/oklch / color-mix forms. Just check the bg
103
+ // string actually differs idle vs hover, and a brief parser confirms
104
+ // the channel that should grow (green for working, blue/green for cyan).
105
+ // Parse colors including alpha. R201's Tailwind /10 → /15 emits same
106
+ // OKLab L/a/b with stepped-up alpha (0.1 → 0.15); R193's gray→cyan
107
+ // pattern emits a different hue at any alpha. Both are "more saturated
108
+ // against the canvas" from the user's POV. Capture alpha and treat
109
+ // alpha-increase as a valid saturation signal alongside chroma growth.
110
+ function parseColor(s) {
111
+ if (!s) return null;
112
+ let m = s.match(/rgba?\(\s*(\d+)\s*,?\s*(\d+)\s*,?\s*(\d+)(?:\s*[,/]\s*([\d.]+))?/);
113
+ if (m) return { kind: 'rgb', r: +m[1], g: +m[2], b: +m[3], a: m[4] != null ? +m[4] : 1 };
114
+ m = s.match(/oklab\(\s*([\-\d.]+)\s+([\-\d.]+)\s+([\-\d.]+)(?:\s*\/\s*([\d.]+))?/);
115
+ if (m) return { kind: 'oklab', L: +m[1], a: +m[2], b: +m[3], alpha: m[4] != null ? +m[4] : 1 };
116
+ m = s.match(/oklch\(\s*([\-\d.]+)\s+([\-\d.]+)\s+([\-\d.]+)(?:\s*\/\s*([\d.]+))?/);
117
+ if (m) return { kind: 'oklch', L: +m[1], C: +m[2], h: +m[3], alpha: m[4] != null ? +m[4] : 1 };
118
+ return null;
119
+ }
120
+
121
+ // "More saturated against the canvas" = either chroma/hue distance
122
+ // from gray grew OR alpha grew (Tailwind /10 → /15 deepens by alpha).
123
+ const moreSaturated = (idle, hov) => {
124
+ if (!idle || !hov) return false;
125
+ if (idle.kind === 'rgb' && hov.kind === 'rgb') {
126
+ const chromaIdle = Math.max(idle.r, idle.g, idle.b) - Math.min(idle.r, idle.g, idle.b);
127
+ const chromaHov = Math.max(hov.r, hov.g, hov.b) - Math.min(hov.r, hov.g, hov.b);
128
+ return chromaHov > chromaIdle || hov.a > idle.a + 0.01;
129
+ }
130
+ if (idle.kind === 'oklab' && hov.kind === 'oklab') {
131
+ const dIdle = Math.hypot(idle.a, idle.b);
132
+ const dHov = Math.hypot(hov.a, hov.b);
133
+ return dHov > dIdle + 0.005 || hov.alpha > idle.alpha + 0.01;
134
+ }
135
+ if (idle.kind === 'oklch' && hov.kind === 'oklch') {
136
+ return hov.C > idle.C + 0.005 || hov.alpha > idle.alpha + 0.01;
137
+ }
138
+ return false;
139
+ };
140
+
141
+ const idleWorkingC = parseColor(idleWorking?.bg);
142
+ const hoverWorkingC = parseColor(hoverWorking?.bg);
143
+ const idleOnlineC = parseColor(idleOnline?.bg);
144
+ const hoverOnlineC = parseColor(hoverOnline?.bg);
145
+
146
+ const results = {
147
+ working_clickable: idleWorking?.clickable === 'true',
148
+ working_className_has_hover: idleWorking?.className.includes('hover:bg-green-500/15')
149
+ && idleWorking?.className.includes('hover:border-green-500/30'),
150
+ working_transition_includes_bg: /background-color\s+(?:200ms|0\.2s)/.test(idleWorking?.transition || ''),
151
+ working_transition_includes_border: /border-color\s+(?:200ms|0\.2s)/.test(idleWorking?.transition || ''),
152
+ working_transition_keeps_boxshadow: /box-shadow\s+(?:150ms|0\.15s)/.test(idleWorking?.transition || ''),
153
+ working_hover_bg_changed: idleWorking?.bg !== hoverWorking?.bg,
154
+ working_hover_more_saturated: moreSaturated(idleWorkingC, hoverWorkingC),
155
+ working_settles_back: settledWorking?.bg === idleWorking?.bg,
156
+ // R55 canvas-effect still fires from the chip — hovering the
157
+ // working chip lifts the legend working row.
158
+ working_canvas_effect_fires: workingRowLifted === 'true',
159
+
160
+ online_clickable: idleOnline?.clickable === 'true',
161
+ online_className_has_hover: idleOnline?.className.includes('hover:bg-cyan-500/15')
162
+ && idleOnline?.className.includes('hover:border-cyan-500/30'),
163
+ online_transition_includes_bg: /background-color\s+(?:200ms|0\.2s)/.test(idleOnline?.transition || ''),
164
+ online_transition_includes_border: /border-color\s+(?:200ms|0\.2s)/.test(idleOnline?.transition || ''),
165
+ online_hover_bg_changed: idleOnline?.bg !== hoverOnline?.bg,
166
+ online_hover_more_saturated: moreSaturated(idleOnlineC, hoverOnlineC),
167
+ online_settles_back: settledOnline?.bg === idleOnline?.bg,
168
+ };
169
+ const ok = Object.values(results).every(Boolean);
170
+ console.log(`${ok ? '✅' : '❌'} status-bar chips self-hover:`, JSON.stringify(results),
171
+ `\n idleWorking:`, idleWorking,
172
+ `\n hoverWorking:`, hoverWorking,
173
+ `\n settledWorking:`, settledWorking,
174
+ `\n idleOnline:`, idleOnline,
175
+ `\n hoverOnline:`, hoverOnline,
176
+ `\n settledOnline:`, settledOnline);
177
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,112 @@
1
+ /* Round 156 verification: SVG <g> interactives gain
2
+ * `.anet-topo-svg-focus` for keyboard focus outline.
3
+ *
4
+ * Mirrors R155's chip-row treatment at the SVG scope. 6 surfaces:
5
+ * data-node (R151 node body)
6
+ * data-group-label-hit (R63 group label)
7
+ * data-edge-count-badge (R121 edge badge)
8
+ * data-recent-row (R116 recent-signal row)
9
+ * data-recent-panel-more-nav (R133 "+N more" footer)
10
+ * data-legend-status (R61 legend row)
11
+ *
12
+ * Class `.anet-topo-svg-focus:focus-visible` paints outline 2px
13
+ * cyan-300 (#67e8f9) with 2px outline-offset. Browser default
14
+ * focus ring on SVG is hard to spot against the dashboard
15
+ * canvas; explicit cyan matches the legendAccent palette.
16
+ *
17
+ * Test: every SVG interactive surface carries the class.
18
+ */
19
+ import { chromium } from 'playwright';
20
+ import { readFileSync } from 'node:fs';
21
+
22
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
23
+ const browser = await chromium.launch({ headless: true });
24
+
25
+ const ctx = await browser.newContext({ viewport: { width: 1600, 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 {
29
+ localStorage.setItem('anet-theme', 'cyber');
30
+ sessionStorage.setItem('anet_v3_auth', '1');
31
+ // grid layout to surface group labels
32
+ localStorage.setItem('anet-topo-layout', 'grid');
33
+ } catch {}
34
+ });
35
+
36
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
37
+ await ctx.route('**/api/hub/status*', async (route) => {
38
+ const r = await route.fetch();
39
+ const b = await r.json();
40
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
41
+ const mk = (alias, status) => ({
42
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
43
+ network_id: nid, project_dir: null,
44
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
45
+ });
46
+ await route.fulfill({ response: r, json: { ...b, sessions: [
47
+ mk('agents-a1', 'working'), mk('agents-a2', 'working'),
48
+ mk('infra-b1', 'working'),
49
+ ] } });
50
+ });
51
+
52
+ const now = Date.now();
53
+ // Edge midpoint count badge (R100) only renders when link.count >= 3.
54
+ // Pair agents-a1 → agents-a2 gets 4 msgs to clear the threshold; add 4
55
+ // other singletons so flowLinks.length = 5 → R128 "+ 2 more flows"
56
+ // footer also renders.
57
+ const msgs = [];
58
+ for (let i = 0; i < 4; i++) {
59
+ msgs.push({
60
+ id: `a2a${i}`, from_alias: 'agents-a1', to_alias: 'agents-a2', content: 'hi',
61
+ network_id: 'default', created_at: new Date(now - (10000 + i * 500)).toISOString(),
62
+ });
63
+ }
64
+ msgs.push({ id: 'm1', from_alias: 'agents-a2', to_alias: 'infra-b1', content: 'hi',
65
+ network_id: 'default', created_at: new Date(now - 11000).toISOString() });
66
+ msgs.push({ id: 'm2', from_alias: 'infra-b1', to_alias: 'agents-a1', content: 'hi',
67
+ network_id: 'default', created_at: new Date(now - 12000).toISOString() });
68
+ msgs.push({ id: 'm3', from_alias: 'agents-a1', to_alias: 'infra-b1', content: 'hi',
69
+ network_id: 'default', created_at: new Date(now - 13000).toISOString() });
70
+ msgs.push({ id: 'm4', from_alias: 'agents-a2', to_alias: 'agents-a1', content: 'hi',
71
+ network_id: 'default', created_at: new Date(now - 14000).toISOString() });
72
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
73
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
74
+
75
+ const page = await ctx.newPage();
76
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
77
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
78
+ await page.waitForSelector('[data-recent-row]', { timeout: 10000 });
79
+ await page.waitForSelector('[data-recent-panel-more-nav]', { timeout: 10000 });
80
+ await page.waitForSelector('[data-edge-count-badge]', { timeout: 10000 });
81
+ await page.waitForSelector('[data-group-label-hit]', { timeout: 10000 });
82
+ await page.waitForTimeout(400);
83
+
84
+ const out = await page.evaluate(() => {
85
+ const has = (sel) => {
86
+ const el = document.querySelector(sel);
87
+ return el && (el.getAttribute('class') || '').includes('anet-topo-svg-focus');
88
+ };
89
+ return {
90
+ nodeBody: has('g[data-node]'),
91
+ groupLabel: has('[data-group-label-hit]'),
92
+ edgeCountBadge: has('[data-edge-count-badge]'),
93
+ recentRow: has('[data-recent-row]'),
94
+ recentPanelMoreNav: has('[data-recent-panel-more-nav]'),
95
+ legendStatusRow: has('[data-legend-status="working"]'),
96
+ };
97
+ });
98
+
99
+ await browser.close();
100
+
101
+ const results = {
102
+ nodeBody_focus: out.nodeBody === true,
103
+ groupLabel_focus: out.groupLabel === true,
104
+ edgeCountBadge_focus: out.edgeCountBadge === true,
105
+ recentRow_focus: out.recentRow === true,
106
+ recentPanelMoreNav_focus: out.recentPanelMoreNav === true,
107
+ legendStatusRow_focus: out.legendStatusRow === true,
108
+ };
109
+ const ok = Object.values(results).every(Boolean);
110
+ console.log(`${ok ? '✅' : '❌'} SVG focus class:`, JSON.stringify(results),
111
+ `\n raw=`, out);
112
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,94 @@
1
+ /* Round 146 verification: R45 radar sweep rotation rate buckets on
2
+ * workingCount, completing the 5-layer busyness-driven motion family:
3
+ *
4
+ * R84 hub grounding-halo breath — 4 / 3.2 / 2.7 / 2.4 s
5
+ * R131 outer-ring orbit period — 16 / 14 / 12 / 10 s
6
+ * R132 groupbox marching ants — 14 / 12 / 10 / 8 s (grid only)
7
+ * R145 idle-spoke flow rate — 2.8 / 2.4 / 2.0 / 1.6 s (ring only)
8
+ * R146 radar sweep rotation — 8 / 6 / 4 / 3 s (both layouts)
9
+ *
10
+ * Shared R84 thresholds (0 / 1-2 / 3-5 / 6+) drive every layer. The
11
+ * sweep is the only layer visible in BOTH layouts and ties the family
12
+ * together at the canvas-wide scope.
13
+ *
14
+ * R45 baseline 6s sits at bucket 1 — preserved when workingCount in
15
+ * [1, 2] so existing demos and screenshots stay identical.
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
+
22
+ async function probe(workingCount, totalCount) {
23
+ const browser = await chromium.launch({ headless: true });
24
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript(() => {
27
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
28
+ });
29
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
30
+ await ctx.route('**/api/hub/status*', async (route) => {
31
+ const r = await route.fetch();
32
+ const b = await r.json();
33
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
34
+ const sessions = [];
35
+ for (let i = 0; i < totalCount; i++) {
36
+ sessions.push({
37
+ alias: `node${i}`,
38
+ status: i < workingCount ? 'working' : 'idle',
39
+ model: 'claude-opus-4', runtime: 'cli-claude-code',
40
+ network_id: nid, project_dir: null,
41
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
42
+ });
43
+ }
44
+ await route.fulfill({ response: r, json: { ...b, sessions } });
45
+ });
46
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
47
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
48
+
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n, totalCount, { timeout: 30000 });
52
+ await page.waitForSelector('.anet-topo-sweep', { timeout: 10000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ const out = await page.evaluate(() => {
56
+ const g = document.querySelector('.anet-topo-sweep');
57
+ return {
58
+ bucket: g?.getAttribute('data-topo-sweep-bucket'),
59
+ dur: g?.getAttribute('data-topo-sweep-dur'),
60
+ cssDur: g ? getComputedStyle(g).getPropertyValue('animation-duration').trim() : null,
61
+ };
62
+ });
63
+ await browser.close();
64
+ return out;
65
+ }
66
+
67
+ const b0 = await probe(0, 4); // 0 working → bucket 0 (8s)
68
+ const b1 = await probe(2, 4); // 2 working → bucket 1 (6s, R45 baseline)
69
+ const b2 = await probe(4, 5); // 4 working → bucket 2 (4s)
70
+ const b3 = await probe(7, 8); // 7 working → bucket 3 (3s)
71
+
72
+ const consistent = (probe, bucket, dur) =>
73
+ probe.bucket === String(bucket) && probe.dur === String(dur) && probe.cssDur === `${dur}s`;
74
+
75
+ const results = {
76
+ b0_bucket0_8s: consistent(b0, 0, 8),
77
+ b1_bucket1_6s: consistent(b1, 1, 6),
78
+ b2_bucket2_4s: consistent(b2, 2, 4),
79
+ b3_bucket3_3s: consistent(b3, 3, 3),
80
+ // dur strictly decreases as busy climbs
81
+ dur_monotonic:
82
+ Number(b0.dur) > Number(b1.dur) &&
83
+ Number(b1.dur) > Number(b2.dur) &&
84
+ Number(b2.dur) > Number(b3.dur),
85
+ // R45 baseline preservation: bucket 1 still 6s
86
+ baseline_preserved: b1.dur === '6',
87
+ };
88
+ const ok = Object.values(results).every(Boolean);
89
+ console.log(`${ok ? '✅' : '❌'} sweep busyness:`, JSON.stringify(results),
90
+ `\n b0(0w/4)=`, b0,
91
+ `\n b1(2w/4)=`, b1,
92
+ `\n b2(4w/5)=`, b2,
93
+ `\n b3(7w/8)=`, b3);
94
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,93 @@
1
+ /* Round 225 verification: info-density tabular-nums sweep across
2
+ * the three remaining digit-bearing texts in the TopoGraph after
3
+ * R224's edge badge:
4
+ * 1) Hub center digit (data-topo-hub-working-count)
5
+ * 2) Recent panel header (data-recent-panel-count tspan)
6
+ * 3) Recent row count (data-recent-row-count tspan)
7
+ *
8
+ * Pre-R225 each suffered the same monospace-digit jitter at the
9
+ * 9 → 10 boundary (the digit-vs-control glyph width variance is
10
+ * non-zero even in mono fonts; textAnchor='middle' or trailing
11
+ * text columns visibly shift on the boundary).
12
+ *
13
+ * Verification: getComputedStyle().fontVariantNumeric contains
14
+ * 'tabular-nums' on each of the three. Single populated scenario
15
+ * (4 working agents + 12-msg hot flow) drives all three at once.
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
+ const ctx = await browser.newContext({ viewport: { width: 1500, 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 { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } 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 mk = (alias) => ({
33
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
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: [
38
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
39
+ ] } });
40
+ });
41
+ const now = Date.now();
42
+ const msgs = [];
43
+ for (let i = 0; i < 12; i++) {
44
+ msgs.push({
45
+ id: `m${i}`, from_alias: 'alpha', to_alias: 'beta', content: 'hi',
46
+ network_id: 'default', created_at: new Date(now - (1000 + i * 50)).toISOString(),
47
+ });
48
+ }
49
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: msgs } }));
50
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
51
+
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.waitForSelector('[data-recent-row-count]', { timeout: 10000, state: 'attached' });
56
+ await page.waitForTimeout(400);
57
+
58
+ const out = await page.evaluate(() => {
59
+ const fvn = (el) => el ? getComputedStyle(el).fontVariantNumeric : null;
60
+ const text = (el) => el?.textContent?.trim() || null;
61
+ const hubDigit = document.querySelector('[data-topo-hub-working-count]');
62
+ const panelCnt = document.querySelector('[data-recent-panel-count]');
63
+ const rowCnt = document.querySelector('[data-recent-row-count]');
64
+ return {
65
+ hub: { found: !!hubDigit, fvn: fvn(hubDigit), text: text(hubDigit),
66
+ working: hubDigit?.getAttribute('data-topo-hub-working-count') },
67
+ panel: { found: !!panelCnt, fvn: fvn(panelCnt), text: text(panelCnt) },
68
+ row: { found: !!rowCnt, fvn: fvn(rowCnt), text: text(rowCnt) },
69
+ };
70
+ });
71
+ await browser.close();
72
+
73
+ const hasTab = (s) => /tabular-nums/.test(s || '');
74
+ const results = {
75
+ hub_found: out.hub.found,
76
+ hub_tabular: hasTab(out.hub.fvn),
77
+ hub_text_present: /^\d+$/.test(out.hub.text || ''),
78
+ hub_text_equals_4: out.hub.text === '4',
79
+
80
+ panel_found: out.panel.found,
81
+ panel_tabular: hasTab(out.panel.fvn),
82
+ panel_text_flows: /^\d+\s+flows?$/.test(out.panel.text || ''),
83
+
84
+ row_found: out.row.found,
85
+ row_tabular: hasTab(out.row.fvn),
86
+ row_text_equals_12: out.row.text === '12',
87
+ };
88
+ const ok = Object.values(results).every(Boolean);
89
+ console.log(`${ok ? '✅' : '❌'} tabular-nums sweep:`, JSON.stringify(results),
90
+ '\n hub: ', out.hub,
91
+ '\n panel:', out.panel,
92
+ '\n row: ', out.row);
93
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,86 @@
1
+ /* Round 303 verification: tier guide ring strokeDasharray tightens
2
+ * from "2 8" → "2 6". Same 2px dash; 8px gap → 6px gap so dashes
3
+ * read closer together, giving tier-guide rings clearer continuous-
4
+ * ring presence after R290/R291 cleared surrounding backdrop noise.
5
+ *
6
+ * Contract:
7
+ * - All [data-tier-ring] elements have stroke-dasharray '2 6'.
8
+ * - strokeWidth still 0.7 (regression).
9
+ * - At least one tier ring rendered with the test fixture (4-node
10
+ * working fleet → single-tier layout 220 fits).
11
+ * - R302 empty-hint ls=0.2 + R301 panel titles ls=0.3 + R300 kicker
12
+ * fw=500 + R294 pulse absent intact.
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const browser = await chromium.launch({ headless: true });
19
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
20
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
21
+ await ctx.addInitScript(() => {
22
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
23
+ });
24
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
25
+ await ctx.route('**/api/hub/status*', async (route) => {
26
+ const r = await route.fetch();
27
+ const b = await r.json();
28
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
29
+ const mk = (alias, model) => ({
30
+ alias, status: 'working', model, runtime: 'claude-code-cli',
31
+ network_id: nid, project_dir: null,
32
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
33
+ });
34
+ // 4 working nodes → single-tier ring layout.
35
+ await route.fulfill({ response: r, json: { ...b, sessions: [
36
+ mk('alpha', 'claude-opus-4'),
37
+ mk('beta', 'gpt-4o'),
38
+ mk('gamma', 'claude-sonnet-4'),
39
+ mk('delta', 'gpt-4'),
40
+ ] } });
41
+ });
42
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
44
+
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
48
+ await page.waitForTimeout(400);
49
+
50
+ const probe = await page.evaluate(() => {
51
+ const tierRings = [...document.querySelectorAll('[data-tier-ring]')];
52
+ const samples = tierRings.map(t => ({
53
+ r: t.getAttribute('data-tier-ring'),
54
+ dasharray: t.getAttribute('stroke-dasharray'),
55
+ strokeWidth: t.getAttribute('stroke-width'),
56
+ occupancy: t.getAttribute('data-tier-occupancy'),
57
+ }));
58
+ const empty = document.querySelector('[data-recent-signal-empty]');
59
+ const recentTitle = document.querySelector('[data-recent-panel-title]');
60
+ const legendTitle = document.querySelector('[data-legend-panel-title]');
61
+ const kicker = document.querySelector('[data-topo-section-kicker]');
62
+ return {
63
+ samples,
64
+ emptyLs: empty?.getAttribute('letter-spacing') ?? null,
65
+ recentTitleLs: recentTitle?.getAttribute('letter-spacing') ?? null,
66
+ legendTitleLs: legendTitle?.getAttribute('letter-spacing') ?? null,
67
+ kickerFw: kicker ? getComputedStyle(kicker).fontWeight : null,
68
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
69
+ };
70
+ });
71
+ await browser.close();
72
+
73
+ const results = {
74
+ tier_rings_rendered: probe.samples.length >= 1,
75
+ all_dasharray_2_6: probe.samples.every(s => s.dasharray === '2 6'),
76
+ all_stroke_width_0_7: probe.samples.every(s => s.strokeWidth === '0.7'),
77
+ r302_empty_hint_ls_0_2: probe.emptyLs === '0.2',
78
+ r301_recent_title_ls: probe.recentTitleLs === '0.3',
79
+ r301_legend_title_ls: probe.legendTitleLs === '0.3',
80
+ r300_kicker_fw_500: String(probe.kickerFw) === '500',
81
+ r294_pulse_absent: probe.pulseCount === 0,
82
+ };
83
+ const ok = Object.values(results).every(Boolean);
84
+ console.log(`${ok ? '✅' : '❌'} tier guide dashes:`, JSON.stringify(results),
85
+ '\n tier rings sampled:', probe.samples);
86
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,107 @@
1
+ /* Round 174 verification: tier guide rings (ring layout) fade-in
2
+ * alongside R9/R172/R173 first-paint wave.
3
+ *
4
+ * Pre-R174 the structural scaffolding for ring layout — R54
5
+ * dashed concentric guide rings — appeared instantly while
6
+ * nodes inside eased in via R9/R72. Closes the symmetry with
7
+ * R173 group boxes (grid layout's structural frame).
8
+ *
9
+ * R174 wraps each tier ring <circle> in:
10
+ * className="anet-fade-in"
11
+ * style.animationDelay: tierIdx × 60ms (cap 8)
12
+ * transition list grows opacity 250ms ease-out
13
+ * data-tier-fade-delay={...}
14
+ *
15
+ * Test:
16
+ * 1. Force ring layout
17
+ * 2. Mock 16 sessions → triggers triple-tier render (>14)
18
+ * 3. Probe all [data-tier-ring] circles
19
+ * 4. Each has .anet-fade-in className + correct fade-delay
20
+ * 5. Delays strictly increase by 60ms
21
+ */
22
+ import { chromium } from 'playwright';
23
+ import { readFileSync } from 'node:fs';
24
+
25
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
26
+ const browser = await chromium.launch({ headless: true });
27
+
28
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try {
32
+ localStorage.setItem('anet-theme', 'cyber');
33
+ sessionStorage.setItem('anet_v3_auth', '1');
34
+ localStorage.setItem('anet-topo-layout', 'ring');
35
+ } catch {}
36
+ });
37
+
38
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
39
+ await ctx.route('**/api/hub/status*', async (route) => {
40
+ const r = await route.fetch();
41
+ const b = await r.json();
42
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
43
+ // 16 sessions → R98 triple-tier (threshold > 14)
44
+ const sessions = [];
45
+ for (let i = 0; i < 16; i++) {
46
+ sessions.push({
47
+ alias: `n${i}`,
48
+ status: 'working',
49
+ model: 'claude-opus-4',
50
+ runtime: 'cli-claude-code',
51
+ network_id: nid,
52
+ project_dir: null,
53
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
54
+ });
55
+ }
56
+ await route.fulfill({ response: r, json: { ...b, sessions } });
57
+ });
58
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
59
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
60
+
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 === 16, { timeout: 30000 });
64
+ await page.waitForSelector('[data-tier-ring]', { timeout: 10000 });
65
+ await page.waitForTimeout(400);
66
+
67
+ const probe = await page.evaluate(() => {
68
+ const rings = [...document.querySelectorAll('[data-tier-ring]')];
69
+ return rings.map(r => ({
70
+ radius: parseFloat(r.getAttribute('data-tier-ring') || ''),
71
+ occupancy: parseFloat(r.getAttribute('data-tier-occupancy') || ''),
72
+ hasFadeClass: (r.getAttribute('class') || '').includes('anet-fade-in'),
73
+ fadeDelayAttr: parseFloat(r.getAttribute('data-tier-fade-delay') || ''),
74
+ animationDelay: r.style.animationDelay || getComputedStyle(r).animationDelay,
75
+ transition: r.style.transition || getComputedStyle(r).transition,
76
+ }));
77
+ });
78
+
79
+ await browser.close();
80
+
81
+ const parseDelay = (s) => {
82
+ if (!s) return null;
83
+ if (s.endsWith('ms')) return parseFloat(s);
84
+ if (s.endsWith('s')) return parseFloat(s) * 1000;
85
+ return parseFloat(s);
86
+ };
87
+ const delays = probe.map(p => parseDelay(p.animationDelay));
88
+
89
+ const expectedDelays = probe.map((_, i) => Math.min(i, 8) * 60);
90
+
91
+ const results = {
92
+ three_tiers_found: probe.length === 3,
93
+ all_have_fade_class: probe.every(p => p.hasFadeClass),
94
+ all_have_opacity_in_transition: probe.every(p =>
95
+ (p.transition || '').includes('opacity')),
96
+ delay_attrs_match: probe.every((p, i) => p.fadeDelayAttr === expectedDelays[i]),
97
+ delays_match: delays.every((d, i) => Math.abs(d - expectedDelays[i]) < 0.5),
98
+ delays_strictly_increase: delays[0] < delays[1] && delays[1] < delays[2],
99
+ first_delay_0ms: delays[0] === 0,
100
+ third_delay_120ms: delays[2] === 120,
101
+ };
102
+ const ok = Object.values(results).every(Boolean);
103
+ console.log(`${ok ? '✅' : '❌'} tier-ring fade-in:`, JSON.stringify(results),
104
+ `\n rings =`, probe,
105
+ `\n delays (ms) =`, delays,
106
+ `\n expected =`, expectedDelays);
107
+ process.exit(ok ? 0 : 1);