@sleep2agi/agent-network-dashboard 0.4.6-preview.26 → 0.4.6-preview.261

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 (459) 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/00.pi0.850kxg.js +1 -0
  181. package/.next/static/chunks/01lmbsd37fybu.js +1 -0
  182. package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
  183. package/.next/static/chunks/03~~oirxz7~vc.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/08y27uzl7w6bx.css +2 -0
  187. package/.next/static/chunks/0ggj5ummw2a84.js +7 -0
  188. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  189. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  190. package/.next/static/chunks/0s64vw8kd5rn7.js +1 -0
  191. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  192. package/.next/static/chunks/11drzen-wfaop.js +4 -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 +5291 -237
  204. package/app/components/utils.ts +8 -7
  205. package/app/globals.css +110 -5
  206. package/app/lib/time.ts +36 -0
  207. package/package.json +2 -2
  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-self-hover-test.mjs +171 -0
  214. package/scripts/topo-active-links-chip-test.mjs +73 -0
  215. package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
  216. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  217. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  218. package/scripts/topo-active-pulse-ease-test.mjs +120 -0
  219. package/scripts/topo-arrival-ping-test.mjs +87 -0
  220. package/scripts/topo-auto-fit-test.mjs +73 -0
  221. package/scripts/topo-canvas-aria-test.mjs +108 -0
  222. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  223. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  224. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  225. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  226. package/scripts/topo-chip-focus-test.mjs +95 -0
  227. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  228. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  229. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  230. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  231. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  232. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  233. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  234. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  235. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  236. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  237. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  238. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  239. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  240. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  241. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  242. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  243. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  244. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  245. package/scripts/topo-dispatch-pulse-test.mjs +83 -0
  246. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  247. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  248. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  249. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  250. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  251. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  252. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  253. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  254. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  255. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  256. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  257. package/scripts/topo-edge-fadein-test.mjs +106 -0
  258. package/scripts/topo-edge-focus-test.mjs +113 -0
  259. package/scripts/topo-edge-hover-test.mjs +93 -0
  260. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  261. package/scripts/topo-edge-pill-test.mjs +105 -0
  262. package/scripts/topo-edge-smil-sync-test.mjs +148 -0
  263. package/scripts/topo-edge-smil-theme-ease-test.mjs +97 -0
  264. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  265. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  266. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  267. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  268. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  269. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  270. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  271. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  272. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  273. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  274. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  275. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  276. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  277. package/scripts/topo-fit-key-test.mjs +106 -0
  278. package/scripts/topo-freshness-chip-test.mjs +74 -0
  279. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  280. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  281. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  282. package/scripts/topo-group-count-test.mjs +68 -0
  283. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  284. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  285. package/scripts/topo-group-label-hover-test.mjs +119 -0
  286. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  287. package/scripts/topo-group-label-tint-test.mjs +99 -0
  288. package/scripts/topo-group-label-title-test.mjs +83 -0
  289. package/scripts/topo-group-pin-test.mjs +119 -0
  290. package/scripts/topo-group-pip-fadein-test.mjs +100 -0
  291. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  292. package/scripts/topo-group-status-pips-test.mjs +103 -0
  293. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  294. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  295. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  296. package/scripts/topo-groupbox-live-test.mjs +100 -0
  297. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  298. package/scripts/topo-groupbox-test.mjs +4 -2
  299. package/scripts/topo-halo-breath-ease-test.mjs +92 -0
  300. package/scripts/topo-halo-breath-stagger-test.mjs +89 -0
  301. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  302. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  303. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  304. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  305. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  306. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  307. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  308. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  309. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  310. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  311. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  312. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  313. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  314. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  315. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  316. package/scripts/topo-hub-a11y-test.mjs +112 -0
  317. package/scripts/topo-hub-busyness-test.mjs +100 -0
  318. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  319. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  320. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  321. package/scripts/topo-hub-fadein-test.mjs +88 -0
  322. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  323. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  324. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  325. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  326. package/scripts/topo-hub-title-test.mjs +93 -0
  327. package/scripts/topo-hub-working-count-test.mjs +99 -0
  328. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  329. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  330. package/scripts/topo-label-lift-test.mjs +119 -0
  331. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  332. package/scripts/topo-layout-key-test.mjs +91 -0
  333. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  334. package/scripts/topo-layout-toggle-test.mjs +131 -0
  335. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  336. package/scripts/topo-legend-count-test.mjs +88 -0
  337. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  338. package/scripts/topo-legend-hover-test.mjs +113 -0
  339. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  340. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  341. package/scripts/topo-legend-pin-test.mjs +128 -0
  342. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  343. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  344. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  345. package/scripts/topo-legend-row-title-test.mjs +102 -0
  346. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  347. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  348. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  349. package/scripts/topo-minimap-test.mjs +68 -0
  350. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  351. package/scripts/topo-mobile-chips-test.mjs +103 -0
  352. package/scripts/topo-node-a11y-test.mjs +107 -0
  353. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  354. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  355. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  356. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  357. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  358. package/scripts/topo-node-title-peers-test.mjs +107 -0
  359. package/scripts/topo-node-title-test.mjs +101 -0
  360. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  361. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  362. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  363. package/scripts/topo-orbit-busyness-test.mjs +96 -0
  364. package/scripts/topo-orbit-opacity-bucket-test.mjs +124 -0
  365. package/scripts/topo-overlap-test.mjs +32 -5
  366. package/scripts/topo-pan-cursor-test.mjs +62 -0
  367. package/scripts/topo-panel-elevation-test.mjs +76 -0
  368. package/scripts/topo-panel-fadein-test.mjs +102 -0
  369. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  370. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  371. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  372. package/scripts/topo-particle-stagger-test.mjs +82 -0
  373. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  374. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  375. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  376. package/scripts/topo-pin-intersection-test.mjs +121 -0
  377. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  378. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  379. package/scripts/topo-pin-persistence-test.mjs +105 -0
  380. package/scripts/topo-ping-pulse-ease-test.mjs +96 -0
  381. package/scripts/topo-popover-meta-test.mjs +78 -0
  382. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  383. package/scripts/topo-pressure-bar-test.mjs +69 -0
  384. package/scripts/topo-pressure-hover-test.mjs +123 -0
  385. package/scripts/topo-pressure-pin-test.mjs +119 -0
  386. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  387. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  388. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  389. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  390. package/scripts/topo-pulse-rate-test.mjs +71 -0
  391. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  392. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  393. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  394. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  395. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  396. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  397. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  398. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  399. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  400. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  401. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  402. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  403. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  404. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  405. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  406. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  407. package/scripts/topo-recent-row-title-test.mjs +112 -0
  408. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  409. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  410. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  411. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  412. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  413. package/scripts/topo-reduced-motion-test.mjs +77 -0
  414. package/scripts/topo-reset-spin-test.mjs +120 -0
  415. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  416. package/scripts/topo-spoke-busyness-test.mjs +110 -0
  417. package/scripts/topo-spoke-tint-test.mjs +96 -0
  418. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  419. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  420. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  421. package/scripts/topo-svg-focus-test.mjs +112 -0
  422. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  423. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  424. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  425. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  426. package/scripts/topo-tier-ring-test.mjs +69 -0
  427. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  428. package/scripts/topo-tier-stagger-test.mjs +89 -0
  429. package/scripts/topo-tz-ghost-test.mjs +69 -0
  430. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  431. package/scripts/topo-vendor-chip-self-hover-test.mjs +130 -0
  432. package/scripts/topo-vendor-chip-tabular-test.mjs +89 -0
  433. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  434. package/scripts/topo-vendor-pill-test.mjs +125 -0
  435. package/scripts/topo-vendor-pin-test.mjs +125 -0
  436. package/scripts/topo-view-smooth-test.mjs +131 -0
  437. package/scripts/topo-wheel-zoom-test.mjs +79 -0
  438. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  439. package/scripts/topo-working-halo-breath-test.mjs +86 -0
  440. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  441. package/scripts/topo-zoom-keys-test.mjs +89 -0
  442. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  443. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  444. package/.next/static/chunks/09nd.t_hd.y6t.css +0 -2
  445. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  446. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  447. package/.next/static/chunks/0fqy_0wj_1xa7.js +0 -7
  448. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  449. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  450. package/.next/static/chunks/0jzqhqbydbrm~.js +0 -1
  451. package/.next/static/chunks/0lvk7f2_5y8b3.js +0 -1
  452. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  453. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  454. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  455. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  456. package/.next/static/chunks/12maqvd_4t3sh.js +0 -1
  457. /package/.next/static/{iC9JNLo0IdNSaeynEneGT → -JO9tjmCkno3inrj-XBxj}/_buildManifest.js +0 -0
  458. /package/.next/static/{iC9JNLo0IdNSaeynEneGT → -JO9tjmCkno3inrj-XBxj}/_clientMiddlewareManifest.js +0 -0
  459. /package/.next/static/{iC9JNLo0IdNSaeynEneGT → -JO9tjmCkno3inrj-XBxj}/_ssgManifest.js +0 -0
@@ -0,0 +1,88 @@
1
+ /* Round 95 verification: legend rows show live counts. Fleet = 3
2
+ * working + 2 idle + 1 offline. Each row's data-legend-count
3
+ * attribute matches the expected number. Pinning a row brightens
4
+ * its count alongside the label.
5
+ *
6
+ * The count is right-aligned at x=215 (after the flow-arrow swatch
7
+ * which spans x=140-196 on the offline row), pointerEvents:none so
8
+ * the row's R55 hover affordance still hits.
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
+ const ctx = await browser.newContext({ viewport: { width: 1400, 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', 'ring');
21
+ sessionStorage.setItem('anet_v3_auth', '1');
22
+ sessionStorage.removeItem('anet-topo-pinned-status');
23
+ } catch {}
24
+ });
25
+
26
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
27
+ const stale10 = new Date(Date.now() - 10 * 60 * 1000).toISOString();
28
+ await ctx.route('**/api/hub/status*', async (route) => {
29
+ const r = await route.fetch();
30
+ const b = await r.json();
31
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
32
+ const mk = (alias, status, ts) => ({
33
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
34
+ network_id: nid, project_dir: null,
35
+ created_at: ts, updated_at: ts, last_seen_at: ts,
36
+ });
37
+ const sessions = [
38
+ mk('wkr1', 'working', fresh),
39
+ mk('wkr2', 'working', fresh),
40
+ mk('wkr3', 'working', fresh),
41
+ mk('idl1', 'idle', fresh),
42
+ mk('idl2', 'idle', fresh),
43
+ mk('off1', 'offline', stale10),
44
+ ];
45
+ await route.fulfill({ response: r, json: { ...b, sessions } });
46
+ });
47
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
48
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 6, { timeout: 30000 });
52
+ await page.waitForTimeout(400);
53
+
54
+ const counts = () => page.evaluate(() => ({
55
+ working: document.querySelector('[data-legend-count="working"]')?.textContent,
56
+ idle: document.querySelector('[data-legend-count="idle"]')?.textContent,
57
+ offline: document.querySelector('[data-legend-count="offline"]')?.textContent,
58
+ }));
59
+ const opacities = () => page.evaluate(() => ({
60
+ working: +(document.querySelector('[data-legend-count="working"]')?.getAttribute('opacity') || '0'),
61
+ idle: +(document.querySelector('[data-legend-count="idle"]')?.getAttribute('opacity') || '0'),
62
+ offline: +(document.querySelector('[data-legend-count="offline"]')?.getAttribute('opacity') || '0'),
63
+ }));
64
+
65
+ const c = await counts();
66
+ const opsRest = await opacities();
67
+
68
+ // Pin the working row — its count should brighten.
69
+ await page.locator('[data-legend-status="working"]').click();
70
+ await page.waitForTimeout(250);
71
+ const opsPinned = await opacities();
72
+
73
+ await browser.close();
74
+
75
+ const results = {
76
+ workingCount_3: c.working === '3',
77
+ idleCount_2: c.idle === '2',
78
+ offlineCount_1: c.offline === '1',
79
+ resting_dim: opsRest.working < 0.7 && opsRest.idle < 0.7 && opsRest.offline < 0.7,
80
+ pinnedWorking_bright: opsPinned.working >= 0.9,
81
+ pinnedWorking_othersUnchanged: opsPinned.idle < 0.7 && opsPinned.offline < 0.7,
82
+ };
83
+ const ok = Object.values(results).every(Boolean);
84
+ console.log(`${ok ? '✅' : '❌'} legend counts:`, JSON.stringify(results),
85
+ `\n counts=`, c,
86
+ `\n opsRest=`, opsRest,
87
+ `\n opsPinned=`, opsPinned);
88
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,121 @@
1
+ /* Round 239 verification: legend count text fill picks up the
2
+ * row's tier colour on hover/pin, completing the hover-deepen-own-
3
+ * hue idiom for the legend row triple (swatch + label + count).
4
+ *
5
+ * Pre-R239 the count digit had opacity 0.65→0.95 on hover but
6
+ * stayed at pal.legendText (neutral gray) for fill. The swatch
7
+ * (R197 grow) and label (R55 brighten) both reacted to hover with
8
+ * tier-identity signals; only the count stayed gray. R239 adds
9
+ * fill transition to row.fill (green/teal/slate) on hover so the
10
+ * row reads as one tier-coloured unit.
11
+ *
12
+ * Empty tiers (row.count === 0) stay at pal.legendText regardless
13
+ * of hover — empty doesn't get to claim tier identity.
14
+ *
15
+ * Test scope:
16
+ * - rest state: count fill === neutral (pal.legendText)
17
+ * - hover state (Playwright .hover() on the legend row <g>):
18
+ * count fill === row.fill (tier colour)
19
+ * - data-legend-count-fill attr flips 'neutral' ↔ 'tier'
20
+ * - transition list includes both opacity AND fill at 150ms
21
+ *
22
+ * Scenario: 4 working agents → working tier has count=4 (populated),
23
+ * idle + offline tiers stay at 0 (empty, never tier-coloured).
24
+ * Hover the working legend row, verify only that row's count
25
+ * flips to tier colour.
26
+ */
27
+ import { chromium } from 'playwright';
28
+ import { readFileSync } from 'node:fs';
29
+
30
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
31
+ const browser = await chromium.launch({ headless: true });
32
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
33
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
34
+ await ctx.addInitScript(() => {
35
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
36
+ });
37
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
38
+ await ctx.route('**/api/hub/status*', async (route) => {
39
+ const r = await route.fetch();
40
+ const b = await r.json();
41
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
42
+ const mk = (alias) => ({
43
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
44
+ network_id: nid, project_dir: null,
45
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
46
+ });
47
+ await route.fulfill({ response: r, json: { ...b, sessions: [
48
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
49
+ ] } });
50
+ });
51
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
52
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
53
+
54
+ const page = await ctx.newPage();
55
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
56
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
57
+ await page.waitForSelector('[data-legend-count="working"]', { timeout: 10000, state: 'attached' });
58
+ await page.waitForTimeout(300);
59
+
60
+ const probeCount = (page, tier) => page.evaluate((t) => {
61
+ const text = document.querySelector(`[data-legend-count="${t}"]`);
62
+ if (!text) return null;
63
+ const cs = getComputedStyle(text);
64
+ return {
65
+ fillAttr: text.getAttribute('fill'),
66
+ fillComp: cs.fill,
67
+ empty: text.getAttribute('data-legend-count-empty'),
68
+ fillState: text.getAttribute('data-legend-count-fill'),
69
+ transition: cs.transition,
70
+ text: (text.textContent || '').trim(),
71
+ };
72
+ }, tier);
73
+
74
+ // REST STATE — no hover
75
+ const rest = await probeCount(page, 'working');
76
+
77
+ // HOVER STATE — hover the working legend row group
78
+ await page.locator('[data-legend-status="working"]').first().hover({ force: true });
79
+ await page.waitForTimeout(400);
80
+ const hover = await probeCount(page, 'working');
81
+
82
+ // Idle tier is empty in this scenario — should stay neutral even
83
+ // when hovered. Hover the idle row to confirm the empty-tier
84
+ // exclusion holds.
85
+ await page.locator('[data-legend-status="idle"]').first().hover({ force: true });
86
+ await page.waitForTimeout(400);
87
+ const idleHover = await probeCount(page, 'idle');
88
+
89
+ await browser.close();
90
+
91
+ // Browser normalises '150ms' → '0.15s' in computed-style transitions.
92
+ // Accept either form.
93
+ const hasTransition = (s, prop) => new RegExp(`${prop}\\s+(?:150ms|0\\.15s)`).test(s || '');
94
+ // Cyber theme working tier fill is #22c55e = rgb(34, 197, 94)
95
+ const tierColorRgb = 'rgb(34, 197, 94)';
96
+ // Cyber theme legendText (pal.legendText for dark palette) = #94a3b8 = rgb(148, 163, 184)
97
+ const neutralRgb = 'rgb(148, 163, 184)';
98
+
99
+ const results = {
100
+ rest_present: rest !== null,
101
+ rest_fill_state_neutral: rest?.fillState === 'neutral',
102
+ rest_fill_is_neutral: rest?.fillComp === neutralRgb,
103
+ rest_text_is_4: rest?.text === '4',
104
+ rest_transition_has_fill: hasTransition(rest?.transition, 'fill'),
105
+ rest_transition_has_opacity: hasTransition(rest?.transition, 'opacity'),
106
+
107
+ hover_fill_state_tier: hover?.fillState === 'tier',
108
+ hover_fill_is_tier: hover?.fillComp === tierColorRgb,
109
+ hover_text_is_4: hover?.text === '4',
110
+
111
+ // Empty tier (idle, count=0) stays neutral even on hover
112
+ idle_empty: idleHover?.empty === 'true',
113
+ idle_stays_neutral: idleHover?.fillState === 'neutral',
114
+ idle_fill_still_neutral: idleHover?.fillComp === neutralRgb,
115
+ };
116
+ const ok = Object.values(results).every(Boolean);
117
+ console.log(`${ok ? '✅' : '❌'} legend count tier fill:`, JSON.stringify(results),
118
+ '\n rest: ', rest,
119
+ '\n hover: ', hover,
120
+ '\n idleHover:', idleHover);
121
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,113 @@
1
+ /* Round 55 verification: hovering a legend status row dims nodes whose
2
+ * status doesn't match.
3
+ *
4
+ * Sessions (mix of statuses):
5
+ * wkr — working (status=working)
6
+ * idl1 — idle online (status=idle)
7
+ * idl2 — idle online (status=idle)
8
+ * off — offline (status=offline, last_seen old)
9
+ *
10
+ * Hover the "working node" row → wkr stays bright (~1.0), the rest dim
11
+ * (~0.28). Hover "online idle" → idl1/idl2 stay, wkr+off dim. Hover
12
+ * "offline / no SSE" → off stays, the three online dim. Release →
13
+ * all back to base.
14
+ */
15
+ import { chromium } from 'playwright';
16
+ import { readFileSync } from 'node:fs';
17
+
18
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
19
+ const browser = await chromium.launch({ headless: true });
20
+
21
+ // Dashboard chrome pushes the SVG ~830 px down at width 1400, so the
22
+ // legend's offline row sits at screen y≈900 — below a 900-px viewport.
23
+ // 1500 px tall gives a comfortable margin for all three rows.
24
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript(() => {
27
+ try {
28
+ localStorage.setItem('anet-theme', 'cyber');
29
+ localStorage.setItem('anet-topo-layout', 'ring');
30
+ sessionStorage.setItem('anet_v3_auth', '1');
31
+ } catch {}
32
+ });
33
+
34
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
35
+ const stale = new Date(Date.now() - 30 * 60 * 1000).toISOString();
36
+ await ctx.route('**/api/hub/status*', async (route) => {
37
+ const r = await route.fetch();
38
+ const b = await r.json();
39
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
40
+ const sessions = [
41
+ { alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
42
+ { alias: 'idl1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
43
+ { alias: 'idl2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
44
+ { alias: 'off', status: 'offline', network_id: nid, project_dir: null, created_at: stale, updated_at: stale, last_seen_at: stale },
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(500);
54
+
55
+ const opacities = () => page.evaluate(() => {
56
+ const o = {};
57
+ for (const a of ['wkr', 'idl1', 'idl2', 'off']) {
58
+ const g = document.querySelector(`g[data-node="${a}"]`);
59
+ o[a] = g ? +(g.style.opacity || '1') : null;
60
+ }
61
+ return o;
62
+ });
63
+
64
+ const before = await opacities();
65
+
66
+ async function hoverRow(key) {
67
+ const target = await page.evaluate((k) => {
68
+ const g = document.querySelector(`g[data-legend-status="${k}"]`);
69
+ if (!g) return null;
70
+ const r = g.getBoundingClientRect();
71
+ return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
72
+ }, key);
73
+ if (!target) throw new Error(`legend row ${key} not found`);
74
+ // jiggle from a neutral spot, then to the target
75
+ await page.mouse.move(10, 10);
76
+ await page.mouse.move(target.x, target.y);
77
+ await page.waitForTimeout(300);
78
+ return await opacities();
79
+ }
80
+
81
+ const onWorking = await hoverRow('working');
82
+ const onIdle = await hoverRow('idle');
83
+ const onOffline = await hoverRow('offline');
84
+ await page.mouse.move(10, 10);
85
+ await page.waitForTimeout(300);
86
+ const released = await opacities();
87
+
88
+ await browser.close();
89
+
90
+ const dim = (v) => v != null && v < 0.4;
91
+ const bright = (v) => v != null && v >= 0.55;
92
+ const results = {
93
+ before_baseline: bright(before.wkr) && bright(before.idl1) && bright(before.idl2),
94
+ working_keepsWorking: bright(onWorking.wkr),
95
+ working_dimsIdle: dim(onWorking.idl1) && dim(onWorking.idl2),
96
+ working_dimsOffline: dim(onWorking.off),
97
+ idle_keepsIdle: bright(onIdle.idl1) && bright(onIdle.idl2),
98
+ idle_dimsWorking: dim(onIdle.wkr),
99
+ idle_dimsOffline: dim(onIdle.off),
100
+ // Offline base opacity is 0.6 (R17), so "kept" means >= 0.55 which the
101
+ // bright() helper already covers — anything in [0.55, 1] counts.
102
+ offline_keepsOffline: bright(onOffline.off),
103
+ offline_dimsOnline: dim(onOffline.wkr) && dim(onOffline.idl1) && dim(onOffline.idl2),
104
+ released_restoresAll: bright(released.wkr) && bright(released.idl1) && bright(released.idl2),
105
+ };
106
+ const ok = Object.values(results).every(Boolean);
107
+ console.log(`${ok ? '✅' : '❌'} legend status hover:`, JSON.stringify(results),
108
+ `\n before=`, before,
109
+ `\n onWorking=`, onWorking,
110
+ `\n onIdle=`, onIdle,
111
+ `\n onOffline=`, onOffline,
112
+ `\n released=`, released);
113
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,83 @@
1
+ /* Round 106 verification: legend panel gains a header matching the
2
+ * recent-signal panel's "recent signal · N flows" format. Same
3
+ * font/position vocabulary so the two side panels feel paired. The
4
+ * fleet count is right-aligned at x=215 in the accent colour.
5
+ *
6
+ * Sanity-checks:
7
+ * - "legend" title text exists in the legend panel
8
+ * - "N node(s)" count matches sessions.length
9
+ * - Singular vs plural ("1 node" vs "5 nodes") correct
10
+ * - Existing rows still render (hover affordance preserved)
11
+ */
12
+ import { chromium } from 'playwright';
13
+ import { readFileSync } from 'node:fs';
14
+
15
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
16
+ const browser = await chromium.launch({ headless: true });
17
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
18
+
19
+ const probe = async (count) => {
20
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
21
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
22
+ await ctx.addInitScript(() => {
23
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
24
+ });
25
+ await ctx.route('**/api/hub/status*', async (route) => {
26
+ const r = await route.fetch();
27
+ const b = await r.json();
28
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
29
+ const sessions = [];
30
+ for (let i = 0; i < count; i++) {
31
+ sessions.push({
32
+ alias: `n${i}`, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
33
+ network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ });
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, count, { timeout: 30000 });
44
+ await page.waitForSelector('[data-legend-panel-count]', { timeout: 10000 });
45
+ await page.waitForTimeout(400);
46
+
47
+ const data = await page.evaluate(() => {
48
+ const headerCount = document.querySelector('[data-legend-panel-count]')?.textContent;
49
+ const headerTitle = (() => {
50
+ // Find a <text> with "legend" content in the legend panel <g>
51
+ const panel = document.querySelector('[data-topo-panel-elevation="legend"]')?.parentElement;
52
+ if (!panel) return null;
53
+ for (const t of panel.querySelectorAll('text')) {
54
+ if (t.textContent === 'legend') return t.textContent;
55
+ }
56
+ return null;
57
+ })();
58
+ const legendRows = document.querySelectorAll('[data-legend-status]').length;
59
+ return { headerCount, headerTitle, legendRows };
60
+ });
61
+ await ctx.close();
62
+ return data;
63
+ };
64
+
65
+ const single = await probe(1);
66
+ const many = await probe(5);
67
+ await browser.close();
68
+
69
+ const results = {
70
+ single_hasTitle: single.headerTitle === 'legend',
71
+ single_count1Node: single.headerCount === '1 node',
72
+ many_hasTitle: many.headerTitle === 'legend',
73
+ many_count5Nodes: many.headerCount === '5 nodes',
74
+ many_rowsStillRender: many.legendRows >= 1, // depends on which states are present
75
+ // Sanity: singular vs plural
76
+ singular_correct: single.headerCount?.endsWith(' node'),
77
+ plural_correct: many.headerCount?.endsWith(' nodes'),
78
+ };
79
+ const ok = Object.values(results).every(Boolean);
80
+ console.log(`${ok ? '✅' : '❌'} legend panel header:`, JSON.stringify(results),
81
+ `\n single (1 node)=`, single,
82
+ `\n many (5 nodes)=`, many);
83
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,112 @@
1
+ /* Round 181 verification: legend row pinned-state ring (R61) is
2
+ * now always-mounted with opacity-gated visibility + 150ms
3
+ * transition — smooth fade in/out on pin / unpin.
4
+ *
5
+ * Pre-R181 the ring mounted/unmounted conditionally, snapping
6
+ * on every state change. Same vocabulary as R165/R180 smooth-
7
+ * pin-mirror family, applied at the legend-row scope.
8
+ *
9
+ * Test:
10
+ * 1. Idle: ring exists in DOM but opacity=0
11
+ * 2. Click 'working' legend row → ring for working = opacity=1
12
+ * 3. Other rings (idle / offline) stay opacity=0
13
+ * 4. Re-click 'working' → unpin → opacity returns to 0
14
+ * 5. All rings carry 'opacity 150ms ease-out' transition
15
+ */
16
+ import { chromium } from 'playwright';
17
+ import { readFileSync } from 'node:fs';
18
+
19
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
20
+ const browser = await chromium.launch({ headless: true });
21
+
22
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
23
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
24
+ await ctx.addInitScript(() => {
25
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
26
+ });
27
+
28
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
29
+ const stale = new Date(Date.now() - 10 * 60 * 1000).toISOString();
30
+ await ctx.route('**/api/hub/status*', async (route) => {
31
+ const r = await route.fetch();
32
+ const b = await r.json();
33
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
34
+ const mk = (alias, status, last) => ({
35
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
36
+ network_id: nid, project_dir: null,
37
+ created_at: last, updated_at: last, last_seen_at: last,
38
+ });
39
+ // Mix of statuses so all three legend rows render
40
+ await route.fulfill({ response: r, json: { ...b, sessions: [
41
+ mk('w1', 'working', fresh), mk('w2', 'working', fresh),
42
+ mk('i1', 'idle', fresh),
43
+ mk('o1', 'offline', stale),
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
+
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
52
+ await page.waitForSelector('[data-legend-pin-ring]', { timeout: 10000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ const probe = () => page.evaluate(() => {
56
+ const grab = (k) => {
57
+ const r = document.querySelector(`[data-legend-pin-ring="${k}"]`);
58
+ return r ? {
59
+ opacity: parseFloat(r.getAttribute('opacity') || ''),
60
+ pinnedAttr: r.getAttribute('data-legend-pin-ring-pinned'),
61
+ transition: r.style.transition || getComputedStyle(r).transition,
62
+ } : null;
63
+ };
64
+ return {
65
+ working: grab('working'),
66
+ idle: grab('idle'),
67
+ offline: grab('offline'),
68
+ };
69
+ });
70
+
71
+ const idle = await probe();
72
+
73
+ // Pin 'working' status by clicking the legend row
74
+ await page.locator('[data-legend-status="working"]').click();
75
+ await page.waitForTimeout(250);
76
+ const afterPin = await probe();
77
+
78
+ // Re-click to unpin
79
+ await page.locator('[data-legend-status="working"]').click();
80
+ await page.waitForTimeout(250);
81
+ const afterUnpin = await probe();
82
+
83
+ await browser.close();
84
+
85
+ const hasTransition = (s) =>
86
+ (s?.transition || '').includes('opacity 150ms') ||
87
+ /opacity\s+0\.15s|opacity\s+150ms/.test(s?.transition || '');
88
+
89
+ const results = {
90
+ // Idle (no pin)
91
+ three_rings_present: idle.working !== null && idle.idle !== null && idle.offline !== null,
92
+ idle_working_opacity_0: idle.working?.opacity === 0,
93
+ idle_idle_opacity_0: idle.idle?.opacity === 0,
94
+ idle_offline_opacity_0: idle.offline?.opacity === 0,
95
+ all_have_transition: hasTransition(idle.working) && hasTransition(idle.idle) && hasTransition(idle.offline),
96
+
97
+ // After pin working
98
+ pin_working_opacity_1: afterPin.working?.opacity === 1,
99
+ pin_working_pinned_attr: afterPin.working?.pinnedAttr === 'true',
100
+ pin_idle_stays_0: afterPin.idle?.opacity === 0,
101
+ pin_offline_stays_0: afterPin.offline?.opacity === 0,
102
+
103
+ // After unpin
104
+ unpin_working_opacity_0: afterUnpin.working?.opacity === 0,
105
+ unpin_working_pinned_attr: afterUnpin.working?.pinnedAttr === 'false',
106
+ };
107
+ const ok = Object.values(results).every(Boolean);
108
+ console.log(`${ok ? '✅' : '❌'} legend pin-ring fade:`, JSON.stringify(results),
109
+ `\n idle =`, idle,
110
+ `\n pinned =`, afterPin,
111
+ `\n unpinned =`, afterUnpin);
112
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,128 @@
1
+ /* Round 61 verification: legend rows also pin status filter on click.
2
+ * - Hovering still works (R55, transient release).
3
+ * - Clicking a row toggles pinnedStatus same as R60 pressure segments.
4
+ * - Pinned row's swatch gets an outer ring at r=8 (concentric, row colour).
5
+ * - Click same row again → release.
6
+ * - Sticky: pin survives mouse-move-away (vs R55 hover which clears).
7
+ * - The R60 pressure-segment pin and this share `pinnedStatus`, so
8
+ * clicking the legend then clicking the pressure-segment for a
9
+ * different status replaces (not stacks) the filter — verified by
10
+ * chain: legend.working → pressure.idle → only idle stays bright.
11
+ */
12
+ import { chromium } from 'playwright';
13
+ import { readFileSync } from 'node:fs';
14
+
15
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
16
+ const browser = await chromium.launch({ headless: true });
17
+
18
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try {
22
+ localStorage.setItem('anet-theme', 'cyber');
23
+ localStorage.setItem('anet-topo-layout', 'ring');
24
+ sessionStorage.setItem('anet_v3_auth', '1');
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 sessions = [
34
+ { alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
35
+ { alias: 'idl1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
36
+ { alias: 'idl2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
37
+ ];
38
+ await route.fulfill({ response: r, json: { ...b, sessions } });
39
+ });
40
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
41
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
42
+ const page = await ctx.newPage();
43
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
44
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
45
+ await page.waitForTimeout(500);
46
+
47
+ const opacities = () => page.evaluate(() => {
48
+ const o = {};
49
+ for (const a of ['wkr', 'idl1', 'idl2']) {
50
+ const g = document.querySelector(`g[data-node="${a}"]`);
51
+ o[a] = g ? +(g.style.opacity || '1') : null;
52
+ }
53
+ return o;
54
+ });
55
+
56
+ const before = await opacities();
57
+
58
+ // Click the WORKING legend row.
59
+ const workingLegend = page.locator('g[data-legend-status="working"]').first();
60
+ const workingExists = await workingLegend.count() > 0;
61
+ if (!workingExists) { console.log('❌ legend row[working] not found'); process.exit(1); }
62
+ await workingLegend.click({ force: true });
63
+ await page.waitForTimeout(250);
64
+ const afterPin = await opacities();
65
+
66
+ // Pin survives mouse move.
67
+ await page.mouse.move(10, 10);
68
+ await page.waitForTimeout(250);
69
+ const afterMouseMove = await opacities();
70
+
71
+ // Pinned row should carry an aria-pressed=true.
72
+ const pinnedAria = await page.evaluate(() => {
73
+ const g = document.querySelector('g[data-legend-status="working"]');
74
+ return g?.getAttribute('aria-pressed');
75
+ });
76
+ // And a concentric r=8 ring exists inside it.
77
+ const pinnedRingExists = await page.evaluate(() => {
78
+ const g = document.querySelector('g[data-legend-status="working"]');
79
+ if (!g) return false;
80
+ return [...g.querySelectorAll('circle')].some(c => c.getAttribute('r') === '8');
81
+ });
82
+
83
+ // Switch pin via pressure segment (idle) — should REPLACE, not stack.
84
+ const idleSeg = page.locator('[data-pressure-seg="idle"]').first();
85
+ await idleSeg.click({ force: true });
86
+ await page.waitForTimeout(250);
87
+ const afterSwitch = await opacities();
88
+
89
+ // And the legend pin should release (concentric ring gone from working).
90
+ const workingRingGone = await page.evaluate(() => {
91
+ const g = document.querySelector('g[data-legend-status="working"]');
92
+ if (!g) return true;
93
+ return ![...g.querySelectorAll('circle')].some(c => c.getAttribute('r') === '8');
94
+ });
95
+
96
+ // Click idle legend again to release pin. The click ALSO triggers a
97
+ // mouseenter that sets hoveredStatus='idle', which would keep the
98
+ // filter active (activeStatus = hoveredStatus ?? pinnedStatus). Move
99
+ // the mouse away to clear the hover so we observe the pin-only state.
100
+ await page.locator('g[data-legend-status="idle"]').first().click({ force: true });
101
+ await page.mouse.move(10, 10);
102
+ await page.waitForTimeout(250);
103
+ const afterRelease = await opacities();
104
+
105
+ await browser.close();
106
+
107
+ const bright = (v) => v != null && v >= 0.55;
108
+ const dim = (v) => v != null && v < 0.4;
109
+ const results = {
110
+ before_baseline: bright(before.wkr) && bright(before.idl1) && bright(before.idl2),
111
+ legendPin_keepsWorking: bright(afterPin.wkr),
112
+ legendPin_dimsIdle: dim(afterPin.idl1) && dim(afterPin.idl2),
113
+ legendPin_survivesMouse: bright(afterMouseMove.wkr) && dim(afterMouseMove.idl1),
114
+ legendPin_ariaPressed: pinnedAria === 'true',
115
+ legendPin_ringRendered: pinnedRingExists,
116
+ pressureSwitch_replaces: dim(afterSwitch.wkr) && bright(afterSwitch.idl1) && bright(afterSwitch.idl2),
117
+ pressureSwitch_clearsLegendRing: workingRingGone,
118
+ release_restoresAll: bright(afterRelease.wkr) && bright(afterRelease.idl1),
119
+ };
120
+ const ok = Object.values(results).every(Boolean);
121
+ console.log(`${ok ? '✅' : '❌'} legend pin:`, JSON.stringify(results),
122
+ `\n before=`, before,
123
+ `\n afterPin=`, afterPin,
124
+ `\n afterMouseMove=`, afterMouseMove,
125
+ `\n afterSwitch=`, afterSwitch,
126
+ `\n afterRelease=`, afterRelease,
127
+ `\n pinnedAria=${pinnedAria} ringRendered=${pinnedRingExists} workingRingGone=${workingRingGone}`);
128
+ process.exit(ok ? 0 : 1);