@sleep2agi/agent-network-dashboard 0.4.6-preview.12 → 0.4.6-preview.121

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 (327) 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 +69 -55
  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/0.hnfdve7~422.js +7 -0
  181. package/.next/static/chunks/001t1axy5kvz8.css +2 -0
  182. package/.next/static/chunks/01lmbsd37fybu.js +1 -0
  183. package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
  184. package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
  185. package/.next/static/chunks/05uk96gc~9mni.js +1 -0
  186. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  187. package/.next/static/chunks/0a-6zp8m0ux5..js +1 -0
  188. package/.next/static/chunks/0ekib0be3t9i5.js +1 -0
  189. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  190. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  191. package/.next/static/chunks/0tvn2l1pc.h65.js +1 -0
  192. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  193. package/.next/static/chunks/106~a5kttxze-.js +4 -0
  194. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  195. package/.next/trace +2 -2
  196. package/.next/trace-build +1 -1
  197. package/.next/types/routes.d.ts +2 -1
  198. package/.next/types/validator.ts +9 -0
  199. package/app/api/hub/servers/route.ts +39 -0
  200. package/app/components/AppShell.tsx +2 -0
  201. package/app/components/ChatPopover.tsx +38 -2
  202. package/app/components/CommandPalette.tsx +94 -0
  203. package/app/components/HelpOverlay.tsx +76 -0
  204. package/app/components/ServersDrawer.tsx +206 -0
  205. package/app/components/TopoGraph.tsx +2181 -163
  206. package/app/components/utils.ts +8 -7
  207. package/app/globals.css +19 -1
  208. package/app/lib/time.ts +36 -0
  209. package/package.json +1 -1
  210. package/scripts/help-overlay-topo-test.mjs +44 -0
  211. package/scripts/servers-drawer-live-test.mjs +96 -0
  212. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  213. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  214. package/scripts/topo-active-links-chip-test.mjs +73 -0
  215. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  216. package/scripts/topo-arrival-ping-test.mjs +87 -0
  217. package/scripts/topo-arrow-tier-test.mjs +61 -0
  218. package/scripts/topo-auto-fit-test.mjs +73 -0
  219. package/scripts/topo-chat-focus-ring-test.mjs +77 -0
  220. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  221. package/scripts/topo-click-ripple-test.mjs +66 -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-count-badge-test.mjs +84 -0
  230. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  231. package/scripts/topo-edge-focus-test.mjs +113 -0
  232. package/scripts/topo-edge-hover-test.mjs +93 -0
  233. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  234. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  235. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  236. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  237. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  238. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  239. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  240. package/scripts/topo-fit-key-test.mjs +106 -0
  241. package/scripts/topo-freshness-chip-test.mjs +74 -0
  242. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  243. package/scripts/topo-group-count-test.mjs +68 -0
  244. package/scripts/topo-group-hover-test.mjs +82 -0
  245. package/scripts/topo-group-label-hover-test.mjs +119 -0
  246. package/scripts/topo-group-label-tint-test.mjs +99 -0
  247. package/scripts/topo-group-label-title-test.mjs +83 -0
  248. package/scripts/topo-group-pin-test.mjs +119 -0
  249. package/scripts/topo-group-status-pips-test.mjs +103 -0
  250. package/scripts/topo-groupbox-live-test.mjs +100 -0
  251. package/scripts/topo-groupbox-test.mjs +4 -2
  252. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  253. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  254. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  255. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  256. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  257. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  258. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  259. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  260. package/scripts/topo-hub-busyness-test.mjs +100 -0
  261. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  262. package/scripts/topo-hub-title-test.mjs +93 -0
  263. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  264. package/scripts/topo-label-lift-test.mjs +119 -0
  265. package/scripts/topo-label-scale-test.mjs +78 -0
  266. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  267. package/scripts/topo-layout-key-test.mjs +91 -0
  268. package/scripts/topo-legend-count-test.mjs +88 -0
  269. package/scripts/topo-legend-hover-test.mjs +113 -0
  270. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  271. package/scripts/topo-legend-pin-test.mjs +128 -0
  272. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  273. package/scripts/topo-minimap-test.mjs +68 -0
  274. package/scripts/topo-mobile-chips-test.mjs +103 -0
  275. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  276. package/scripts/topo-node-title-test.mjs +101 -0
  277. package/scripts/topo-offline-dim-test.mjs +70 -0
  278. package/scripts/topo-overlap-test.mjs +32 -5
  279. package/scripts/topo-pan-cursor-test.mjs +62 -0
  280. package/scripts/topo-panel-elevation-test.mjs +76 -0
  281. package/scripts/topo-particle-stagger-test.mjs +82 -0
  282. package/scripts/topo-pin-persistence-test.mjs +105 -0
  283. package/scripts/topo-popover-meta-test.mjs +78 -0
  284. package/scripts/topo-pressure-bar-test.mjs +69 -0
  285. package/scripts/topo-pressure-hover-test.mjs +123 -0
  286. package/scripts/topo-pressure-pin-test.mjs +119 -0
  287. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  288. package/scripts/topo-pulse-rate-test.mjs +71 -0
  289. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  290. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  291. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  292. package/scripts/topo-recent-row-ts-test.mjs +92 -0
  293. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  294. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  295. package/scripts/topo-reduced-motion-test.mjs +77 -0
  296. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  297. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  298. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  299. package/scripts/topo-tier-ring-test.mjs +69 -0
  300. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  301. package/scripts/topo-tier-stagger-test.mjs +89 -0
  302. package/scripts/topo-tz-ghost-test.mjs +69 -0
  303. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  304. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  305. package/scripts/topo-vendor-pill-test.mjs +125 -0
  306. package/scripts/topo-vendor-pin-test.mjs +125 -0
  307. package/scripts/topo-wheel-zoom-test.mjs +79 -0
  308. package/scripts/topo-working-halo-breath-test.mjs +86 -0
  309. package/scripts/topo-zoom-keys-test.mjs +89 -0
  310. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  311. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  312. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  313. package/.next/static/chunks/0cj5qr_a8dzky.js +0 -7
  314. package/.next/static/chunks/0dttftqqehpdj.js +0 -1
  315. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  316. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  317. package/.next/static/chunks/0ja71r81z59di.js +0 -1
  318. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  319. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  320. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  321. package/.next/static/chunks/0z6mgaoe8c89-.js +0 -1
  322. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  323. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  324. package/.next/static/chunks/11y8xsp9mn.m8.css +0 -2
  325. /package/.next/static/{irozqkI_mD2Ls10yQ_4yG → fh5pUqgTFNt162QarYePi}/_buildManifest.js +0 -0
  326. /package/.next/static/{irozqkI_mD2Ls10yQ_4yG → fh5pUqgTFNt162QarYePi}/_clientMiddlewareManifest.js +0 -0
  327. /package/.next/static/{irozqkI_mD2Ls10yQ_4yG → fh5pUqgTFNt162QarYePi}/_ssgManifest.js +0 -0
@@ -0,0 +1,63 @@
1
+ /* Round 45 verification: recent-signal panel shows "no flow yet" placeholder
2
+ * when flowLinks is empty; renders normal link rows when populated. */
3
+ import { chromium } from 'playwright';
4
+ import { readFileSync } from 'node:fs';
5
+
6
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
7
+ const browser = await chromium.launch({ headless: true });
8
+
9
+ async function probe({ msgs }) {
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.setItem('anet-topo-layout', 'grid');
16
+ sessionStorage.setItem('anet_v3_auth', '1');
17
+ } catch {}
18
+ });
19
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
20
+ await ctx.route('**/api/hub/status*', async (route) => {
21
+ const r = await route.fetch();
22
+ const b = await r.json();
23
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
24
+ const sessions = ['a', 'b'].map(a => ({
25
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
26
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
27
+ }));
28
+ await route.fulfill({ response: r, json: { ...b, sessions } });
29
+ });
30
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
31
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
32
+ const page = await ctx.newPage();
33
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
34
+ await page.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 30000 });
35
+ await page.waitForTimeout(500);
36
+ const data = await page.evaluate(() => {
37
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
38
+ const empty = svg.querySelector('text[data-recent-signal-empty]');
39
+ // count flow-link description rows in the recent-signal panel (translate(16,16))
40
+ const panel = [...svg.querySelectorAll(':scope > g[transform^="translate(16, 16)"]')][0];
41
+ const linkRows = panel ? [...panel.querySelectorAll('text')].filter(t => /->.*\/.*\//.test(t.textContent || '')).length : 0;
42
+ return { emptyShown: !!empty, emptyText: empty?.textContent || null, linkRows };
43
+ });
44
+ await ctx.close();
45
+ return data;
46
+ }
47
+
48
+ const noFlow = await probe({ msgs: [] });
49
+ const withFlow = await probe({ msgs: [
50
+ { from_alias: 'a', to_alias: 'b', content: 'hello', created_at: new Date().toISOString() },
51
+ ] });
52
+ await browser.close();
53
+
54
+ const results = {
55
+ emptyShowsPlaceholder: noFlow.emptyShown === true && /no flow yet/i.test(noFlow.emptyText || ''),
56
+ emptyHidesLinkRows: noFlow.linkRows === 0,
57
+ populatedHidesPlaceholder: withFlow.emptyShown === false,
58
+ populatedShowsLinkRows: withFlow.linkRows >= 1,
59
+ };
60
+ const ok = Object.values(results).every(Boolean);
61
+ console.log(`${ok ? '✅' : '❌'} recent-signal empty state:`, JSON.stringify(results),
62
+ `\n noFlow=`, noFlow, `\n withFlow=`, withFlow);
63
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,77 @@
1
+ /* Round 36 verification: prefers-reduced-motion disables all SMIL
2
+ * animations in the topology. Round 29 zeroed CSS via media query;
3
+ * SVG <animate> / <animateMotion> elements need JS to opt out and
4
+ * this round wires that via the useReducedMotion hook. */
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
+
11
+ async function probe({ reduce, layout }) {
12
+ const ctx = await browser.newContext({
13
+ viewport: { width: 1280, height: 1600 },
14
+ reducedMotion: reduce ? 'reduce' : 'no-preference',
15
+ });
16
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
17
+ await ctx.addInitScript((lay) => {
18
+ try {
19
+ localStorage.setItem('anet-theme', 'cyber');
20
+ localStorage.setItem('anet-topo-layout', lay);
21
+ localStorage.setItem('anet-topo-view', JSON.stringify({ zoom: 1, x: 0, y: 0 }));
22
+ sessionStorage.setItem('anet_v3_auth', '1');
23
+ } catch {}
24
+ }, layout);
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ const r = await route.fetch();
28
+ const b = await r.json();
29
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
30
+ // Include a 'working' status so the working pulse path renders.
31
+ const sessions = [
32
+ { alias: 'wkr', status: 'working', network_id: nid, project_dir: null,
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh },
34
+ { alias: 'idl', status: 'idle', 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
+ });
39
+ // Inject a tiny message so a flow link + particle render.
40
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
41
+ { from_alias: 'wkr', to_alias: 'idl', content: 'hi', created_at: fresh },
42
+ ] } }));
43
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
46
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
47
+ await page.waitForTimeout(600);
48
+ // Click a node so the click ripple is invoked (it should NOT render under reduced motion).
49
+ await page.locator('g[data-node="wkr"]').click().catch(() => {});
50
+ await page.waitForTimeout(120);
51
+ const counts = await page.evaluate(() => {
52
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
53
+ return {
54
+ animate: svg.querySelectorAll('animate').length,
55
+ animateMotion: svg.querySelectorAll('animateMotion').length,
56
+ };
57
+ });
58
+ await ctx.close();
59
+ return counts;
60
+ }
61
+
62
+ // Baseline (no reduce) — many SMIL elements expected.
63
+ const normalRing = await probe({ reduce: false, layout: 'ring' });
64
+ // Reduced motion — every SMIL element should be gone.
65
+ const reducedRing = await probe({ reduce: true, layout: 'ring' });
66
+ const reducedGrid = await probe({ reduce: true, layout: 'grid' });
67
+
68
+ await browser.close();
69
+ const results = {
70
+ baselineHasAnimations: normalRing.animate + normalRing.animateMotion > 0,
71
+ reducedRingDropsAll: reducedRing.animate === 0 && reducedRing.animateMotion === 0,
72
+ reducedGridDropsAll: reducedGrid.animate === 0 && reducedGrid.animateMotion === 0,
73
+ };
74
+ const ok = Object.values(results).every(Boolean);
75
+ console.log(`${ok ? '✅' : '❌'} reduced motion:`, JSON.stringify(results),
76
+ `\n normalRing=`, normalRing, `\n reducedRing=`, reducedRing, `\n reducedGrid=`, reducedGrid);
77
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,101 @@
1
+ /* Round 79 verification: header working + online count chips become
2
+ * hover affordances (sister to R77 active-links chip).
3
+ * - Hover "N working" → setHoveredStatus('working') → idle + offline
4
+ * nodes dim, working stays bright. Same outcome as R55 legend row
5
+ * hover, just from the chip surface.
6
+ * - Hover "N online" with working present → same as above.
7
+ * - Mouseleave restores baseline.
8
+ *
9
+ * Fleet: 1 working (wkr), 2 idle (idl1, idl2), 1 offline (off).
10
+ */
11
+ import { chromium } from 'playwright';
12
+ import { readFileSync } from 'node:fs';
13
+
14
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
15
+ const browser = await chromium.launch({ headless: true });
16
+
17
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
18
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
19
+ await ctx.addInitScript(() => {
20
+ try {
21
+ localStorage.setItem('anet-theme', 'cyber');
22
+ localStorage.setItem('anet-topo-layout', 'ring');
23
+ sessionStorage.setItem('anet_v3_auth', '1');
24
+ sessionStorage.removeItem('anet-topo-pinned-status');
25
+ sessionStorage.removeItem('anet-topo-pinned-group');
26
+ } catch {}
27
+ });
28
+
29
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
30
+ const stale = new Date(Date.now() - 30 * 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
+ { alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
37
+ { alias: 'idl1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
38
+ { alias: 'idl2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
39
+ { alias: 'off', status: 'offline', network_id: nid, project_dir: null, created_at: stale, updated_at: stale, last_seen_at: stale },
40
+ ];
41
+ await route.fulfill({ response: r, json: { ...b, sessions } });
42
+ });
43
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
44
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
48
+ await page.waitForTimeout(500);
49
+
50
+ const opacities = () => page.evaluate(() => {
51
+ const o = {};
52
+ for (const a of ['wkr', 'idl1', 'idl2', 'off']) {
53
+ const g = document.querySelector(`g[data-node="${a}"]`);
54
+ o[a] = g ? +(g.style.opacity || '1') : null;
55
+ }
56
+ return o;
57
+ });
58
+
59
+ const before = await opacities();
60
+
61
+ // Hover working chip
62
+ await page.locator('[data-working-chip]').hover();
63
+ await page.waitForTimeout(250);
64
+ const onWorking = await opacities();
65
+
66
+ // Move away
67
+ await page.mouse.move(10, 10);
68
+ await page.waitForTimeout(250);
69
+ const afterWorking = await opacities();
70
+
71
+ // Hover online chip (working present → routes to 'working')
72
+ await page.locator('[data-online-chip]').hover();
73
+ await page.waitForTimeout(250);
74
+ const onOnline = await opacities();
75
+
76
+ await page.mouse.move(10, 10);
77
+ await page.waitForTimeout(250);
78
+ const afterOnline = await opacities();
79
+
80
+ await browser.close();
81
+
82
+ const bright = (v) => v != null && v >= 0.55;
83
+ const dim = (v) => v != null && v < 0.4;
84
+ const results = {
85
+ before_baseline: bright(before.wkr) && bright(before.idl1) && bright(before.idl2),
86
+ working_keepsWkrBright: bright(onWorking.wkr),
87
+ working_dimsIdle: dim(onWorking.idl1) && dim(onWorking.idl2),
88
+ working_dimsOffline: dim(onWorking.off),
89
+ workingLeave_restoresAll: bright(afterWorking.wkr) && bright(afterWorking.idl1),
90
+ online_keepsWkrBright: bright(onOnline.wkr), // routes to working filter
91
+ online_dimsIdle: dim(onOnline.idl1) && dim(onOnline.idl2),
92
+ onlineLeave_restoresAll: bright(afterOnline.wkr) && bright(afterOnline.idl1),
93
+ };
94
+ const ok = Object.values(results).every(Boolean);
95
+ console.log(`${ok ? '✅' : '❌'} status-chip hover:`, JSON.stringify(results),
96
+ `\n before=`, before,
97
+ `\n onWorking=`, onWorking,
98
+ `\n afterWorking=`, afterWorking,
99
+ `\n onOnline=`, onOnline,
100
+ `\n afterOnline=`, afterOnline);
101
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,95 @@
1
+ /* Round 113 verification: chip-row "N working" + "N online" tooltips
2
+ * list matched aliases. Closes the last hover surfaces in the chip-
3
+ * row that had generic "Hover to highlight" titles. Same R97-R102 /
4
+ * R101 alias-list pattern.
5
+ *
6
+ * Fleet: 2 working (wkr1/2) + 2 idle (idl1/2) + 1 offline (off1).
7
+ * working chip title contains wkr1, wkr2; data-working-chip-aliases CSV matches
8
+ * online chip title contains all online aliases (4); data-online-chip-aliases matches
9
+ * pin state flips the action hint
10
+ */
11
+ import { chromium } from 'playwright';
12
+ import { readFileSync } from 'node:fs';
13
+
14
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
15
+ const browser = await chromium.launch({ headless: true });
16
+
17
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
18
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
19
+ await ctx.addInitScript(() => {
20
+ try {
21
+ localStorage.setItem('anet-theme', 'cyber');
22
+ sessionStorage.setItem('anet_v3_auth', '1');
23
+ sessionStorage.removeItem('anet-topo-pinned-status');
24
+ } catch {}
25
+ });
26
+
27
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
28
+ const stale10 = new Date(Date.now() - 10 * 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, ts) => ({
34
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
35
+ network_id: nid, project_dir: null,
36
+ created_at: ts, updated_at: ts, last_seen_at: ts,
37
+ });
38
+ await route.fulfill({ response: r, json: { ...b, sessions: [
39
+ mk('wkr1', 'working', fresh),
40
+ mk('wkr2', 'working', fresh),
41
+ mk('idl1', 'idle', fresh),
42
+ mk('idl2', 'idle', fresh),
43
+ mk('off1', 'offline', stale10),
44
+ ] } });
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
+ const page = await ctx.newPage();
49
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
50
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 5, { timeout: 30000 });
51
+ await page.waitForTimeout(400);
52
+
53
+ const read = (sel) => page.evaluate((s) => {
54
+ const el = document.querySelector(s);
55
+ if (!el) return null;
56
+ return {
57
+ title: el.getAttribute('title') || '',
58
+ aliases: el.getAttribute('data-working-chip-aliases') || el.getAttribute('data-online-chip-aliases') || '',
59
+ };
60
+ }, sel);
61
+
62
+ const workingBefore = await read('[data-working-chip]');
63
+ const onlineBefore = await read('[data-online-chip]');
64
+
65
+ // Pin working status — action hint should flip.
66
+ await page.evaluate(() => {
67
+ sessionStorage.setItem('anet-topo-pinned-status', 'working');
68
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'status', value: 'working' } }));
69
+ });
70
+ await page.waitForTimeout(300);
71
+ const workingPinned = await read('[data-working-chip]');
72
+
73
+ await browser.close();
74
+
75
+ const sameSet = (csv, expected) => {
76
+ const got = new Set(csv.split(',').filter(Boolean));
77
+ const exp = new Set(expected);
78
+ return got.size === exp.size && [...exp].every(x => got.has(x));
79
+ };
80
+
81
+ const results = {
82
+ workingBefore_aliasesMatch: sameSet(workingBefore?.aliases || '', ['wkr1', 'wkr2']),
83
+ workingBefore_titleListsThem: /wkr1.*wkr2|wkr2.*wkr1/.test(workingBefore?.title || ''),
84
+ workingBefore_titleClickHint: /click to pin/.test(workingBefore?.title || ''),
85
+ onlineBefore_aliasesMatch: sameSet(onlineBefore?.aliases || '', ['wkr1', 'wkr2', 'idl1', 'idl2']),
86
+ onlineBefore_titleListsThem: /wkr1/.test(onlineBefore?.title || '') && /idl1/.test(onlineBefore?.title || ''),
87
+ pinnedWorking_aliasesStable: sameSet(workingPinned?.aliases || '', ['wkr1', 'wkr2']),
88
+ pinnedWorking_titleHintFlipped:/pinned, Esc to clear/.test(workingPinned?.title || ''),
89
+ };
90
+ const ok = Object.values(results).every(Boolean);
91
+ console.log(`${ok ? '✅' : '❌'} status-chip tooltip:`, JSON.stringify(results),
92
+ `\n workingBefore=`, workingBefore,
93
+ `\n onlineBefore=`, onlineBefore,
94
+ `\n workingPinned=`, workingPinned);
95
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,87 @@
1
+ /* Round 92 verification: tier-ring opacity reflects occupancy. R54
2
+ * drew the guide rings at a fixed opacity regardless of how many
3
+ * nodes lived on each tier. R92 buckets the count and scales
4
+ * opacity, so a crowded tier shouts and an empty tier disappears.
5
+ *
6
+ * Probe three fleet sizes: small (4 → 1 ring), medium (12 → 2 rings),
7
+ * large (18 → 3 rings). Assert bucket monotonicity (higher count =
8
+ * higher bucket = higher opacity) and that each rendered ring carries
9
+ * its occupancy + bucket data attributes.
10
+ */
11
+ import { chromium } from 'playwright';
12
+ import { readFileSync } from 'node:fs';
13
+
14
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
15
+ const browser = await chromium.launch({ headless: true });
16
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
17
+
18
+ const probe = async (count) => {
19
+ const ctx = await browser.newContext({ viewport: { width: 1400, 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 {
23
+ localStorage.setItem('anet-theme', 'cyber');
24
+ localStorage.setItem('anet-topo-layout', 'ring');
25
+ sessionStorage.setItem('anet_v3_auth', '1');
26
+ } catch {}
27
+ });
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 = [];
33
+ for (let i = 0; i < count; i++) {
34
+ sessions.push({ alias: `n${i}`, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
35
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh });
36
+ }
37
+ await route.fulfill({ response: r, json: { ...b, sessions } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
40
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
41
+ const page = await ctx.newPage();
42
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
43
+ await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n,
44
+ count, { timeout: 30000 });
45
+ await page.waitForTimeout(500);
46
+
47
+ const rings = await page.evaluate(() => {
48
+ return [...document.querySelectorAll('[data-tier-ring]')].map(r => ({
49
+ radius: r.getAttribute('data-tier-ring'),
50
+ occupancy: r.getAttribute('data-tier-occupancy'),
51
+ bucket: r.getAttribute('data-tier-bucket'),
52
+ opacity: +(r.getAttribute('opacity') || '0'),
53
+ })).sort((a, b) => +a.radius - +b.radius);
54
+ });
55
+ await ctx.close();
56
+ return rings;
57
+ };
58
+
59
+ const small = await probe(4); // 4 nodes → 1 ring, occupancy 4, bucket 1 (3-6)
60
+ const medium = await probe(12); // 12 nodes → 2 rings
61
+ const large = await probe(18); // 18 nodes → 3 rings
62
+ await browser.close();
63
+
64
+ const occupancySum = (rings) => rings.reduce((acc, r) => acc + +r.occupancy, 0);
65
+
66
+ const results = {
67
+ small_oneRing: small.length === 1,
68
+ small_occupancy4: small[0]?.occupancy === '4',
69
+ small_bucket1: small[0]?.bucket === '1',
70
+ medium_twoRings: medium.length === 2,
71
+ medium_occupancyMatchesFleet: occupancySum(medium) === 12,
72
+ large_threeRings: large.length === 3,
73
+ large_occupancyMatchesFleet: occupancySum(large) === 18,
74
+ // Higher bucket → higher opacity (monotonic ladder).
75
+ bucketLadder_monotonic: large.every(r => {
76
+ const b = +r.bucket;
77
+ // dark theme: bucket 0 → 0.32, 1 → 0.46, 2 → 0.62
78
+ const expected = [0.32, 0.46, 0.62][b];
79
+ return Math.abs(r.opacity - expected) < 0.01;
80
+ }),
81
+ };
82
+ const ok = Object.values(results).every(Boolean);
83
+ console.log(`${ok ? '✅' : '❌'} tier-ring occupancy:`, JSON.stringify(results),
84
+ `\n small (4)=`, small,
85
+ `\n medium (12)=`, medium,
86
+ `\n large (18)=`, large);
87
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,69 @@
1
+ /* Round 54 verification: tier-radius guide rings render at the active
2
+ * tier radii only, picked from the online node count.
3
+ * - N ≤ 8 → 1 ring at r=220
4
+ * - 9-14 → 2 rings at r=175 and r=260
5
+ * - N > 14 → 3 rings at r=145, 215, 285
6
+ * - Grid layout has NO tier rings (they're ring-only).
7
+ * Identified via the `data-tier-ring` attribute the implementation
8
+ * stamps on each guide circle. */
9
+ import { chromium } from 'playwright';
10
+ import { readFileSync } from 'node:fs';
11
+
12
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
13
+ const browser = await chromium.launch({ headless: true });
14
+
15
+ async function probe(layout, count) {
16
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1500 } });
17
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
18
+ await ctx.addInitScript((layout) => {
19
+ try {
20
+ localStorage.setItem('anet-theme', 'cyber');
21
+ localStorage.setItem('anet-topo-layout', layout);
22
+ sessionStorage.setItem('anet_v3_auth', '1');
23
+ } catch {}
24
+ }, layout);
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ const r = await route.fetch();
28
+ const b = await r.json();
29
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
30
+ const sessions = Array.from({ length: count }).map((_, i) => ({
31
+ alias: `n${i.toString().padStart(2, '0')}`,
32
+ status: 'idle', network_id: nid, project_dir: null,
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
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
39
+ const page = await ctx.newPage();
40
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
41
+ await page.waitForFunction(c => document.querySelectorAll('g[data-node]').length === c, count, { timeout: 30000 });
42
+ await page.waitForTimeout(400);
43
+ const radii = await page.evaluate(() => {
44
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
45
+ return [...svg.querySelectorAll('circle[data-tier-ring]')]
46
+ .map(c => +c.getAttribute('data-tier-ring'))
47
+ .sort((a, b) => a - b);
48
+ });
49
+ await ctx.close();
50
+ return radii;
51
+ }
52
+
53
+ const single = await probe('ring', 6);
54
+ const dual = await probe('ring', 12);
55
+ const triple = await probe('ring', 22);
56
+ const grid = await probe('grid', 6);
57
+
58
+ await browser.close();
59
+ const eq = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
60
+ const results = {
61
+ singleHas220: eq(single, [220]),
62
+ dualHas175And260: eq(dual, [175, 260]),
63
+ tripleHas145_215_285: eq(triple, [145, 215, 285]),
64
+ gridHasNoTierRings: eq(grid, []),
65
+ };
66
+ const ok = Object.values(results).every(Boolean);
67
+ console.log(`${ok ? '✅' : '❌'} tier rings:`, JSON.stringify(results),
68
+ `\n single=${JSON.stringify(single)} dual=${JSON.stringify(dual)} triple=${JSON.stringify(triple)} grid=${JSON.stringify(grid)}`);
69
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,110 @@
1
+ /* Round 93 verification: tier rings tint to pal.legendAccent when
2
+ * any of pinnedStatus / pinnedGroup / pinnedVendor is active, and
3
+ * revert to the neutral pal.ringStroke when all three release.
4
+ *
5
+ * Drive pin state via the R69 CustomEvent for status (deterministic;
6
+ * same approach the R82 chip-pin-mirror test uses) and the vendor
7
+ * letter click for vendor. Verify data-tier-tinted toggles + stroke
8
+ * attribute differs from the neutral baseline.
9
+ */
10
+ import { chromium } from 'playwright';
11
+ import { readFileSync } from 'node:fs';
12
+
13
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
14
+ const browser = await chromium.launch({ headless: true });
15
+
16
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
17
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
18
+ await ctx.addInitScript(() => {
19
+ try {
20
+ localStorage.setItem('anet-theme', 'cyber');
21
+ localStorage.setItem('anet-topo-layout', 'ring');
22
+ sessionStorage.setItem('anet_v3_auth', '1');
23
+ sessionStorage.removeItem('anet-topo-pinned-status');
24
+ sessionStorage.removeItem('anet-topo-pinned-group');
25
+ sessionStorage.removeItem('anet-topo-pinned-vendor');
26
+ } catch {}
27
+ });
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
+ // 4 nodes → single tier (≤8 threshold). Enough to have 1 ring to inspect.
35
+ const mk = (alias, model) => ({
36
+ alias, status: 'idle', model, runtime: 'cli-claude-code',
37
+ network_id: nid, project_dir: null,
38
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
39
+ });
40
+ const sessions = [
41
+ mk('alpha', 'claude-opus-4'),
42
+ mk('beta', 'gpt-5'),
43
+ mk('gamma', 'claude-opus-4'),
44
+ mk('delta', 'gpt-5'),
45
+ ];
46
+ await route.fulfill({ response: r, json: { ...b, sessions } });
47
+ });
48
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
49
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
50
+ const page = await ctx.newPage();
51
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
52
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ const ring = () => page.evaluate(() => {
56
+ const r = document.querySelector('[data-tier-ring]');
57
+ if (!r) return null;
58
+ return {
59
+ tinted: r.getAttribute('data-tier-tinted'),
60
+ stroke: r.getAttribute('stroke'),
61
+ };
62
+ });
63
+
64
+ const before = await ring();
65
+
66
+ // Pin a status — tier ring should tint.
67
+ await page.evaluate(() => {
68
+ sessionStorage.setItem('anet-topo-pinned-status', 'idle');
69
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'status', value: 'idle' } }));
70
+ });
71
+ await page.waitForTimeout(300);
72
+ const afterStatusPin = await ring();
73
+
74
+ // Clear via Esc — ring should revert.
75
+ await page.keyboard.press('Escape');
76
+ await page.waitForTimeout(250);
77
+ const afterEscStatus = await ring();
78
+
79
+ // Pin vendor — tier ring should tint again.
80
+ await page.locator('[data-vendor-letter="A"]').click();
81
+ await page.mouse.move(10, 10);
82
+ await page.waitForTimeout(250);
83
+ const afterVendorPin = await ring();
84
+
85
+ // Clear via Esc — back to neutral.
86
+ await page.keyboard.press('Escape');
87
+ await page.waitForTimeout(250);
88
+ const afterEscVendor = await ring();
89
+
90
+ await browser.close();
91
+
92
+ const results = {
93
+ before_neutral: before?.tinted === 'false',
94
+ before_strokeSet: typeof before?.stroke === 'string' && before.stroke.length > 0,
95
+ statusPin_tinted: afterStatusPin?.tinted === 'true',
96
+ statusPin_strokeDiffers: afterStatusPin?.stroke !== before?.stroke,
97
+ escStatus_revertsTinted: afterEscStatus?.tinted === 'false',
98
+ escStatus_strokeMatchesBaseline: afterEscStatus?.stroke === before?.stroke,
99
+ vendorPin_tinted: afterVendorPin?.tinted === 'true',
100
+ vendorPin_strokeDiffers: afterVendorPin?.stroke !== before?.stroke,
101
+ escVendor_revertsTinted: afterEscVendor?.tinted === 'false',
102
+ };
103
+ const ok = Object.values(results).every(Boolean);
104
+ console.log(`${ok ? '✅' : '❌'} tier-ring tint:`, JSON.stringify(results),
105
+ `\n before=`, before,
106
+ `\n afterStatusPin=`, afterStatusPin,
107
+ `\n afterEscStatus=`, afterEscStatus,
108
+ `\n afterVendorPin=`, afterVendorPin,
109
+ `\n afterEscVendor=`, afterEscVendor);
110
+ process.exit(ok ? 0 : 1);