@sleep2agi/agent-network-dashboard 0.4.6-preview.17 → 0.4.6-preview.170

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 (374) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +41 -41
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/routes-manifest.json +6 -0
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +2 -2
  17. package/.next/server/app/_not-found.rsc +12 -12
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  19. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  20. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  24. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  25. package/.next/server/app/admin.html +2 -2
  26. package/.next/server/app/admin.rsc +14 -14
  27. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  28. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  29. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  30. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  31. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  32. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  33. package/.next/server/app/api/hub/servers/route/app-paths-manifest.json +3 -0
  34. package/.next/server/app/api/hub/servers/route/build-manifest.json +9 -0
  35. package/.next/server/app/api/hub/servers/route/server-reference-manifest.json +4 -0
  36. package/.next/server/app/api/hub/servers/route.js +7 -0
  37. package/.next/server/app/api/hub/servers/route.js.map +5 -0
  38. package/.next/server/app/api/hub/servers/route.js.nft.json +1 -0
  39. package/.next/server/app/api/hub/servers/route_client-reference-manifest.js +3 -0
  40. package/.next/server/app/index.html +2 -2
  41. package/.next/server/app/index.rsc +14 -14
  42. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  43. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  44. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  45. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  46. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  47. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/login.html +2 -2
  49. package/.next/server/app/login.rsc +14 -14
  50. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  55. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  56. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/logs.html +2 -2
  58. package/.next/server/app/logs.rsc +14 -14
  59. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  60. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  61. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  62. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  64. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  65. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app/messages.html +2 -2
  67. package/.next/server/app/messages.rsc +14 -14
  68. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  69. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  70. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  71. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  73. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  74. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/node.html +2 -2
  76. package/.next/server/app/node.rsc +14 -14
  77. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  78. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  79. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  80. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  82. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  83. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/nodes.html +2 -2
  85. package/.next/server/app/nodes.rsc +14 -14
  86. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  87. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  88. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  89. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  90. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  91. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  92. package/.next/server/app/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/server-logs.html +2 -2
  95. package/.next/server/app/server-logs.rsc +14 -14
  96. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  97. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  98. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  99. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  100. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  101. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  102. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  103. package/.next/server/app/settings/networks.html +2 -2
  104. package/.next/server/app/settings/networks.rsc +14 -14
  105. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  106. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  107. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  108. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  109. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  110. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  111. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  112. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  113. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  114. package/.next/server/app/settings/tokens.html +2 -2
  115. package/.next/server/app/settings/tokens.rsc +14 -14
  116. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  117. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  118. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  119. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  120. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  121. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  122. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  123. package/.next/server/app/settings.html +2 -2
  124. package/.next/server/app/settings.rsc +14 -14
  125. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  126. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  127. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  128. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  130. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  131. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  132. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  133. package/.next/server/app/tasks.html +2 -2
  134. package/.next/server/app/tasks.rsc +14 -14
  135. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  136. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  137. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  138. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  139. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  140. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  141. package/.next/server/app-paths-manifest.json +1 -0
  142. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js +3 -0
  143. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js.map +1 -0
  144. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +3 -0
  145. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -0
  146. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
  147. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
  148. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  149. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  150. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
  151. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
  152. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -1
  153. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  154. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  155. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  156. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +1 -1
  157. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -1
  158. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  159. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  160. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
  161. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
  162. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
  163. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
  164. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
  165. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
  166. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
  167. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
  168. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
  169. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
  170. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
  171. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
  172. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  173. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  174. package/.next/server/middleware-build-manifest.js +3 -3
  175. package/.next/server/pages/404.html +2 -2
  176. package/.next/server/pages/500.html +1 -1
  177. package/.next/server/server-reference-manifest.js +1 -1
  178. package/.next/server/server-reference-manifest.json +1 -1
  179. package/.next/static/chunks/0-mpa_947ipeq.js +1 -0
  180. package/.next/static/chunks/01lmbsd37fybu.js +1 -0
  181. package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
  182. package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
  183. package/.next/static/chunks/050s9ndkk07i9.js +1 -0
  184. package/.next/static/chunks/05uk96gc~9mni.js +1 -0
  185. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  186. package/.next/static/chunks/0d7u.7_u.l9sh.js +4 -0
  187. package/.next/static/chunks/0ggj5ummw2a84.js +7 -0
  188. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  189. package/.next/static/chunks/0gvra4akqlit0.js +1 -0
  190. package/.next/static/chunks/0otgjhpse~amj.css +2 -0
  191. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  192. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  193. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  194. package/.next/trace +2 -2
  195. package/.next/trace-build +1 -1
  196. package/.next/types/routes.d.ts +2 -1
  197. package/.next/types/validator.ts +9 -0
  198. package/app/api/hub/servers/route.ts +39 -0
  199. package/app/components/ChatPopover.tsx +38 -2
  200. package/app/components/CommandPalette.tsx +107 -0
  201. package/app/components/HelpOverlay.tsx +126 -0
  202. package/app/components/ServersDrawer.tsx +64 -27
  203. package/app/components/TopoGraph.tsx +3338 -241
  204. package/app/components/utils.ts +8 -7
  205. package/app/globals.css +75 -5
  206. package/app/lib/time.ts +36 -0
  207. package/package.json +1 -1
  208. package/scripts/help-overlay-topo-test.mjs +44 -0
  209. package/scripts/servers-drawer-live-test.mjs +96 -0
  210. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  211. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  212. package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
  213. package/scripts/topo-active-links-chip-test.mjs +73 -0
  214. package/scripts/topo-active-links-freshness-test.mjs +113 -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-canvas-aria-test.mjs +108 -0
  220. package/scripts/topo-chat-focus-ring-test.mjs +77 -0
  221. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  222. package/scripts/topo-chip-focus-test.mjs +95 -0
  223. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  224. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  225. package/scripts/topo-click-ripple-test.mjs +66 -0
  226. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  227. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  228. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  229. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  230. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  231. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  232. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  233. package/scripts/topo-dispatch-pulse-test.mjs +83 -0
  234. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  235. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  236. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  237. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  238. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  239. package/scripts/topo-edge-focus-test.mjs +113 -0
  240. package/scripts/topo-edge-hover-test.mjs +93 -0
  241. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  242. package/scripts/topo-edge-pill-test.mjs +105 -0
  243. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  244. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  245. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  246. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  247. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  248. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  249. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  250. package/scripts/topo-fit-key-test.mjs +106 -0
  251. package/scripts/topo-freshness-chip-test.mjs +74 -0
  252. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  253. package/scripts/topo-group-count-test.mjs +68 -0
  254. package/scripts/topo-group-hover-test.mjs +82 -0
  255. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  256. package/scripts/topo-group-label-hover-test.mjs +119 -0
  257. package/scripts/topo-group-label-tint-test.mjs +99 -0
  258. package/scripts/topo-group-label-title-test.mjs +83 -0
  259. package/scripts/topo-group-pin-test.mjs +119 -0
  260. package/scripts/topo-group-status-pips-test.mjs +103 -0
  261. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  262. package/scripts/topo-groupbox-live-test.mjs +100 -0
  263. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  264. package/scripts/topo-groupbox-test.mjs +4 -2
  265. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  266. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  267. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  268. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  269. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  270. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  271. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  272. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  273. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  274. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  275. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  276. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  277. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  278. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  279. package/scripts/topo-hub-a11y-test.mjs +112 -0
  280. package/scripts/topo-hub-busyness-test.mjs +100 -0
  281. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  282. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  283. package/scripts/topo-hub-title-test.mjs +93 -0
  284. package/scripts/topo-hub-working-count-test.mjs +99 -0
  285. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  286. package/scripts/topo-label-lift-test.mjs +119 -0
  287. package/scripts/topo-label-scale-test.mjs +78 -0
  288. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  289. package/scripts/topo-layout-key-test.mjs +91 -0
  290. package/scripts/topo-layout-toggle-test.mjs +131 -0
  291. package/scripts/topo-legend-count-test.mjs +88 -0
  292. package/scripts/topo-legend-hover-test.mjs +113 -0
  293. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  294. package/scripts/topo-legend-pin-test.mjs +128 -0
  295. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  296. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  297. package/scripts/topo-legend-row-title-test.mjs +102 -0
  298. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  299. package/scripts/topo-minimap-test.mjs +68 -0
  300. package/scripts/topo-mobile-chips-test.mjs +103 -0
  301. package/scripts/topo-node-a11y-test.mjs +107 -0
  302. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  303. package/scripts/topo-node-title-peers-test.mjs +107 -0
  304. package/scripts/topo-node-title-test.mjs +101 -0
  305. package/scripts/topo-offline-dim-test.mjs +70 -0
  306. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  307. package/scripts/topo-orbit-busyness-test.mjs +96 -0
  308. package/scripts/topo-overlap-test.mjs +32 -5
  309. package/scripts/topo-pan-cursor-test.mjs +62 -0
  310. package/scripts/topo-panel-elevation-test.mjs +76 -0
  311. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  312. package/scripts/topo-particle-stagger-test.mjs +82 -0
  313. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  314. package/scripts/topo-pin-intersection-test.mjs +121 -0
  315. package/scripts/topo-pin-persistence-test.mjs +105 -0
  316. package/scripts/topo-popover-meta-test.mjs +78 -0
  317. package/scripts/topo-pressure-bar-test.mjs +69 -0
  318. package/scripts/topo-pressure-hover-test.mjs +123 -0
  319. package/scripts/topo-pressure-pin-test.mjs +119 -0
  320. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  321. package/scripts/topo-pulse-rate-test.mjs +71 -0
  322. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  323. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  324. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  325. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  326. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  327. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  328. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  329. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  330. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  331. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  332. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  333. package/scripts/topo-recent-row-title-test.mjs +112 -0
  334. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  335. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  336. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  337. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  338. package/scripts/topo-reduced-motion-test.mjs +77 -0
  339. package/scripts/topo-spoke-busyness-test.mjs +110 -0
  340. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  341. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  342. package/scripts/topo-svg-focus-test.mjs +112 -0
  343. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  344. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  345. package/scripts/topo-tier-ring-test.mjs +69 -0
  346. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  347. package/scripts/topo-tier-stagger-test.mjs +89 -0
  348. package/scripts/topo-tz-ghost-test.mjs +69 -0
  349. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  350. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  351. package/scripts/topo-vendor-pill-test.mjs +125 -0
  352. package/scripts/topo-vendor-pin-test.mjs +125 -0
  353. package/scripts/topo-wheel-zoom-test.mjs +79 -0
  354. package/scripts/topo-working-chip-pin-test.mjs +148 -0
  355. package/scripts/topo-working-halo-breath-test.mjs +86 -0
  356. package/scripts/topo-zoom-keys-test.mjs +89 -0
  357. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  358. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  359. package/.next/static/chunks/096hkjkwj4hb3.js +0 -1
  360. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  361. package/.next/static/chunks/0d6xn54cd2zo-.js +0 -1
  362. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  363. package/.next/static/chunks/0fqy_0wj_1xa7.js +0 -7
  364. package/.next/static/chunks/0iv2-b3po~sbg.js +0 -1
  365. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  366. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  367. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  368. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  369. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  370. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  371. package/.next/static/chunks/13vw-ubg-9pjp.css +0 -2
  372. /package/.next/static/{TBtREVf7o4zYSqGqq3QT5 → m3FtdjT1mdUIOaZp0o9gq}/_buildManifest.js +0 -0
  373. /package/.next/static/{TBtREVf7o4zYSqGqq3QT5 → m3FtdjT1mdUIOaZp0o9gq}/_clientMiddlewareManifest.js +0 -0
  374. /package/.next/static/{TBtREVf7o4zYSqGqq3QT5 → m3FtdjT1mdUIOaZp0o9gq}/_ssgManifest.js +0 -0
@@ -0,0 +1,74 @@
1
+ /* Round 46 verification: live · Ns freshness chip.
2
+ * - renders in the topology header with initial "live · 0s" or "live · 1s"
3
+ * - ticks up over time (>= 2s after a 2.5s wait)
4
+ * - resets near 0 when SWR delivers a new sessions payload (we trigger
5
+ * this by mutating the mocked /api/hub/status response — SWR's 5s
6
+ * refresh re-fetches and the chip should re-zero)
7
+ * - turns amber when stale (>10s since last sync; verified by blocking
8
+ * the second poll and waiting >12s) */
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
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 900 } });
15
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
16
+ await ctx.addInitScript(() => {
17
+ try {
18
+ localStorage.setItem('anet-theme', 'cyber');
19
+ localStorage.setItem('anet-topo-layout', 'grid');
20
+ sessionStorage.setItem('anet_v3_auth', '1');
21
+ } catch {}
22
+ });
23
+
24
+ let pollCount = 0;
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ pollCount++;
28
+ const r = await route.fetch();
29
+ const b = await r.json();
30
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
31
+ const sessions = [
32
+ { alias: `a${pollCount}`, status: 'idle', network_id: nid, project_dir: null,
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh },
34
+ { alias: 'b', 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
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
40
+ const page = await ctx.newPage();
41
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
42
+ await page.waitForSelector('[data-freshness-chip]', { timeout: 30000 });
43
+
44
+ const readChip = () => page.evaluate(() => {
45
+ const el = document.querySelector('[data-freshness-chip]');
46
+ if (!el) return null;
47
+ return {
48
+ text: (el.textContent || '').trim(),
49
+ isAmber: /amber|warning/i.test(el.className) || el.className.includes('amber'),
50
+ };
51
+ });
52
+
53
+ const initial = await readChip();
54
+ await page.waitForTimeout(2500);
55
+ const afterTick = await readChip();
56
+
57
+ // SWR refreshes every 5s — wait past one cycle and confirm the chip re-zeros.
58
+ // Initial chip might be 1s, after 2.5s should be 3s. After a ~5s sync,
59
+ // should drop to 0-1s.
60
+ await page.waitForTimeout(3500);
61
+ const afterSync = await readChip();
62
+
63
+ await browser.close();
64
+ const seconds = (s) => +(s.text.match(/(\d+)s/)?.[1] ?? -1);
65
+ const results = {
66
+ initialNearZero: initial !== null && seconds(initial) <= 2,
67
+ tickedUpAfter25s: afterTick !== null && seconds(afterTick) >= 2 && seconds(afterTick) <= 4,
68
+ reZeroedAfterSync: afterSync !== null && seconds(afterSync) <= 2,
69
+ pollsHappened: pollCount >= 2,
70
+ };
71
+ const ok = Object.values(results).every(Boolean);
72
+ console.log(`${ok ? '✅' : '❌'} freshness chip:`, JSON.stringify(results),
73
+ `\n initial=${JSON.stringify(initial)}\n afterTick=${JSON.stringify(afterTick)}\n afterSync=${JSON.stringify(afterSync)} polls=${pollCount}`);
74
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,95 @@
1
+ /* Round 68 verification: group-box rect has a 3-tier visual ladder:
2
+ * idle → strokeWidth 1.5, dashed, ringStroke colour
3
+ * hovered → strokeWidth 2, solid, legendAccent colour
4
+ * pinned → strokeWidth 3, solid, legendAccent colour (stronger)
5
+ *
6
+ * Plus `data-group-box-pinned` reflects the pinned state so external
7
+ * tooling can spot a pinned box without inspecting computed CSS.
8
+ */
9
+ import { chromium } from 'playwright';
10
+ import { readFileSync } from 'node:fs';
11
+
12
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
13
+ const browser = await chromium.launch({ headless: true });
14
+
15
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1500 } });
16
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
17
+ await ctx.addInitScript(() => {
18
+ try {
19
+ localStorage.setItem('anet-theme', 'cyber');
20
+ localStorage.setItem('anet-topo-layout', 'grid');
21
+ sessionStorage.setItem('anet_v3_auth', '1');
22
+ } catch {}
23
+ });
24
+
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 = [
31
+ { alias: 'alpha1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
32
+ { alias: 'alpha2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
33
+ { alias: 'alpha3', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
34
+ { alias: 'beta1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
35
+ { alias: 'beta2', status: 'idle', 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(() => document.querySelectorAll('g[data-node]').length === 5, { timeout: 30000 });
44
+ await page.waitForTimeout(600);
45
+
46
+ const readAlphaBox = () => page.evaluate(() => {
47
+ // Find the alpha group <g data-group="alpha"> and read its <rect>.
48
+ const g = document.querySelector('g[data-group="alpha"]');
49
+ if (!g) return null;
50
+ const rect = g.querySelector('rect');
51
+ if (!rect) return null;
52
+ return {
53
+ strokeWidth: rect.getAttribute('stroke-width'),
54
+ stroke: rect.getAttribute('stroke'),
55
+ dashArray: rect.getAttribute('stroke-dasharray'),
56
+ fillOpacity: rect.getAttribute('fill-opacity'),
57
+ pinnedAttr: rect.getAttribute('data-group-box-pinned'),
58
+ };
59
+ });
60
+
61
+ const idle = await readAlphaBox();
62
+
63
+ // Hover a member of alpha (alpha1) → group becomes hovered.
64
+ await page.locator('g[data-node="alpha1"]').hover();
65
+ await page.waitForTimeout(300);
66
+ const hovered = await readAlphaBox();
67
+ await page.mouse.move(10, 10);
68
+ await page.waitForTimeout(300);
69
+
70
+ // Pin alpha via group label click.
71
+ await page.locator('g[data-group-label-hit="alpha"]').first().click({ force: true });
72
+ await page.mouse.move(10, 10);
73
+ await page.waitForTimeout(300);
74
+ const pinned = await readAlphaBox();
75
+
76
+ await browser.close();
77
+
78
+ const results = {
79
+ idle_stroke15: idle && idle.strokeWidth === '1.5',
80
+ idle_dashed: idle && (idle.dashArray || '').startsWith('6'),
81
+ idle_pinnedFalse: idle && idle.pinnedAttr === 'false',
82
+ hovered_stroke2: hovered && hovered.strokeWidth === '2',
83
+ hovered_solid: hovered && (hovered.dashArray === 'none' || hovered.dashArray === null),
84
+ hovered_pinnedFalse: hovered && hovered.pinnedAttr === 'false',
85
+ pinned_stroke3: pinned && pinned.strokeWidth === '3',
86
+ pinned_solid: pinned && (pinned.dashArray === 'none' || pinned.dashArray === null),
87
+ pinned_pinnedTrue: pinned && pinned.pinnedAttr === 'true',
88
+ pinned_strongerFill: pinned && hovered && parseFloat(pinned.fillOpacity) > parseFloat(hovered.fillOpacity),
89
+ };
90
+ const ok = Object.values(results).every(Boolean);
91
+ console.log(`${ok ? '✅' : '❌'} group-box pinned state:`, JSON.stringify(results),
92
+ `\n idle=`, idle,
93
+ `\n hovered=`, hovered,
94
+ `\n pinned=`, pinned);
95
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,68 @@
1
+ /* Round 19 verification: each group-box label carries a member-count
2
+ * tspan ("· N"), reflecting the actual member count of the group. */
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
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
10
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
11
+ await ctx.addInitScript(() => {
12
+ try {
13
+ localStorage.setItem('anet-theme', 'cyber');
14
+ localStorage.removeItem('anet-brand');
15
+ localStorage.removeItem('anet-topo-view');
16
+ localStorage.setItem('anet-topo-layout', 'grid');
17
+ sessionStorage.setItem('anet_v3_auth', '1');
18
+ } catch {}
19
+ });
20
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
21
+ await ctx.route('**/api/hub/status*', async (route) => {
22
+ const r = await route.fetch();
23
+ const b = await r.json();
24
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
25
+ // A站 with 4 members, B站 with 2 members → 2 group boxes (counts 4 and 2).
26
+ const aliases = ['A站红', 'A站蓝', 'A站绿', 'A站黄', 'B站乙', 'B站丙'];
27
+ const sessions = aliases.map(a => ({
28
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
29
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
30
+ }));
31
+ await route.fulfill({ response: r, json: { ...b, sessions } });
32
+ });
33
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
34
+ const page = await ctx.newPage();
35
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
36
+ await page.waitForFunction(() => {
37
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
38
+ return !!svg && svg.querySelectorAll('g[data-group]').length === 2;
39
+ }, { timeout: 30000 }).catch(() => {});
40
+ await page.waitForTimeout(400);
41
+
42
+ const labels = await page.$$eval('svg[viewBox="0 0 1000 680"] g[data-group]', els => els.map(g => {
43
+ const text = g.querySelector('text');
44
+ const tspan = text.querySelector('tspan');
45
+ return {
46
+ key: g.getAttribute('data-group'),
47
+ full: text.textContent.replace(/\s+/g, ' ').trim(),
48
+ chipFontSize: tspan ? +tspan.getAttribute('font-size') : null,
49
+ chipFontWeight: tspan ? tspan.getAttribute('font-weight') : null,
50
+ };
51
+ }));
52
+
53
+ await browser.close();
54
+ const a = labels.find(l => l.key === 'A站');
55
+ const b = labels.find(l => l.key === 'B站');
56
+ // R58 appended status-mix pips inside the same <text>, and SVG textContent
57
+ // concatenates tspans without preserving the visual `dx` gaps — so what
58
+ // renders as "A站 · 4 4w" surfaces as the string "A站· 44w". The R19
59
+ // count chip is still the second tspan; verify by matching "Group·" then
60
+ // the count, allowing any trailing pip text.
61
+ const results = {
62
+ bothGroupsHaveChip: /^A站·\s*4/.test(a?.full || '') && /^B站·\s*2/.test(b?.full || ''),
63
+ chipSmallerFont: a?.chipFontSize === 11,
64
+ chipLighterWeight: a?.chipFontWeight === '400',
65
+ };
66
+ const ok = Object.values(results).every(Boolean);
67
+ console.log(`${ok ? '✅' : '❌'} group count chip:`, JSON.stringify(results), 'labels=', labels);
68
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,82 @@
1
+ /* Round 18 verification: hovering a node in a multi-member group upgrades
2
+ * the group-box to accent treatment (solid stroke, thicker, legendAccent
3
+ * colour) and brightens the label; other groups dim to 0.28; resting
4
+ * appearance unchanged. */
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
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
12
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
13
+ await ctx.addInitScript(() => {
14
+ try {
15
+ localStorage.setItem('anet-theme', 'cyber');
16
+ localStorage.removeItem('anet-brand');
17
+ localStorage.removeItem('anet-topo-view');
18
+ localStorage.setItem('anet-topo-layout', 'grid');
19
+ sessionStorage.setItem('anet_v3_auth', '1');
20
+ } catch {}
21
+ });
22
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
23
+ await ctx.route('**/api/hub/status*', async (route) => {
24
+ const r = await route.fetch();
25
+ const b = await r.json();
26
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
27
+ // Two prefix groups (A站 / B站), each ≥2 members so groupBoxes renders.
28
+ const aliases = ['A站红', 'A站蓝', 'A站绿', 'B站乙', 'B站丙', 'B站丁'];
29
+ const sessions = aliases.map(a => ({
30
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
31
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
32
+ }));
33
+ await route.fulfill({ response: r, json: { ...b, sessions } });
34
+ });
35
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
36
+ const page = await ctx.newPage();
37
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
38
+ await page.waitForFunction(() => {
39
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
40
+ return !!svg && svg.querySelectorAll('g[data-group]').length === 2;
41
+ }, { timeout: 30000 }).catch(() => {});
42
+ await page.waitForTimeout(400);
43
+
44
+ const readGroup = key => page.evaluate(k => {
45
+ const g = document.querySelector(`g[data-group="${k}"]`);
46
+ if (!g) return null;
47
+ const rect = g.querySelector('rect');
48
+ const text = g.querySelector('text');
49
+ return {
50
+ opacity: parseFloat(getComputedStyle(g).opacity || '1'),
51
+ stroke: rect.getAttribute('stroke'),
52
+ strokeWidth: +rect.getAttribute('stroke-width'),
53
+ strokeDasharray: rect.getAttribute('stroke-dasharray'),
54
+ fillOpacity: rect.getAttribute('fill-opacity'),
55
+ textFill: text.getAttribute('fill'),
56
+ };
57
+ }, key);
58
+
59
+ // Resting state (no hover)
60
+ const restA = await readGroup('A站');
61
+ const restB = await readGroup('B站');
62
+
63
+ // Hover a node in group A站
64
+ await page.locator('g[data-node="A站红"]').hover();
65
+ await page.waitForTimeout(350); // allow the 200ms transition to settle
66
+
67
+ const hoverA = await readGroup('A站');
68
+ const hoverB = await readGroup('B站');
69
+
70
+ await browser.close();
71
+ const results = {
72
+ restADashedThin: restA?.strokeDasharray === '6 6' && restA?.strokeWidth === 1.5,
73
+ restBDashedThin: restB?.strokeDasharray === '6 6' && restB?.strokeWidth === 1.5,
74
+ hoveredASolidThick: hoverA?.strokeDasharray === 'none' && hoverA?.strokeWidth === 2,
75
+ hoveredAAccentStroke: hoverA?.stroke === '#67e8f9', // dark legendAccent
76
+ hoveredABrighterText: hoverA?.textFill === '#e5e7eb', // dark legendHeadline
77
+ otherBDimmedOpacity: Math.abs((hoverB?.opacity ?? 1) - 0.28) < 0.02,
78
+ otherBKeepsDashed: hoverB?.strokeDasharray === '6 6',
79
+ };
80
+ const ok = Object.values(results).every(Boolean);
81
+ console.log(`${ok ? '✅' : '❌'} group hover linkage:`, JSON.stringify(results));
82
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,113 @@
1
+ /* Round 152 verification: group-label hit gains onKeyDown.
2
+ *
3
+ * R63 added role="button" + tabIndex=0 + aria-pressed to the
4
+ * group label hit-area, but never wired keyboard activation —
5
+ * focused users could Tab to the label but Enter / Space did
6
+ * nothing. R152 wires onKeyDown to fire the same setPinnedGroup
7
+ * setter onClick uses.
8
+ *
9
+ * This is the last keyboard gap among the role="button" surfaces;
10
+ * pressure-bar segs (R60), legend rows (R61), recent rows (R116),
11
+ * edge count badges (R121), chip-row chips (R136 / R139 / R140),
12
+ * vendor letters (R88), and nodes (R151) all had it. Group labels
13
+ * were the last hold-out.
14
+ *
15
+ * Fleet: 3 working sessions in grid layout to force group boxes
16
+ * (R85). Two prefix groups: agents-* (2) and infra-* (1).
17
+ *
18
+ * Path:
19
+ * 1. Focus agents group label → aria-pressed=false initially
20
+ * 2. Press Enter → aria-pressed=true (pinned)
21
+ * 3. Press Enter again → aria-pressed=false (released)
22
+ * 4. Press Space → aria-pressed=true (pinned)
23
+ * 5. Esc clears → aria-pressed=false
24
+ */
25
+ import { chromium } from 'playwright';
26
+ import { readFileSync } from 'node:fs';
27
+
28
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
29
+ const browser = await chromium.launch({ headless: true });
30
+
31
+ const ctx = await browser.newContext({ viewport: { width: 1600, height: 1500 } });
32
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
33
+ await ctx.addInitScript(() => {
34
+ try {
35
+ localStorage.setItem('anet-theme', 'cyber');
36
+ sessionStorage.setItem('anet_v3_auth', '1');
37
+ // group boxes only render in grid layout
38
+ localStorage.setItem('anet-topo-layout', 'grid');
39
+ } catch {}
40
+ });
41
+
42
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
43
+ await ctx.route('**/api/hub/status*', async (route) => {
44
+ const r = await route.fetch();
45
+ const b = await r.json();
46
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
47
+ const mk = (alias, status) => ({
48
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
49
+ network_id: nid, project_dir: null,
50
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
51
+ });
52
+ await route.fulfill({ response: r, json: { ...b, sessions: [
53
+ mk('agents-a1', 'working'), mk('agents-a2', 'working'),
54
+ mk('infra-b1', 'working'),
55
+ ] } });
56
+ });
57
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
58
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
59
+
60
+ const page = await ctx.newPage();
61
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
62
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
63
+ await page.waitForSelector('[data-group-label-hit]', { timeout: 10000 });
64
+ await page.waitForTimeout(400);
65
+
66
+ // Find the actual group keys (cluster prefix may differ)
67
+ const groupKeys = await page.evaluate(() =>
68
+ [...document.querySelectorAll('[data-group-label-hit]')].map(g => g.getAttribute('data-group-label-hit')));
69
+ const agentsKey = groupKeys.find(k => k.startsWith('agents')) || groupKeys[0];
70
+
71
+ const readAriaPressed = () => page.evaluate((k) => {
72
+ const g = document.querySelector(`[data-group-label-hit="${k}"]`);
73
+ return g?.getAttribute('aria-pressed');
74
+ }, agentsKey);
75
+
76
+ const before = await readAriaPressed();
77
+
78
+ // Focus the label and press Enter
79
+ await page.locator(`[data-group-label-hit="${agentsKey}"]`).focus();
80
+ await page.keyboard.press('Enter');
81
+ await page.waitForTimeout(200);
82
+ const afterEnter = await readAriaPressed();
83
+
84
+ // Press Enter again → toggle off
85
+ await page.keyboard.press('Enter');
86
+ await page.waitForTimeout(200);
87
+ const afterReEnter = await readAriaPressed();
88
+
89
+ // Press Space → pin again
90
+ await page.locator(`[data-group-label-hit="${agentsKey}"]`).focus();
91
+ await page.keyboard.press(' ');
92
+ await page.waitForTimeout(200);
93
+ const afterSpace = await readAriaPressed();
94
+
95
+ // Esc clears
96
+ await page.keyboard.press('Escape');
97
+ await page.waitForTimeout(200);
98
+ const afterEsc = await readAriaPressed();
99
+
100
+ await browser.close();
101
+
102
+ const results = {
103
+ before_notPinned: before === 'false',
104
+ enter_pins: afterEnter === 'true',
105
+ reEnter_releases: afterReEnter === 'false',
106
+ space_pins: afterSpace === 'true',
107
+ esc_clears: afterEsc === 'false',
108
+ };
109
+ const ok = Object.values(results).every(Boolean);
110
+ console.log(`${ok ? '✅' : '❌'} group label a11y:`, JSON.stringify(results),
111
+ `\n groupKey=${agentsKey}`,
112
+ `\n before=${before} enter=${afterEnter} reEnter=${afterReEnter} space=${afterSpace} esc=${afterEsc}`);
113
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,119 @@
1
+ /* Round 86 verification: hovering a group label previews the same
2
+ * dim mechanism as hovering a member node. R63 wired click-to-pin
3
+ * but forgot the hover preview — same R83 hover/click consistency
4
+ * gap. Now closed for group labels too.
5
+ *
6
+ * Fleet: 3 alpha + 2 beta in grid layout. Hover alpha label →
7
+ * alpha nodes stay bright, beta nodes dim. Hover beta label →
8
+ * inverse. Click alpha label → pin (R63 existing). Hover beta
9
+ * while alpha pinned → live transient preview of beta, alpha dims.
10
+ * Leave beta label → alpha pin restores (activeGroup =
11
+ * hoveredGroup ?? pinnedGroup).
12
+ */
13
+ import { chromium } from 'playwright';
14
+ import { readFileSync } from 'node:fs';
15
+
16
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
17
+ const browser = await chromium.launch({ headless: true });
18
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try {
22
+ localStorage.setItem('anet-theme', 'cyber');
23
+ localStorage.setItem('anet-topo-layout', 'grid');
24
+ sessionStorage.setItem('anet_v3_auth', '1');
25
+ sessionStorage.removeItem('anet-topo-pinned-status');
26
+ sessionStorage.removeItem('anet-topo-pinned-group');
27
+ } catch {}
28
+ });
29
+
30
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
31
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ const mk = (alias) => ({
36
+ alias, status: 'idle', model: 'claude-opus-4', 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 = [mk('alpha1'), mk('alpha2'), mk('alpha3'), mk('beta1'), mk('beta2')];
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('[data-group-label-hit]').length >= 2, { timeout: 30000 });
48
+ await page.waitForTimeout(400);
49
+
50
+ const ops = () => page.evaluate(() => {
51
+ const o = {};
52
+ for (const a of ['alpha1', 'alpha2', 'alpha3', 'beta1', 'beta2']) {
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 ops();
60
+
61
+ // Hover the alpha label — alphas should stay bright, betas dim.
62
+ await page.locator('[data-group-label-hit="alpha"]').hover();
63
+ await page.waitForTimeout(250);
64
+ const onHoverAlpha = await ops();
65
+
66
+ // Leave (move mouse far away) — baseline restores.
67
+ await page.mouse.move(10, 10);
68
+ await page.waitForTimeout(250);
69
+ const afterLeaveAlpha = await ops();
70
+
71
+ // Hover beta label — inverse.
72
+ await page.locator('[data-group-label-hit="beta"]').hover();
73
+ await page.waitForTimeout(250);
74
+ const onHoverBeta = await ops();
75
+
76
+ await page.mouse.move(10, 10);
77
+ await page.waitForTimeout(250);
78
+
79
+ // Click alpha label to pin → alpha pinned (R63 behaviour preserved).
80
+ await page.locator('[data-group-label-hit="alpha"]').click();
81
+ await page.mouse.move(10, 10);
82
+ await page.waitForTimeout(250);
83
+ const afterPinAlpha = await ops();
84
+
85
+ // Hover beta while alpha is pinned — transient preview wins.
86
+ await page.locator('[data-group-label-hit="beta"]').hover();
87
+ await page.waitForTimeout(250);
88
+ const onHoverBetaWhilePin = await ops();
89
+
90
+ // Leave beta — alpha pin restores.
91
+ await page.mouse.move(10, 10);
92
+ await page.waitForTimeout(250);
93
+ const afterLeaveBetaWhilePin = await ops();
94
+
95
+ await browser.close();
96
+
97
+ const bright = (v) => v != null && v >= 0.55;
98
+ const dim = (v) => v != null && v < 0.4;
99
+ const results = {
100
+ before_baseline: bright(before.alpha1) && bright(before.beta1),
101
+ hoverAlpha_alphasBright: bright(onHoverAlpha.alpha1) && bright(onHoverAlpha.alpha2) && bright(onHoverAlpha.alpha3),
102
+ hoverAlpha_betasDim: dim(onHoverAlpha.beta1) && dim(onHoverAlpha.beta2),
103
+ leaveAlpha_restores: bright(afterLeaveAlpha.alpha1) && bright(afterLeaveAlpha.beta1),
104
+ hoverBeta_betasBright: bright(onHoverBeta.beta1) && bright(onHoverBeta.beta2),
105
+ hoverBeta_alphasDim: dim(onHoverBeta.alpha1) && dim(onHoverBeta.alpha2),
106
+ pinAlpha_keepsAlpha: bright(afterPinAlpha.alpha1) && bright(afterPinAlpha.alpha2),
107
+ pinAlpha_dimsBeta: dim(afterPinAlpha.beta1) && dim(afterPinAlpha.beta2),
108
+ hoverBetaPin_transient: bright(onHoverBetaWhilePin.beta1) && dim(onHoverBetaWhilePin.alpha1),
109
+ leaveBetaPin_alphaRestored: bright(afterLeaveBetaWhilePin.alpha1) && dim(afterLeaveBetaWhilePin.beta1),
110
+ };
111
+ const ok = Object.values(results).every(Boolean);
112
+ console.log(`${ok ? '✅' : '❌'} group-label hover:`, JSON.stringify(results),
113
+ `\n before=`, before,
114
+ `\n onHoverAlpha=`, onHoverAlpha,
115
+ `\n onHoverBeta=`, onHoverBeta,
116
+ `\n afterPinAlpha=`, afterPinAlpha,
117
+ `\n onHoverBetaPin=`, onHoverBetaWhilePin,
118
+ `\n leaveBetaPin=`, afterLeaveBetaWhilePin);
119
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,99 @@
1
+ /* Round 107 verification: group-label hit `<rect>` picks up a tint
2
+ * on hover and pin — extends the R104 (recent-signal) / R105
3
+ * (legend) list-item-tint pattern to the SVG group labels. The tint
4
+ * is pal.legendAccent (cyan) since groups don't carry an inherent
5
+ * swatch; same colour family R68 already uses for hovered/pinned
6
+ * group-box strokes.
7
+ *
8
+ * Verifies idle / hover / pinned via the new data-group-label-tinted
9
+ * attribute + fill/opacity. Pin opacity stronger than hover.
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', 'grid');
23
+ sessionStorage.setItem('anet_v3_auth', '1');
24
+ sessionStorage.removeItem('anet-topo-pinned-group');
25
+ } catch {}
26
+ });
27
+
28
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
29
+ await ctx.route('**/api/hub/status*', async (route) => {
30
+ const r = await route.fetch();
31
+ const b = await r.json();
32
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
33
+ const mk = (alias) => ({
34
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
35
+ network_id: nid, project_dir: null,
36
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
37
+ });
38
+ await route.fulfill({ response: r, json: { ...b, sessions: [
39
+ mk('alpha1'), mk('alpha2'), mk('alpha3'),
40
+ mk('beta1'), mk('beta2'),
41
+ ] } });
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('[data-group-label-hit]').length >= 2, { timeout: 30000 });
48
+ await page.waitForTimeout(400);
49
+
50
+ const readLabel = (key) => page.evaluate((k) => {
51
+ const g = document.querySelector(`[data-group-label-hit="${k}"]`);
52
+ if (!g) return null;
53
+ const rect = g.querySelector('rect');
54
+ return {
55
+ tinted: rect?.getAttribute('data-group-label-tinted') || '',
56
+ fill: rect?.getAttribute('fill') || '',
57
+ opacity: rect?.getAttribute('opacity') || '',
58
+ };
59
+ }, key);
60
+
61
+ const beforeAlpha = await readLabel('alpha');
62
+ const beforeBeta = await readLabel('beta');
63
+
64
+ // Hover alpha label.
65
+ await page.locator('[data-group-label-hit="alpha"]').hover();
66
+ await page.waitForTimeout(250);
67
+ const onHoverAlpha = await readLabel('alpha');
68
+ const onHoverBeta = await readLabel('beta');
69
+
70
+ // Move pointer far away.
71
+ await page.mouse.move(10, 10);
72
+ await page.waitForTimeout(250);
73
+
74
+ // Pin beta.
75
+ await page.locator('[data-group-label-hit="beta"]').click();
76
+ await page.mouse.move(10, 10);
77
+ await page.waitForTimeout(250);
78
+ const pinBeta = await readLabel('beta');
79
+ const pinAlphaUnchanged = await readLabel('alpha');
80
+
81
+ await browser.close();
82
+
83
+ const opacity = (s) => parseFloat(s) || 0;
84
+ const results = {
85
+ before_alphaTransparent: beforeAlpha?.tinted === 'none' && beforeAlpha?.fill === 'transparent',
86
+ before_betaTransparent: beforeBeta?.tinted === 'none' && beforeBeta?.fill === 'transparent',
87
+ hover_alphaTinted: onHoverAlpha?.tinted === 'hover' && onHoverAlpha?.fill !== 'transparent',
88
+ hover_alphaOpacityLow: opacity(onHoverAlpha?.opacity || '') > 0 && opacity(onHoverAlpha?.opacity || '') < 0.2,
89
+ hover_betaUnchanged: onHoverBeta?.tinted === 'none',
90
+ pin_betaPinned: pinBeta?.tinted === 'pinned' && pinBeta?.fill !== 'transparent',
91
+ pin_strongerThanHover: opacity(pinBeta?.opacity || '') > opacity(onHoverAlpha?.opacity || ''),
92
+ pin_alphaClean: pinAlphaUnchanged?.tinted === 'none',
93
+ };
94
+ const ok = Object.values(results).every(Boolean);
95
+ console.log(`${ok ? '✅' : '❌'} group-label tint:`, JSON.stringify(results),
96
+ `\n beforeAlpha=`, beforeAlpha,
97
+ `\n onHoverAlpha=`, onHoverAlpha,
98
+ `\n pinBeta=`, pinBeta);
99
+ process.exit(ok ? 0 : 1);