@sleep2agi/agent-network-dashboard 0.4.6-preview.21 → 0.4.6-preview.210

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 (413) 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/05uk96gc~9mni.js +1 -0
  184. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  185. package/.next/static/chunks/0f0iq6304tw-p.js +1 -0
  186. package/.next/static/chunks/0ggj5ummw2a84.js +7 -0
  187. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  188. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  189. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  190. package/.next/static/chunks/0xtbt3szoyqsp.js +4 -0
  191. package/.next/static/chunks/0~k1yvhakbob6.css +2 -0
  192. package/.next/static/chunks/12m0az1fb1ovq.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 +4212 -255
  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 +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-self-hover-test.mjs +171 -0
  214. package/scripts/topo-active-links-chip-test.mjs +73 -0
  215. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  216. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  217. package/scripts/topo-arrival-ping-test.mjs +87 -0
  218. package/scripts/topo-arrow-tier-test.mjs +61 -0
  219. package/scripts/topo-auto-fit-test.mjs +73 -0
  220. package/scripts/topo-canvas-aria-test.mjs +108 -0
  221. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  222. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  223. package/scripts/topo-chip-focus-test.mjs +95 -0
  224. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  225. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  226. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  227. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  228. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  229. package/scripts/topo-click-ripple-test.mjs +66 -0
  230. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  231. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  232. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  233. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  234. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  235. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  236. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  237. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  238. package/scripts/topo-dispatch-pulse-test.mjs +83 -0
  239. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  240. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  241. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  242. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  243. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  244. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  245. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  246. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  247. package/scripts/topo-edge-fadein-test.mjs +106 -0
  248. package/scripts/topo-edge-focus-test.mjs +113 -0
  249. package/scripts/topo-edge-hover-test.mjs +93 -0
  250. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  251. package/scripts/topo-edge-pill-test.mjs +105 -0
  252. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  253. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  254. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  255. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  256. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  257. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  258. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  259. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  260. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  261. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  262. package/scripts/topo-fit-key-test.mjs +106 -0
  263. package/scripts/topo-freshness-chip-test.mjs +74 -0
  264. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  265. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  266. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  267. package/scripts/topo-group-count-test.mjs +68 -0
  268. package/scripts/topo-group-hover-test.mjs +82 -0
  269. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  270. package/scripts/topo-group-label-hover-test.mjs +119 -0
  271. package/scripts/topo-group-label-tint-test.mjs +99 -0
  272. package/scripts/topo-group-label-title-test.mjs +83 -0
  273. package/scripts/topo-group-pin-test.mjs +119 -0
  274. package/scripts/topo-group-status-pips-test.mjs +103 -0
  275. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  276. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  277. package/scripts/topo-groupbox-live-test.mjs +100 -0
  278. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  279. package/scripts/topo-groupbox-test.mjs +4 -2
  280. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  281. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  282. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  283. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  284. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  285. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  286. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  287. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  288. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  289. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  290. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  291. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  292. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  293. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  294. package/scripts/topo-hub-a11y-test.mjs +112 -0
  295. package/scripts/topo-hub-busyness-test.mjs +100 -0
  296. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  297. package/scripts/topo-hub-fadein-test.mjs +88 -0
  298. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  299. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  300. package/scripts/topo-hub-title-test.mjs +93 -0
  301. package/scripts/topo-hub-working-count-test.mjs +99 -0
  302. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  303. package/scripts/topo-label-lift-test.mjs +119 -0
  304. package/scripts/topo-label-scale-test.mjs +78 -0
  305. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  306. package/scripts/topo-layout-key-test.mjs +91 -0
  307. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  308. package/scripts/topo-layout-toggle-test.mjs +131 -0
  309. package/scripts/topo-legend-count-test.mjs +88 -0
  310. package/scripts/topo-legend-hover-test.mjs +113 -0
  311. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  312. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  313. package/scripts/topo-legend-pin-test.mjs +128 -0
  314. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  315. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  316. package/scripts/topo-legend-row-title-test.mjs +102 -0
  317. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  318. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  319. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  320. package/scripts/topo-minimap-test.mjs +68 -0
  321. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  322. package/scripts/topo-mobile-chips-test.mjs +103 -0
  323. package/scripts/topo-node-a11y-test.mjs +107 -0
  324. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  325. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  326. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  327. package/scripts/topo-node-title-peers-test.mjs +107 -0
  328. package/scripts/topo-node-title-test.mjs +101 -0
  329. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  330. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  331. package/scripts/topo-offline-dim-test.mjs +70 -0
  332. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  333. package/scripts/topo-orbit-busyness-test.mjs +96 -0
  334. package/scripts/topo-overlap-test.mjs +32 -5
  335. package/scripts/topo-pan-cursor-test.mjs +62 -0
  336. package/scripts/topo-panel-elevation-test.mjs +76 -0
  337. package/scripts/topo-panel-fadein-test.mjs +102 -0
  338. package/scripts/topo-panel-hot-tail-fade-test.mjs +124 -0
  339. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  340. package/scripts/topo-particle-stagger-test.mjs +82 -0
  341. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  342. package/scripts/topo-pin-intersection-test.mjs +121 -0
  343. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  344. package/scripts/topo-pin-persistence-test.mjs +105 -0
  345. package/scripts/topo-popover-meta-test.mjs +78 -0
  346. package/scripts/topo-pressure-bar-test.mjs +69 -0
  347. package/scripts/topo-pressure-hover-test.mjs +123 -0
  348. package/scripts/topo-pressure-pin-test.mjs +119 -0
  349. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  350. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  351. package/scripts/topo-pulse-rate-test.mjs +71 -0
  352. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  353. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  354. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  355. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  356. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  357. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  358. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  359. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  360. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  361. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  362. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  363. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  364. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  365. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  366. package/scripts/topo-recent-row-title-test.mjs +112 -0
  367. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  368. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  369. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  370. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  371. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  372. package/scripts/topo-reduced-motion-test.mjs +77 -0
  373. package/scripts/topo-reset-spin-test.mjs +120 -0
  374. package/scripts/topo-spoke-busyness-test.mjs +110 -0
  375. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  376. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  377. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  378. package/scripts/topo-svg-focus-test.mjs +112 -0
  379. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  380. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  381. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  382. package/scripts/topo-tier-ring-test.mjs +69 -0
  383. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  384. package/scripts/topo-tier-stagger-test.mjs +89 -0
  385. package/scripts/topo-tz-ghost-test.mjs +69 -0
  386. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  387. package/scripts/topo-vendor-chip-self-hover-test.mjs +130 -0
  388. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  389. package/scripts/topo-vendor-pill-test.mjs +125 -0
  390. package/scripts/topo-vendor-pin-test.mjs +125 -0
  391. package/scripts/topo-view-smooth-test.mjs +131 -0
  392. package/scripts/topo-wheel-zoom-test.mjs +79 -0
  393. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  394. package/scripts/topo-working-halo-breath-test.mjs +86 -0
  395. package/scripts/topo-zoom-keys-test.mjs +89 -0
  396. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  397. package/.next/static/chunks/01j59m93~ijpq.js +0 -1
  398. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  399. package/.next/static/chunks/09nd.t_hd.y6t.css +0 -2
  400. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  401. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  402. package/.next/static/chunks/0fqy_0wj_1xa7.js +0 -7
  403. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  404. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  405. package/.next/static/chunks/0ms~5~zq93-vs.js +0 -1
  406. package/.next/static/chunks/0nh4tljy5z5ns.js +0 -1
  407. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  408. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  409. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  410. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  411. /package/.next/static/{XiA2VWgHsZvuY-5yWxSPj → Wt-rije1CRXoKiR80pqDA}/_buildManifest.js +0 -0
  412. /package/.next/static/{XiA2VWgHsZvuY-5yWxSPj → Wt-rije1CRXoKiR80pqDA}/_clientMiddlewareManifest.js +0 -0
  413. /package/.next/static/{XiA2VWgHsZvuY-5yWxSPj → Wt-rije1CRXoKiR80pqDA}/_ssgManifest.js +0 -0
@@ -0,0 +1,125 @@
1
+ /* Round 89 verification: pinning a vendor renders a "filter: X · N ×"
2
+ * pill in the chip-row, mirroring R64 status + group pills. R88 added
3
+ * the pin but the affordance lived only on the letter — body-click /
4
+ * × button on a dedicated pill was missing. R89 closes that.
5
+ *
6
+ * Also verifies the stale-purge useEffect: if the fleet's vendor
7
+ * distribution loses the pinned vendor (last node disconnects), the
8
+ * pin clears automatically so the pill doesn't show "filter: A · 0"
9
+ * forever. Same defensive pattern pinnedGroup uses.
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: 1200 } });
18
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
19
+ await ctx.addInitScript(() => {
20
+ try {
21
+ localStorage.setItem('anet-theme', 'cyber');
22
+ localStorage.setItem('anet-topo-layout', 'ring');
23
+ sessionStorage.setItem('anet_v3_auth', '1');
24
+ sessionStorage.removeItem('anet-topo-pinned-status');
25
+ sessionStorage.removeItem('anet-topo-pinned-group');
26
+ sessionStorage.removeItem('anet-topo-pinned-vendor');
27
+ } catch {}
28
+ });
29
+
30
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
31
+ let dropGPT = false;
32
+ await ctx.route('**/api/hub/status*', async (route) => {
33
+ const r = await route.fetch();
34
+ const b = await r.json();
35
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
36
+ const mk = (alias, model) => ({
37
+ alias, status: 'idle', model, runtime: 'cli-claude-code',
38
+ network_id: nid, project_dir: null,
39
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
40
+ });
41
+ const sessions = [
42
+ mk('alpha', 'claude-opus-4'),
43
+ mk('gamma', 'claude-opus-4'),
44
+ ];
45
+ if (!dropGPT) sessions.splice(1, 0, mk('beta', 'gpt-5'));
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 === 3, { timeout: 30000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ const pill = () => page.evaluate(() => {
56
+ const p = document.querySelector('[data-active-filter="vendor"]');
57
+ if (!p) return null;
58
+ return {
59
+ text: (p.textContent || '').trim(),
60
+ count: p.getAttribute('data-filter-match-count'),
61
+ color: getComputedStyle(p).color,
62
+ };
63
+ });
64
+ const storage = () => page.evaluate(() => sessionStorage.getItem('anet-topo-pinned-vendor'));
65
+
66
+ // Pin O (gpt-5 → OpenAI) via click.
67
+ const before = await pill();
68
+ await page.locator('[data-vendor-letter="O"]').click();
69
+ await page.mouse.move(10, 10);
70
+ await page.waitForTimeout(250);
71
+ const afterPinO = await pill();
72
+ const afterPinOStorage = await storage();
73
+
74
+ // Clicking the pill body clears the pin.
75
+ await page.locator('[data-active-filter="vendor"]').click();
76
+ await page.waitForTimeout(250);
77
+ const afterBodyClick = await pill();
78
+
79
+ // Pin A (Anthropic) — verify the × button works.
80
+ await page.locator('[data-vendor-letter="A"]').click();
81
+ await page.mouse.move(10, 10);
82
+ await page.waitForTimeout(250);
83
+ const afterPinA = await pill();
84
+ await page.locator('[data-active-filter="vendor"] button').click();
85
+ await page.waitForTimeout(250);
86
+ const afterXButton = await pill();
87
+
88
+ // Pin O again, then mock the GPT node away → stale-purge fires.
89
+ await page.locator('[data-vendor-letter="O"]').click();
90
+ await page.waitForTimeout(250);
91
+ const beforePurge = await pill();
92
+ dropGPT = true;
93
+ // Force a refetch via swr revalidation — easiest is page.reload, but
94
+ // that wipes sessionStorage. Instead, just await the next swr cycle
95
+ // (5s by default). To speed things up, dispatch a focus event which
96
+ // triggers swr revalidateOnFocus. We expect the pin to clear once
97
+ // the vendor leaves the distribution.
98
+ await page.evaluate(() => window.dispatchEvent(new Event('focus')));
99
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
100
+ await page.waitForTimeout(400);
101
+ const afterPurge = await pill();
102
+ const afterPurgeStorage = await storage();
103
+
104
+ await browser.close();
105
+
106
+ const results = {
107
+ before_noPill: before === null,
108
+ pinO_pillRendered: afterPinO !== null && /filter:?\s*O/.test(afterPinO.text),
109
+ pinO_countCorrect: afterPinO?.count === '1',
110
+ pinO_storage: afterPinOStorage === 'O',
111
+ bodyClick_clears: afterBodyClick === null,
112
+ pinA_pillRendered: afterPinA !== null && /filter:?\s*A/.test(afterPinA.text),
113
+ pinA_countCorrect: afterPinA?.count === '2',
114
+ xButton_clears: afterXButton === null,
115
+ beforePurge_present: beforePurge !== null && /filter:?\s*O/.test(beforePurge.text),
116
+ purge_pillGone: afterPurge === null,
117
+ purge_storageGone: afterPurgeStorage === null,
118
+ };
119
+ const ok = Object.values(results).every(Boolean);
120
+ console.log(`${ok ? '✅' : '❌'} vendor pill:`, JSON.stringify(results),
121
+ `\n afterPinO=`, afterPinO,
122
+ `\n afterPinA=`, afterPinA,
123
+ `\n beforePurge=`, beforePurge,
124
+ `\n afterPurge=`, afterPurge);
125
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,125 @@
1
+ /* Round 88 verification: clicking a vendor letter in the distribution
2
+ * chip toggles a sticky pin — closes the only filter dimension that
3
+ * still released on mouseleave (R60 status, R63 group, R69 Cmd+K all
4
+ * support pin). Composes via activeVendor = hoveredVendor ??
5
+ * pinnedVendor so hover still wins for spot comparison.
6
+ *
7
+ * Fleet: 2 Claude (alpha, gamma) + 1 GPT (beta). Click A → alpha + gamma
8
+ * stay bright across mouseleave; pinned-style boxShadow on the A
9
+ * letter; aria-pressed="true". Hover O while A pinned → transient O
10
+ * preview (alpha/gamma dim, beta lit); mouseleave → A pin restores.
11
+ * Esc clears the pin (R62/R63 universal-cancel extends to vendor).
12
+ * SessionStorage round-trips.
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const browser = await chromium.launch({ headless: true });
19
+
20
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1200 } });
21
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
22
+ await ctx.addInitScript(() => {
23
+ try {
24
+ localStorage.setItem('anet-theme', 'cyber');
25
+ localStorage.setItem('anet-topo-layout', 'ring');
26
+ sessionStorage.setItem('anet_v3_auth', '1');
27
+ sessionStorage.removeItem('anet-topo-pinned-status');
28
+ sessionStorage.removeItem('anet-topo-pinned-group');
29
+ sessionStorage.removeItem('anet-topo-pinned-vendor');
30
+ } catch {}
31
+ });
32
+
33
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
34
+ await ctx.route('**/api/hub/status*', async (route) => {
35
+ const r = await route.fetch();
36
+ const b = await r.json();
37
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
38
+ const sessions = [
39
+ { alias: 'alpha', status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
40
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
41
+ { alias: 'beta', status: 'idle', model: 'gpt-5', runtime: 'codex-cli',
42
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
43
+ { alias: 'gamma', status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
44
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
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 === 3, { timeout: 30000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ const ops = () => page.evaluate(() => {
56
+ const o = {};
57
+ for (const a of ['alpha', 'beta', 'gamma']) {
58
+ const g = document.querySelector(`g[data-node="${a}"]`);
59
+ o[a] = g ? +(g.style.opacity || '1') : null;
60
+ }
61
+ return o;
62
+ });
63
+ const meta = () => page.evaluate(() => {
64
+ const a = document.querySelector('[data-vendor-letter="A"]');
65
+ const o = document.querySelector('[data-vendor-letter="O"]');
66
+ return {
67
+ A: { pinned: a?.getAttribute('data-vendor-pinned'), aria: a?.getAttribute('aria-pressed'), shadow: (a?.getAttribute('style') || '').includes('inset') },
68
+ O: { pinned: o?.getAttribute('data-vendor-pinned'), aria: o?.getAttribute('aria-pressed'), shadow: (o?.getAttribute('style') || '').includes('inset') },
69
+ };
70
+ });
71
+ const storage = () => page.evaluate(() => sessionStorage.getItem('anet-topo-pinned-vendor'));
72
+
73
+ const before = await ops();
74
+ const beforeMeta = await meta();
75
+
76
+ // Click A to pin. Mouse moves away — pin survives.
77
+ await page.locator('[data-vendor-letter="A"]').click();
78
+ await page.mouse.move(10, 10);
79
+ await page.waitForTimeout(300);
80
+ const afterClickA = await ops();
81
+ const afterClickAMeta = await meta();
82
+ const afterClickAStorage = await storage();
83
+
84
+ // Hover O while A pinned — transient O preview wins.
85
+ await page.locator('[data-vendor-letter="O"]').hover();
86
+ await page.waitForTimeout(250);
87
+ const hoverOWhilePinA = await ops();
88
+
89
+ // Leave — A pin restores.
90
+ await page.mouse.move(10, 10);
91
+ await page.waitForTimeout(250);
92
+ const leaveOWhilePinA = await ops();
93
+
94
+ // Esc clears the vendor pin.
95
+ await page.keyboard.press('Escape');
96
+ await page.waitForTimeout(250);
97
+ const afterEsc = await ops();
98
+ const afterEscMeta = await meta();
99
+ const afterEscStorage = await storage();
100
+
101
+ await browser.close();
102
+
103
+ const bright = (v) => v != null && v >= 0.55;
104
+ const dim = (v) => v != null && v < 0.4;
105
+ const results = {
106
+ before_baseline: bright(before.alpha) && bright(before.beta) && bright(before.gamma),
107
+ before_noPinMeta: beforeMeta.A.pinned === 'false' && beforeMeta.A.aria === 'false' && !beforeMeta.A.shadow,
108
+ pinA_keepsClaudes: bright(afterClickA.alpha) && bright(afterClickA.gamma),
109
+ pinA_dimsGPT: dim(afterClickA.beta),
110
+ pinA_metaSet: afterClickAMeta.A.pinned === 'true' && afterClickAMeta.A.aria === 'true' && afterClickAMeta.A.shadow,
111
+ pinA_storagePersisted: afterClickAStorage === 'A',
112
+ hoverOPinA_transientO: bright(hoverOWhilePinA.beta) && dim(hoverOWhilePinA.alpha) && dim(hoverOWhilePinA.gamma),
113
+ leaveOPinA_aRestored: bright(leaveOWhilePinA.alpha) && bright(leaveOWhilePinA.gamma) && dim(leaveOWhilePinA.beta),
114
+ esc_releasesPin: bright(afterEsc.alpha) && bright(afterEsc.beta) && bright(afterEsc.gamma),
115
+ esc_metaCleared: afterEscMeta.A.pinned === 'false' && afterEscMeta.A.aria === 'false' && !afterEscMeta.A.shadow,
116
+ esc_storageCleared: afterEscStorage === null,
117
+ };
118
+ const ok = Object.values(results).every(Boolean);
119
+ console.log(`${ok ? '✅' : '❌'} vendor pin:`, JSON.stringify(results),
120
+ `\n before=`, before,
121
+ `\n afterClickA=`, afterClickA,
122
+ `\n hoverOPinA=`, hoverOWhilePinA,
123
+ `\n leaveOPinA=`, leaveOWhilePinA,
124
+ `\n afterEsc=`, afterEsc);
125
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,131 @@
1
+ /* Round 168 verification: smooth viewport transition on reset/fit.
2
+ *
3
+ * Pre-R168 resetView/fitView snapped the <g transform> from
4
+ * current zoom/pan to {zoom:1, x:0, y:0} in one frame. Pressing
5
+ * `0` or clicking the hub jolted the eye.
6
+ *
7
+ * R168 arms a one-shot smoothView flag when resetView/fitView
8
+ * fires; the viewport <g> reads this flag and conditionally
9
+ * applies `transition: transform 300ms ease-out`. Auto-clears
10
+ * 350ms later. Pan (R103 drag) and wheel zoom never set the
11
+ * flag, so interactive view changes stay snappy.
12
+ *
13
+ * Test:
14
+ * 1. Load page → idle state, smoothView=false (no transition)
15
+ * 2. Zoom in twice via '+' to set non-default view
16
+ * 3. Press '0' (resetView) → smoothView=true, viewport <g>
17
+ * has style.transition = 'transform 300ms ease-out'
18
+ * 4. Wait 450ms → smoothView=false again, transition cleared
19
+ * 5. Repeat with 'f' (fitView)
20
+ */
21
+ import { chromium } from 'playwright';
22
+ import { readFileSync } from 'node:fs';
23
+
24
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
25
+ const browser = await chromium.launch({ headless: true });
26
+
27
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
28
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
29
+ await ctx.addInitScript(() => {
30
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
31
+ });
32
+
33
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
34
+ await ctx.route('**/api/hub/status*', async (route) => {
35
+ const r = await route.fetch();
36
+ const b = await r.json();
37
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
38
+ const mk = (alias) => ({
39
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
40
+ network_id: nid, project_dir: null,
41
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
42
+ });
43
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta'), mk('gamma')] } });
44
+ });
45
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
46
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
47
+
48
+ const page = await ctx.newPage();
49
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
50
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
51
+ await page.waitForSelector('[data-topo-viewport]', { timeout: 10000 });
52
+ await page.waitForTimeout(400);
53
+
54
+ // Idle: smoothView=false, no transition style.
55
+ const idle = await page.evaluate(() => {
56
+ const g = document.querySelector('[data-topo-viewport]');
57
+ return {
58
+ smooth: g?.getAttribute('data-topo-viewport-smooth'),
59
+ transition: g?.style?.transition || '',
60
+ };
61
+ });
62
+
63
+ // Zoom in twice → set non-default view
64
+ await page.keyboard.press('+');
65
+ await page.keyboard.press('+');
66
+ await page.waitForTimeout(150);
67
+ const zoomBefore = await page.evaluate(() =>
68
+ document.querySelector('[data-topo-chrome-zoom-level]')?.textContent?.trim());
69
+
70
+ // Press 0 (resetView) → smoothView=true. Probe within the
71
+ // 350ms window so the style is still set.
72
+ await page.keyboard.press('0');
73
+ // Small delay so React commits the state update.
74
+ await page.waitForTimeout(50);
75
+ const duringReset = await page.evaluate(() => {
76
+ const g = document.querySelector('[data-topo-viewport]');
77
+ return {
78
+ smooth: g?.getAttribute('data-topo-viewport-smooth'),
79
+ transition: g?.style?.transition || '',
80
+ };
81
+ });
82
+
83
+ // Wait past the 350ms window
84
+ await page.waitForTimeout(450);
85
+ const afterReset = await page.evaluate(() => {
86
+ const g = document.querySelector('[data-topo-viewport]');
87
+ return {
88
+ smooth: g?.getAttribute('data-topo-viewport-smooth'),
89
+ transition: g?.style?.transition || '',
90
+ };
91
+ });
92
+ const zoomAfter = await page.evaluate(() =>
93
+ document.querySelector('[data-topo-chrome-zoom-level]')?.textContent?.trim());
94
+
95
+ // Now zoom in again and press 'f' (fitView) — same arming path
96
+ await page.keyboard.press('+');
97
+ await page.keyboard.press('+');
98
+ await page.waitForTimeout(150);
99
+ await page.keyboard.press('f');
100
+ await page.waitForTimeout(50);
101
+ const duringFit = await page.evaluate(() => {
102
+ const g = document.querySelector('[data-topo-viewport]');
103
+ return {
104
+ smooth: g?.getAttribute('data-topo-viewport-smooth'),
105
+ transition: g?.style?.transition || '',
106
+ };
107
+ });
108
+
109
+ await browser.close();
110
+
111
+ const results = {
112
+ viewport_found: idle !== null,
113
+ idle_smooth_false: idle.smooth === 'false',
114
+ idle_no_transition: !idle.transition.includes('transform'),
115
+ zoomed_before_reset: zoomBefore !== '100%',
116
+ reset_armed_smooth: duringReset.smooth === 'true',
117
+ reset_transition_set: duringReset.transition.includes('transform 300ms'),
118
+ after_reset_smooth_false: afterReset.smooth === 'false',
119
+ after_reset_no_transition: !afterReset.transition.includes('transform'),
120
+ reset_landed_at_100: zoomAfter === '100%',
121
+ fit_armed_smooth: duringFit.smooth === 'true',
122
+ fit_transition_set: duringFit.transition.includes('transform 300ms'),
123
+ };
124
+ const ok = Object.values(results).every(Boolean);
125
+ console.log(`${ok ? '✅' : '❌'} view smooth transition:`, JSON.stringify(results),
126
+ `\n idle =`, idle,
127
+ `\n duringReset =`, duringReset,
128
+ `\n afterReset =`, afterReset,
129
+ `\n duringFit =`, duringFit,
130
+ `\n zoom before/after = ${zoomBefore} → ${zoomAfter}`);
131
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,79 @@
1
+ /* Round 23 verification: wheel-zoom is guarded inline (requires
2
+ * ctrl/meta) but unconditional in fullscreen. Plain wheel over the
3
+ * canvas must let page scroll through. */
4
+ import { chromium } from 'playwright';
5
+ import { readFileSync } from 'node:fs';
6
+
7
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
8
+ const browser = await chromium.launch({ headless: true });
9
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
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.setItem('anet-topo-layout', 'grid');
15
+ sessionStorage.setItem('anet_v3_auth', '1');
16
+ } catch {}
17
+ });
18
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
19
+ await ctx.route('**/api/hub/status*', async (route) => {
20
+ const r = await route.fetch();
21
+ const b = await r.json();
22
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
23
+ const sessions = ['a', 'b'].map(a => ({
24
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
25
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
26
+ }));
27
+ await route.fulfill({ response: r, json: { ...b, sessions } });
28
+ });
29
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
30
+ const page = await ctx.newPage();
31
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
32
+ await page.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 30000 });
33
+ await page.waitForTimeout(400);
34
+
35
+ const readZoom = () => page.evaluate(() => {
36
+ const span = document.querySelector('button[aria-label="Zoom in"]').parentElement.querySelector('span[title*="zoom level"]');
37
+ return +span.textContent.replace('%', '');
38
+ });
39
+
40
+ // Move cursor over the SVG centre
41
+ const box = await page.$eval('svg[viewBox="0 0 1000 680"]', el => el.getBoundingClientRect());
42
+ const cx = box.x + box.width / 2;
43
+ const cy = box.y + box.height / 2;
44
+ await page.mouse.move(cx, cy);
45
+ const start = await readZoom();
46
+
47
+ // PLAIN wheel — must NOT zoom (guard active inline)
48
+ await page.mouse.wheel(0, -100);
49
+ await page.waitForTimeout(80);
50
+ const afterPlainWheel = await readZoom();
51
+
52
+ // PLAIN wheel must let page scroll
53
+ const scrollBefore = await page.evaluate(() => window.scrollY);
54
+ await page.mouse.wheel(0, 200);
55
+ await page.waitForTimeout(80);
56
+ const scrollAfter = await page.evaluate(() => window.scrollY);
57
+ // scroll back so the next test has the SVG in view
58
+ await page.evaluate(() => window.scrollTo(0, 0));
59
+ await page.mouse.move(cx, cy);
60
+ await page.waitForTimeout(100);
61
+
62
+ // CTRL+wheel — must zoom inline
63
+ const beforeCtrl = await readZoom();
64
+ await page.keyboard.down('Control');
65
+ await page.mouse.wheel(0, -100);
66
+ await page.keyboard.up('Control');
67
+ await page.waitForTimeout(80);
68
+ const afterCtrl = await readZoom();
69
+
70
+ await browser.close();
71
+ const results = {
72
+ plainWheelDoesNotZoom: afterPlainWheel === start,
73
+ plainWheelPageScrolls: scrollAfter > scrollBefore,
74
+ ctrlWheelZooms: afterCtrl > beforeCtrl,
75
+ };
76
+ const ok = Object.values(results).every(Boolean);
77
+ console.log(`${ok ? '✅' : '❌'} wheel zoom guard:`, JSON.stringify(results),
78
+ `start=${start} plainWheel=${afterPlainWheel} scrollBefore=${scrollBefore} scrollAfter=${scrollAfter} beforeCtrl=${beforeCtrl} afterCtrl=${afterCtrl}`);
79
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,154 @@
1
+ /* Round 139 verification: the chip-row "N working" chip click
2
+ * pins pinnedStatus='working'. Closes the cursor-pointer-lying
3
+ * bug from R79 — the chip's tooltip has been promising "click to
4
+ * pin" since R79 ("hover highlights, click to pin") but no onClick
5
+ * was ever wired. Same shape as R136 fix on the active-links chip.
6
+ *
7
+ * Composes with two existing pin surfaces:
8
+ * R60 pressure-bar segments
9
+ * R61 legend rows
10
+ * R139 working chip in the chip row (new)
11
+ * All three toggle the same pinnedStatus state via setPinnedStatus.
12
+ *
13
+ * Behavior tested:
14
+ * 1. Idle baseline: workingCount > 0, aria-pressed="false",
15
+ * data-pin-mirror="false", boxShadow absent
16
+ * 2. Click → pinnedStatus='working', aria-pressed="true",
17
+ * data-pin-mirror="true", boxShadow set
18
+ * 3. Click again → unpin, aria-pressed="false"
19
+ * 4. Esc clears pin (R62 invariant)
20
+ * 5. Empty fleet (workingCount=0): chip not interactive
21
+ * (no role, no tabindex, no cursor:pointer)
22
+ */
23
+ import { chromium } from 'playwright';
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
27
+
28
+ async function probe(workingCount, totalCount) {
29
+ const browser = await chromium.launch({ headless: true });
30
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
31
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
32
+ await ctx.addInitScript(() => {
33
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
34
+ });
35
+ const fresh = new Date(Date.now() - 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
+ for (let i = 0; i < totalCount; i++) {
42
+ sessions.push({
43
+ alias: `node${i}`,
44
+ status: i < workingCount ? 'working' : 'idle',
45
+ model: 'claude-opus-4', runtime: 'cli-claude-code',
46
+ network_id: nid, project_dir: null,
47
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
48
+ });
49
+ }
50
+ await route.fulfill({ response: r, json: { ...b, sessions } });
51
+ });
52
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
53
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
54
+
55
+ const page = await ctx.newPage();
56
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
57
+ await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n, totalCount, { timeout: 30000 });
58
+ await page.waitForSelector('[data-working-chip]', { timeout: 10000 });
59
+ await page.waitForTimeout(400);
60
+
61
+ return { browser, page };
62
+ }
63
+
64
+ // State A: fleet with working agents
65
+ const { browser: b1, page: p1 } = await probe(3, 5);
66
+
67
+ const readChip = () => p1.evaluate(() => {
68
+ const chip = document.querySelector('[data-working-chip]');
69
+ return {
70
+ clickable: chip?.getAttribute('data-working-chip-clickable'),
71
+ role: chip?.getAttribute('role'),
72
+ tabindex: chip?.getAttribute('tabindex'),
73
+ pinMirror: chip?.getAttribute('data-pin-mirror'),
74
+ ariaPressed: chip?.getAttribute('aria-pressed'),
75
+ // R180 fix: probe the box-shadow PROPERTY value, not a
76
+ // substring match on the style attribute. Pre-R180 the
77
+ // inline style only contained 'box-shadow' when pinned;
78
+ // R180 added 'transition: box-shadow 150ms ease-out' that
79
+ // also matches the substring even when unpinned. Read the
80
+ // actual property to keep the assertion precise.
81
+ boxShadow: !!(chip && chip.style && chip.style.boxShadow),
82
+ cursor: chip?.getAttribute('style')?.includes('cursor: pointer'),
83
+ };
84
+ });
85
+
86
+ const before = await readChip();
87
+
88
+ await p1.locator('[data-working-chip]').click();
89
+ await p1.waitForTimeout(200);
90
+ const afterClick = await readChip();
91
+
92
+ await p1.locator('[data-working-chip]').click();
93
+ await p1.waitForTimeout(200);
94
+ const afterReclick = await readChip();
95
+
96
+ // Re-pin then test Esc clears (R62 invariant)
97
+ await p1.locator('[data-working-chip]').click();
98
+ await p1.waitForTimeout(200);
99
+ await p1.keyboard.press('Escape');
100
+ await p1.waitForTimeout(200);
101
+ const afterEsc = await readChip();
102
+
103
+ await b1.close();
104
+
105
+ // State B: empty fleet (workingCount=0) — chip not interactive
106
+ const { browser: b2, page: p2 } = await probe(0, 3);
107
+ const emptyState = await p2.evaluate(() => {
108
+ const chip = document.querySelector('[data-working-chip]');
109
+ return {
110
+ clickable: chip?.getAttribute('data-working-chip-clickable'),
111
+ role: chip?.getAttribute('role'),
112
+ tabindex: chip?.getAttribute('tabindex'),
113
+ cursor: chip?.getAttribute('style')?.includes('cursor: pointer'),
114
+ };
115
+ });
116
+ await b2.close();
117
+
118
+ const results = {
119
+ // Initial (3 working / 5 total)
120
+ before_clickableTrue: before.clickable === 'true',
121
+ before_roleButton: before.role === 'button',
122
+ before_tabIndex0: before.tabindex === '0',
123
+ before_ariaPressedFalse: before.ariaPressed === 'false',
124
+ before_pinMirrorFalse: before.pinMirror === 'false',
125
+ before_cursorPointer: before.cursor === true,
126
+ before_noBoxShadow: before.boxShadow === false,
127
+
128
+ // After click — pinned
129
+ click_ariaPressedTrue: afterClick.ariaPressed === 'true',
130
+ click_pinMirrorTrue: afterClick.pinMirror === 'true',
131
+ click_boxShadowSet: afterClick.boxShadow === true,
132
+
133
+ // After reclick — released
134
+ reclick_ariaPressedFalse: afterReclick.ariaPressed === 'false',
135
+ reclick_pinMirrorFalse: afterReclick.pinMirror === 'false',
136
+
137
+ // Esc clears pin
138
+ esc_ariaPressedFalse: afterEsc.ariaPressed === 'false',
139
+ esc_pinMirrorFalse: afterEsc.pinMirror === 'false',
140
+
141
+ // Empty fleet — not interactive
142
+ empty_clickableFalse: emptyState.clickable === 'false',
143
+ empty_noRole: emptyState.role === null || emptyState.role === undefined,
144
+ empty_noTabIndex: emptyState.tabindex === null || emptyState.tabindex === undefined,
145
+ empty_noCursor: !emptyState.cursor,
146
+ };
147
+ const ok = Object.values(results).every(Boolean);
148
+ console.log(`${ok ? '✅' : '❌'} working chip pin:`, JSON.stringify(results),
149
+ `\n before=`, before,
150
+ `\n afterClick=`, afterClick,
151
+ `\n afterReclick=`, afterReclick,
152
+ `\n afterEsc=`, afterEsc,
153
+ `\n emptyState=`, emptyState);
154
+ process.exit(ok ? 0 : 1);