@sleep2agi/agent-network-dashboard 0.4.6-preview.9 → 0.4.6-preview.91

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 (296) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +69 -55
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/routes-manifest.json +6 -0
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +2 -2
  17. package/.next/server/app/_not-found.rsc +12 -12
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  19. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  20. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  24. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  25. package/.next/server/app/admin.html +2 -2
  26. package/.next/server/app/admin.rsc +14 -14
  27. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  28. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  29. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  30. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  31. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  32. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  33. package/.next/server/app/api/hub/servers/route/app-paths-manifest.json +3 -0
  34. package/.next/server/app/api/hub/servers/route/build-manifest.json +9 -0
  35. package/.next/server/app/api/hub/servers/route/server-reference-manifest.json +4 -0
  36. package/.next/server/app/api/hub/servers/route.js +7 -0
  37. package/.next/server/app/api/hub/servers/route.js.map +5 -0
  38. package/.next/server/app/api/hub/servers/route.js.nft.json +1 -0
  39. package/.next/server/app/api/hub/servers/route_client-reference-manifest.js +3 -0
  40. package/.next/server/app/index.html +2 -2
  41. package/.next/server/app/index.rsc +14 -14
  42. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  43. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  44. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  45. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  46. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  47. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/login.html +2 -2
  49. package/.next/server/app/login.rsc +14 -14
  50. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  55. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  56. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/logs.html +2 -2
  58. package/.next/server/app/logs.rsc +14 -14
  59. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  60. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  61. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  62. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  64. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  65. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app/messages.html +2 -2
  67. package/.next/server/app/messages.rsc +14 -14
  68. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  69. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  70. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  71. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  73. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  74. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/node.html +2 -2
  76. package/.next/server/app/node.rsc +14 -14
  77. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  78. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  79. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  80. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  82. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  83. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/nodes.html +2 -2
  85. package/.next/server/app/nodes.rsc +14 -14
  86. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  87. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  88. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  89. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  90. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  91. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  92. package/.next/server/app/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/server-logs.html +2 -2
  95. package/.next/server/app/server-logs.rsc +14 -14
  96. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  97. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  98. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  99. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  100. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  101. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  102. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  103. package/.next/server/app/settings/networks.html +2 -2
  104. package/.next/server/app/settings/networks.rsc +14 -14
  105. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  106. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  107. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  108. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  109. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  110. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  111. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  112. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  113. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  114. package/.next/server/app/settings/tokens.html +2 -2
  115. package/.next/server/app/settings/tokens.rsc +14 -14
  116. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  117. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  118. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  119. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  120. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  121. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  122. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  123. package/.next/server/app/settings.html +2 -2
  124. package/.next/server/app/settings.rsc +14 -14
  125. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  126. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  127. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  128. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  130. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  131. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  132. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  133. package/.next/server/app/tasks.html +2 -2
  134. package/.next/server/app/tasks.rsc +14 -14
  135. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  136. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  137. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  138. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  139. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  140. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  141. package/.next/server/app-paths-manifest.json +1 -0
  142. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js +3 -0
  143. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js.map +1 -0
  144. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +3 -0
  145. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -0
  146. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
  147. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
  148. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  149. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  150. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
  151. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
  152. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +2 -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/08pu6p-oh~oa1.js +1 -0
  186. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  187. package/.next/static/chunks/0oknosftxc9lm.css +2 -0
  188. package/.next/static/chunks/0p_caune7bdi8.js +2 -0
  189. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  190. package/.next/static/chunks/0tvn2l1pc.h65.js +1 -0
  191. package/.next/static/chunks/0w3m56zhfnsnx.js +7 -0
  192. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  193. package/.next/static/chunks/0yxrc2w2b71zj.js +1 -0
  194. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  195. package/.next/trace +2 -2
  196. package/.next/trace-build +1 -1
  197. package/.next/types/routes.d.ts +2 -1
  198. package/.next/types/validator.ts +9 -0
  199. package/app/api/hub/servers/route.ts +39 -0
  200. package/app/components/AppShell.tsx +2 -0
  201. package/app/components/ChatPopover.tsx +38 -2
  202. package/app/components/CommandPalette.tsx +51 -0
  203. package/app/components/HelpOverlay.tsx +55 -0
  204. package/app/components/ServersDrawer.tsx +206 -0
  205. package/app/components/TopoGraph.tsx +1636 -157
  206. package/app/components/utils.ts +8 -7
  207. package/app/lib/time.ts +36 -0
  208. package/package.json +1 -1
  209. package/scripts/help-overlay-topo-test.mjs +44 -0
  210. package/scripts/servers-drawer-live-test.mjs +96 -0
  211. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  212. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  213. package/scripts/topo-active-links-chip-test.mjs +73 -0
  214. package/scripts/topo-arrival-ping-test.mjs +87 -0
  215. package/scripts/topo-arrow-tier-test.mjs +61 -0
  216. package/scripts/topo-auto-fit-test.mjs +73 -0
  217. package/scripts/topo-chat-focus-ring-test.mjs +77 -0
  218. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  219. package/scripts/topo-click-ripple-test.mjs +66 -0
  220. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  221. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  222. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  223. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  224. package/scripts/topo-dispatch-pulse-test.mjs +83 -0
  225. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  226. package/scripts/topo-edge-focus-test.mjs +113 -0
  227. package/scripts/topo-edge-hover-test.mjs +93 -0
  228. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  229. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  230. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  231. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  232. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  233. package/scripts/topo-fit-key-test.mjs +106 -0
  234. package/scripts/topo-freshness-chip-test.mjs +74 -0
  235. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  236. package/scripts/topo-group-count-test.mjs +68 -0
  237. package/scripts/topo-group-hover-test.mjs +82 -0
  238. package/scripts/topo-group-pin-test.mjs +119 -0
  239. package/scripts/topo-group-status-pips-test.mjs +103 -0
  240. package/scripts/topo-groupbox-test.mjs +4 -2
  241. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  242. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  243. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  244. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  245. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  246. package/scripts/topo-hub-busyness-test.mjs +100 -0
  247. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  248. package/scripts/topo-hub-title-test.mjs +93 -0
  249. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  250. package/scripts/topo-label-lift-test.mjs +119 -0
  251. package/scripts/topo-label-scale-test.mjs +78 -0
  252. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  253. package/scripts/topo-layout-key-test.mjs +91 -0
  254. package/scripts/topo-legend-hover-test.mjs +113 -0
  255. package/scripts/topo-legend-pin-test.mjs +128 -0
  256. package/scripts/topo-minimap-test.mjs +68 -0
  257. package/scripts/topo-mobile-chips-test.mjs +103 -0
  258. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  259. package/scripts/topo-offline-dim-test.mjs +70 -0
  260. package/scripts/topo-overlap-test.mjs +32 -5
  261. package/scripts/topo-pan-cursor-test.mjs +62 -0
  262. package/scripts/topo-panel-elevation-test.mjs +76 -0
  263. package/scripts/topo-pin-persistence-test.mjs +105 -0
  264. package/scripts/topo-popover-meta-test.mjs +78 -0
  265. package/scripts/topo-pressure-bar-test.mjs +69 -0
  266. package/scripts/topo-pressure-hover-test.mjs +123 -0
  267. package/scripts/topo-pressure-pin-test.mjs +119 -0
  268. package/scripts/topo-pulse-rate-test.mjs +71 -0
  269. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  270. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  271. package/scripts/topo-reduced-motion-test.mjs +77 -0
  272. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  273. package/scripts/topo-tier-ring-test.mjs +69 -0
  274. package/scripts/topo-tier-stagger-test.mjs +89 -0
  275. package/scripts/topo-tz-ghost-test.mjs +69 -0
  276. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  277. package/scripts/topo-wheel-zoom-test.mjs +79 -0
  278. package/scripts/topo-zoom-keys-test.mjs +89 -0
  279. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  280. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  281. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  282. package/.next/static/chunks/0_rmxom9~1lmk.css +0 -2
  283. package/.next/static/chunks/0cj5qr_a8dzky.js +0 -7
  284. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  285. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  286. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  287. package/.next/static/chunks/0kgak.tfh2-~i.js +0 -1
  288. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  289. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  290. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  291. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  292. package/.next/static/chunks/131.zrqhdbu5x.js +0 -1
  293. package/.next/static/chunks/165lqf5.z.n10.js +0 -1
  294. /package/.next/static/{T0hK2WE0kVtyFouPsSpos → EgsOXmeDYX7rzWQpipoVB}/_buildManifest.js +0 -0
  295. /package/.next/static/{T0hK2WE0kVtyFouPsSpos → EgsOXmeDYX7rzWQpipoVB}/_clientMiddlewareManifest.js +0 -0
  296. /package/.next/static/{T0hK2WE0kVtyFouPsSpos → EgsOXmeDYX7rzWQpipoVB}/_ssgManifest.js +0 -0
@@ -0,0 +1,73 @@
1
+ /* Round 28 verification: first-paint auto-fit zoom when grid content
2
+ * would overflow viewBox; respects persisted view; doesn't fire on
3
+ * small fleets (no overflow). */
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
+
10
+ async function probe({ fleetSize, persisted }) {
11
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
12
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
13
+ await ctx.addInitScript(([pers]) => {
14
+ try {
15
+ localStorage.setItem('anet-theme', 'cyber');
16
+ localStorage.setItem('anet-topo-layout', 'grid');
17
+ if (pers) localStorage.setItem('anet-topo-view', JSON.stringify(pers));
18
+ else localStorage.removeItem('anet-topo-view');
19
+ sessionStorage.setItem('anet_v3_auth', '1');
20
+ } catch {}
21
+ }, [persisted]);
22
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
23
+ await ctx.route('**/api/hub/status*', async (route) => {
24
+ const r = await route.fetch();
25
+ const b = await r.json();
26
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
27
+ // Use prefix groups with 5 members each — 7 bands stack vertically
28
+ // and overflow the 680-px viewBox at cellH ≥ 81 (Round 27 floor).
29
+ const aliases = Array.from({ length: fleetSize }, (_, i) => {
30
+ const groups = ['A站', 'B站', 'C站', 'D站', 'E站', 'F站', 'G站', 'H站', 'I站', 'J站'];
31
+ const group = groups[Math.floor(i / 5) % groups.length];
32
+ return `${group}n${i + 1}`;
33
+ });
34
+ const sessions = aliases.map(a => ({
35
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
36
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
37
+ }));
38
+ await route.fulfill({ response: r, json: { ...b, sessions } });
39
+ });
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) => {
44
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
45
+ return !!svg && svg.querySelectorAll('g[data-node]').length === n;
46
+ }, fleetSize, { timeout: 30000 }).catch(() => {});
47
+ await page.waitForTimeout(600);
48
+ const zoom = await page.evaluate(() => {
49
+ const span = document.querySelector('button[aria-label="Zoom in"]').parentElement.querySelector('span[title*="zoom level"]');
50
+ return +span.textContent.replace('%', '');
51
+ });
52
+ await ctx.close();
53
+ return zoom;
54
+ }
55
+
56
+ // 1. Big fleet (35 nodes / 7 bands → overflows ~680-px viewBox at cellH≥81)
57
+ // with no persisted view → auto-fit triggers (zoom < 100%)
58
+ const bigFresh = await probe({ fleetSize: 35 });
59
+ // 2. Big fleet, user has persisted view (zoom=1) → respect it, no auto-fit
60
+ const bigPersisted = await probe({ fleetSize: 35, persisted: { zoom: 1, x: 0, y: 0 } });
61
+ // 3. Small fleet, no persisted view → no overflow, zoom stays 100%
62
+ const smallFresh = await probe({ fleetSize: 6 });
63
+
64
+ await browser.close();
65
+ const results = {
66
+ bigFleetAutoFits: bigFresh < 100 && bigFresh >= 50,
67
+ persistedRespected: bigPersisted === 100,
68
+ smallFleetStays100: smallFresh === 100,
69
+ };
70
+ const ok = Object.values(results).every(Boolean);
71
+ console.log(`${ok ? '✅' : '❌'} auto-fit:`, JSON.stringify(results),
72
+ `bigFresh=${bigFresh}% bigPersisted=${bigPersisted}% smallFresh=${smallFresh}%`);
73
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,77 @@
1
+ /* Round 11 verification: clicking a node opens the ChatPopover AND draws a
2
+ * persistent chat-focus ring on that node (strokeWidth=2.5, status colour).
3
+ * Closing the popover removes the ring. The ring's strokeWidth stays clear
4
+ * of the overlap-test selectors (1.5 / 3). */
5
+ import { chromium } from 'playwright';
6
+ import { readFileSync } from 'node:fs';
7
+
8
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
9
+ const browser = await chromium.launch({ headless: true });
10
+
11
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
12
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
13
+ await ctx.addInitScript(() => {
14
+ try {
15
+ localStorage.setItem('anet-theme', 'cyber');
16
+ localStorage.removeItem('anet-brand');
17
+ localStorage.removeItem('anet-topo-view');
18
+ localStorage.setItem('anet-topo-layout', 'grid');
19
+ localStorage.setItem('anet-topo-nodescale', '1'); // L so radius=26
20
+ sessionStorage.setItem('anet_v3_auth', '1');
21
+ } catch {}
22
+ });
23
+ await ctx.route('**/api/hub/status*', async (route) => {
24
+ const r = await route.fetch();
25
+ const b = await r.json();
26
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
27
+ const sessions = ['A站马', 'B站马', 'C站马'].map(a => ({
28
+ alias: a, status: 'idle', network_id: nid,
29
+ project_dir: null,
30
+ created_at: '2026-05-15T00:00:00Z', updated_at: '2026-05-15T00:00:00Z',
31
+ last_seen_at: new Date().toISOString(),
32
+ }));
33
+ await route.fulfill({ response: r, json: { ...b, sessions } });
34
+ });
35
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
36
+ const page = await ctx.newPage();
37
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
38
+ await page.waitForFunction(() => {
39
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
40
+ return !!svg && svg.querySelectorAll('g[data-node]').length >= 3;
41
+ }, { timeout: 30000 }).catch(() => {});
42
+ await page.waitForTimeout(400);
43
+
44
+ // Pre-click: NO chat-focus ring on any node (strokeWidth=2.5, no group-hover class)
45
+ const ringsBefore = await page.$$eval('svg[viewBox="0 0 1000 680"] g[data-node] circle[stroke-width="2.5"]', els => els.length);
46
+
47
+ // Click A站马 → opens chat popover
48
+ await page.locator('g[data-node="A站马"]').click();
49
+ await page.waitForTimeout(300);
50
+
51
+ const ringsAfter = await page.$$eval('svg[viewBox="0 0 1000 680"] g[data-node] circle[stroke-width="2.5"]', els => els.length);
52
+ const ringOnTarget = await page.$eval(
53
+ 'g[data-node="A站马"] circle[stroke-width="2.5"]',
54
+ el => ({ r: +el.getAttribute('r'), opacity: +el.getAttribute('opacity') })
55
+ ).catch(() => null);
56
+
57
+ // Confirm popover is open
58
+ const popover = await page.locator('[role="dialog"], [data-chat-popover]').count();
59
+
60
+ // Ring should be ONLY on the clicked node, not on the others
61
+ const ringOnOther = await page.$$eval('g[data-node="B站马"] circle[stroke-width="2.5"], g[data-node="C站马"] circle[stroke-width="2.5"]', els => els.length);
62
+
63
+ const results = {
64
+ noRingBefore: ringsBefore === 0,
65
+ oneRingAfter: ringsAfter === 1,
66
+ ringOnTargetNode: !!ringOnTarget && ringOnTarget.r === 40, // radius=26 + 14
67
+ ringNotOnOthers: ringOnOther === 0,
68
+ };
69
+
70
+ // Re-run the overlap selector to confirm the chat-focus ring isn't mis-counted as a status ring
71
+ const statusRings = await page.$$eval('svg[viewBox="0 0 1000 680"] g[data-node] circle[stroke-width="3"], svg[viewBox="0 0 1000 680"] g[data-node] circle[stroke-width="1.5"]', els => els.length);
72
+ results.statusRingCountUnchanged = statusRings === 3; // exactly one per online node
73
+
74
+ await browser.close();
75
+ const ok = Object.values(results).every(Boolean);
76
+ console.log(`${ok ? '✅' : '❌'} chat-focus ring:`, JSON.stringify(results));
77
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,113 @@
1
+ /* Round 82 verification: chip-row "N working" + "N online" chips mirror
2
+ * the pin state already shown by the R60 pressure-bar segments. When
3
+ * pinnedStatus === 'working', the working chip lights up via inset
4
+ * boxShadow + data-pin-mirror="true"; idle pin lights the online chip.
5
+ * Closes the visual inconsistency where pressure-bar + legend showed
6
+ * the pin but the sibling count chips stayed flat.
7
+ *
8
+ * Drive the pin via Cmd+K palette (R69 action) to verify the chip
9
+ * reflects a pin set from a non-chip surface — exercises the full
10
+ * coupling, not just a chip clicking itself.
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: 1200 } });
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
+ sessionStorage.setItem('anet_v3_auth', '1');
24
+ sessionStorage.removeItem('anet-topo-pinned-status');
25
+ sessionStorage.removeItem('anet-topo-pinned-group');
26
+ } catch {}
27
+ });
28
+
29
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
30
+ await ctx.route('**/api/hub/status*', async (route) => {
31
+ const r = await route.fetch();
32
+ const b = await r.json();
33
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
34
+ const sessions = [
35
+ { alias: 'alpha', status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
36
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
37
+ { alias: 'beta', status: 'idle', model: 'gpt-5', runtime: 'codex-cli',
38
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
39
+ ];
40
+ await route.fulfill({ response: r, json: { ...b, sessions } });
41
+ });
42
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
46
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
47
+ await page.waitForTimeout(400);
48
+
49
+ const snap = () => page.evaluate(() => {
50
+ const w = document.querySelector('[data-working-chip]');
51
+ const o = document.querySelector('[data-online-chip]');
52
+ return {
53
+ working: {
54
+ mirror: w?.getAttribute('data-pin-mirror'),
55
+ shadow: w ? (w.getAttribute('style') || '').includes('inset') : false,
56
+ title: w?.getAttribute('title') || '',
57
+ },
58
+ online: {
59
+ mirror: o?.getAttribute('data-pin-mirror'),
60
+ shadow: o ? (o.getAttribute('style') || '').includes('inset') : false,
61
+ title: o?.getAttribute('title') || '',
62
+ },
63
+ };
64
+ });
65
+
66
+ const before = await snap();
67
+
68
+ // Dispatch the R69 CustomEvent directly — same coupling the palette
69
+ // uses, deterministic without fuzzy-match ambiguity. The R69 listener
70
+ // in TopoGraph reads `detail.kind === 'status'` + `detail.value`.
71
+ const pin = async (value) => {
72
+ await page.evaluate((v) => {
73
+ try { sessionStorage.setItem('anet-topo-pinned-status', v); } catch {}
74
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'status', value: v } }));
75
+ }, value);
76
+ await page.waitForTimeout(200);
77
+ };
78
+ const clear = async () => {
79
+ await page.keyboard.press('Escape');
80
+ await page.waitForTimeout(250);
81
+ };
82
+
83
+ await pin('working');
84
+ const onWorking = await snap();
85
+ await clear();
86
+ const afterWorking = await snap();
87
+
88
+ await pin('idle');
89
+ const onIdle = await snap();
90
+ await clear();
91
+ const afterIdle = await snap();
92
+
93
+ await browser.close();
94
+
95
+ const results = {
96
+ before_workingFlat: before.working.mirror === 'false' && !before.working.shadow,
97
+ before_onlineFlat: before.online.mirror === 'false' && !before.online.shadow,
98
+ workingPin_workingLit: onWorking.working.mirror === 'true' && onWorking.working.shadow,
99
+ workingPin_onlineFlat: onWorking.online.mirror === 'false' && !onWorking.online.shadow,
100
+ workingPin_titleHint: /Pinned/i.test(onWorking.working.title),
101
+ esc_workingFlat: afterWorking.working.mirror === 'false' && !afterWorking.working.shadow,
102
+ esc_onlineUntouched: afterWorking.online.mirror === 'false' && !afterWorking.online.shadow,
103
+ idlePin_onlineLit: onIdle.online.mirror === 'true' && onIdle.online.shadow,
104
+ idlePin_workingFlat: onIdle.working.mirror === 'false' && !onIdle.working.shadow,
105
+ idlePin_titleHint: /Pinned/i.test(onIdle.online.title),
106
+ esc_onlineFlat: afterIdle.online.mirror === 'false' && !afterIdle.online.shadow,
107
+ };
108
+ const ok = Object.values(results).every(Boolean);
109
+ console.log(`${ok ? '✅' : '❌'} chip pin-mirror:`, JSON.stringify(results),
110
+ `\n before=`, before,
111
+ `\n onWorking=`, onWorking,
112
+ `\n onIdle=`, onIdle);
113
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,66 @@
1
+ /* Round 14 verification: clicking a node spawns a one-shot ripple circle
2
+ * (strokeWidth=2, status-coloured, ~500ms) that's gone by t=800ms. */
3
+ import { chromium } from 'playwright';
4
+ import { readFileSync } from 'node:fs';
5
+
6
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
7
+ const browser = await chromium.launch({ headless: true });
8
+
9
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
10
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
11
+ await ctx.addInitScript(() => {
12
+ try {
13
+ localStorage.setItem('anet-theme', 'cyber');
14
+ localStorage.removeItem('anet-brand');
15
+ localStorage.removeItem('anet-topo-view');
16
+ localStorage.setItem('anet-topo-layout', 'grid');
17
+ localStorage.setItem('anet-topo-nodescale', '1');
18
+ sessionStorage.setItem('anet_v3_auth', '1');
19
+ } catch {}
20
+ });
21
+ await ctx.route('**/api/hub/status*', async (route) => {
22
+ const r = await route.fetch();
23
+ const b = await r.json();
24
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
25
+ const sessions = ['ripA', 'ripB'].map(a => ({
26
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
27
+ created_at: '2026-05-15T00:00:00Z', updated_at: '2026-05-15T00:00:00Z',
28
+ last_seen_at: new Date().toISOString(),
29
+ }));
30
+ await route.fulfill({ response: r, json: { ...b, sessions } });
31
+ });
32
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
33
+ const page = await ctx.newPage();
34
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
35
+ await page.waitForFunction(() => {
36
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
37
+ return !!svg && svg.querySelectorAll('g[data-node]').length >= 2;
38
+ }, { timeout: 30000 }).catch(() => {});
39
+ await page.waitForTimeout(400);
40
+
41
+ // Ripple circle = direct child of the zoom/pan <g>, strokeWidth=2, with a
42
+ // <animate> child whose attributeName="r" (distinguishes it from the
43
+ // hover ring, which has strokeWidth=2 but no <animate> children).
44
+ const ripplePresent = async () => page.evaluate(() => {
45
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
46
+ for (const c of svg.querySelectorAll('circle[stroke-width="2"]')) {
47
+ const anims = c.querySelectorAll('animate');
48
+ if (anims.length && [...anims].some(a => a.getAttribute('attributeName') === 'r')) return true;
49
+ }
50
+ return false;
51
+ });
52
+
53
+ const before = await ripplePresent();
54
+ await page.locator('g[data-node="ripA"]').click();
55
+ // Sample at ~150ms (mid-animation) — ripple should be present.
56
+ await page.waitForTimeout(150);
57
+ const during = await ripplePresent();
58
+ // After 800ms total — well past the 600ms clear timer.
59
+ await page.waitForTimeout(700);
60
+ const after = await ripplePresent();
61
+
62
+ await browser.close();
63
+ const results = { noRippleBefore: before === false, rippleDuring: during === true, rippleClearedAfter: after === false };
64
+ const ok = Object.values(results).every(Boolean);
65
+ console.log(`${ok ? '✅' : '❌'} click ripple:`, JSON.stringify(results));
66
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,108 @@
1
+ /* Round 74 verification: Cmd+K palette can toggle layout + fit canvas.
2
+ * Same architecture as R69 — palette dispatches a window CustomEvent,
3
+ * TopoGraph reduces it into state.
4
+ *
5
+ * - Start in ring layout (default). Dispatch anet:topo-layout {toggle}
6
+ * → SVG re-renders in grid: zoom group's transform changes; no hub
7
+ * `<g data-topo-hub>` present (grid drops it).
8
+ * - Dispatch again → back to ring (hub re-appears).
9
+ * - Zoom in via keyboard `+` (3 times). Dispatch anet:topo-view {fit}
10
+ * → zoom transform returns to fitted state.
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: 1280, 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
+ sessionStorage.removeItem('anet-topo-pinned-status');
26
+ sessionStorage.removeItem('anet-topo-pinned-group');
27
+ } catch {}
28
+ });
29
+
30
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
31
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ const sessions = ['a', 'b', 'c'].map(a => ({
36
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
37
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
38
+ }));
39
+ await route.fulfill({ response: r, json: { ...b, sessions } });
40
+ });
41
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
42
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
43
+ const page = await ctx.newPage();
44
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
45
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
46
+ await page.waitForTimeout(500);
47
+
48
+ const readState = () => page.evaluate(() => {
49
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
50
+ const hub = svg ? svg.querySelector('g[data-topo-hub]') : null;
51
+ let zoomTransform = null;
52
+ if (svg) {
53
+ for (const g of svg.querySelectorAll(':scope > g')) {
54
+ const t = g.getAttribute('transform') || '';
55
+ if (t.includes('scale(')) { zoomTransform = t; break; }
56
+ }
57
+ }
58
+ return {
59
+ hubPresent: !!hub,
60
+ layoutStorage: localStorage.getItem('anet-topo-layout'),
61
+ transform: zoomTransform,
62
+ };
63
+ });
64
+
65
+ const startState = await readState(); // ring → hub present
66
+
67
+ // Dispatch toggle → grid
68
+ await page.evaluate(() => window.dispatchEvent(new CustomEvent('anet:topo-layout', { detail: { kind: 'toggle' } })));
69
+ await page.waitForTimeout(300);
70
+ const afterToggle1 = await readState(); // grid → hub gone
71
+
72
+ // Dispatch toggle again → ring
73
+ await page.evaluate(() => window.dispatchEvent(new CustomEvent('anet:topo-layout', { detail: { kind: 'toggle' } })));
74
+ await page.waitForTimeout(300);
75
+ const afterToggle2 = await readState(); // ring → hub present
76
+
77
+ // Now test fit. Zoom in 3 times via `+` key.
78
+ await page.keyboard.press('+');
79
+ await page.keyboard.press('+');
80
+ await page.keyboard.press('+');
81
+ await page.waitForTimeout(200);
82
+ const zoomedState = await readState();
83
+
84
+ // Dispatch view fit
85
+ await page.evaluate(() => window.dispatchEvent(new CustomEvent('anet:topo-view', { detail: { kind: 'fit' } })));
86
+ await page.waitForTimeout(250);
87
+ const afterFitState = await readState();
88
+
89
+ await browser.close();
90
+
91
+ const results = {
92
+ start_ring_hubPresent: startState.hubPresent === true,
93
+ start_storageRing: startState.layoutStorage === 'ring',
94
+ toggle1_gridHubAbsent: afterToggle1.hubPresent === false,
95
+ toggle1_storageGrid: afterToggle1.layoutStorage === 'grid',
96
+ toggle2_ringHubBack: afterToggle2.hubPresent === true,
97
+ toggle2_storageRing: afterToggle2.layoutStorage === 'ring',
98
+ zoom_transformChanged: zoomedState.transform !== afterToggle2.transform,
99
+ fit_transformReturnsToFit: afterFitState.transform !== zoomedState.transform,
100
+ };
101
+ const ok = Object.values(results).every(Boolean);
102
+ console.log(`${ok ? '✅' : '❌'} cmdk layout+view:`, JSON.stringify(results),
103
+ `\n start=`, startState,
104
+ `\n toggle1=`, afterToggle1,
105
+ `\n toggle2=`, afterToggle2,
106
+ `\n zoomed=`, zoomedState,
107
+ `\n afterFit=`, afterFitState);
108
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,123 @@
1
+ /* Round 69 verification: Cmd+K palette can pin topology filters from
2
+ * anywhere. The palette dispatches a CustomEvent('anet:topo-pin') that
3
+ * TopoGraph listens for and translates into pinnedStatus / pinnedGroup
4
+ * state. sessionStorage is written in lockstep so a reload preserves it
5
+ * (R66 carries it through).
6
+ *
7
+ * - Dispatch {kind:'status', value:'working'} → pill appears + idle/
8
+ * offline nodes dim. sessionStorage holds 'working'.
9
+ * - Dispatch {kind:'clear'} → pill disappears + opacities restore.
10
+ * sessionStorage entries cleared.
11
+ * - Dispatch {kind:'group', value:'alpha'} → group pill + opacities
12
+ * composed; storage holds 'alpha'.
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: 1500 } });
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', 'grid');
26
+ sessionStorage.setItem('anet_v3_auth', '1');
27
+ sessionStorage.removeItem('anet-topo-pinned-status');
28
+ sessionStorage.removeItem('anet-topo-pinned-group');
29
+ } catch {}
30
+ });
31
+
32
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
33
+ await ctx.route('**/api/hub/status*', async (route) => {
34
+ const r = await route.fetch();
35
+ const b = await r.json();
36
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
37
+ const sessions = [
38
+ { alias: 'alpha1', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
39
+ { alias: 'alpha2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
40
+ { alias: 'alpha3', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
41
+ { alias: 'beta', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
42
+ ];
43
+ await route.fulfill({ response: r, json: { ...b, sessions } });
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
+ const page = await ctx.newPage();
48
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
49
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
50
+ await page.waitForTimeout(500);
51
+
52
+ const snapshot = () => page.evaluate(() => {
53
+ const pills = [...document.querySelectorAll('[data-active-filter]')].map(el => ({
54
+ kind: el.getAttribute('data-active-filter'),
55
+ text: (el.innerText || el.textContent || '').trim(),
56
+ }));
57
+ const opacities = {};
58
+ for (const a of ['alpha1', 'alpha2', 'beta']) {
59
+ const g = document.querySelector(`g[data-node="${a}"]`);
60
+ opacities[a] = g ? +(g.style.opacity || '1') : null;
61
+ }
62
+ return {
63
+ pills,
64
+ opacities,
65
+ storageStatus: sessionStorage.getItem('anet-topo-pinned-status'),
66
+ storageGroup: sessionStorage.getItem('anet-topo-pinned-group'),
67
+ };
68
+ });
69
+
70
+ const before = await snapshot();
71
+
72
+ // Dispatch palette pin: status=working
73
+ await page.evaluate(() => {
74
+ // The Cmd+K commands write to storage AND dispatch the event in
75
+ // lockstep. We mirror both here so we exercise the same code path
76
+ // a palette `perform()` would take.
77
+ sessionStorage.setItem('anet-topo-pinned-status', 'working');
78
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'status', value: 'working' } }));
79
+ });
80
+ await page.waitForTimeout(250);
81
+ const afterStatus = await snapshot();
82
+
83
+ // Dispatch palette pin: group=alpha (additionally)
84
+ await page.evaluate(() => {
85
+ sessionStorage.setItem('anet-topo-pinned-group', 'alpha');
86
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'group', value: 'alpha' } }));
87
+ });
88
+ await page.waitForTimeout(250);
89
+ const afterGroup = await snapshot();
90
+
91
+ // Dispatch palette clear
92
+ await page.evaluate(() => {
93
+ sessionStorage.removeItem('anet-topo-pinned-status');
94
+ sessionStorage.removeItem('anet-topo-pinned-group');
95
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'clear' } }));
96
+ });
97
+ await page.waitForTimeout(250);
98
+ const afterClear = await snapshot();
99
+
100
+ await browser.close();
101
+
102
+ const bright = (v) => v != null && v >= 0.55;
103
+ const dim = (v) => v != null && v < 0.4;
104
+ const results = {
105
+ before_noPills: before.pills.length === 0,
106
+ before_baselineBright: bright(before.opacities.alpha1) && bright(before.opacities.alpha2),
107
+ status_pillRendered: afterStatus.pills.some(p => p.kind === 'status' && /working/.test(p.text)),
108
+ status_dimsIdle: dim(afterStatus.opacities.alpha2) && dim(afterStatus.opacities.beta),
109
+ status_keepsWorking: bright(afterStatus.opacities.alpha1),
110
+ status_storageWritten: afterStatus.storageStatus === 'working',
111
+ groupAdded_bothPills: afterGroup.pills.length === 2,
112
+ groupAdded_storageBoth: afterGroup.storageStatus === 'working' && afterGroup.storageGroup === 'alpha',
113
+ clear_noPills: afterClear.pills.length === 0,
114
+ clear_restoredOpacities: bright(afterClear.opacities.alpha1) && bright(afterClear.opacities.alpha2) && bright(afterClear.opacities.beta),
115
+ clear_storageCleared: afterClear.storageStatus === null && afterClear.storageGroup === null,
116
+ };
117
+ const ok = Object.values(results).every(Boolean);
118
+ console.log(`${ok ? '✅' : '❌'} cmdk-pin:`, JSON.stringify(results),
119
+ `\n before=`, before,
120
+ `\n afterStatus=`, afterStatus,
121
+ `\n afterGroup=`, afterGroup,
122
+ `\n afterClear=`, afterClear);
123
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,64 @@
1
+ /* Round 33 verification: hover tooltip carries `cwd: <project_dir>` on a
2
+ * second line when reported; falls back to the identity-only line
3
+ * otherwise; no `cwd:` line for nodes without project_dir. */
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: 900 } });
10
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
11
+ await ctx.addInitScript(() => {
12
+ try {
13
+ localStorage.setItem('anet-theme', 'cyber');
14
+ localStorage.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 = [
24
+ {
25
+ alias: 'full', status: 'idle', network_id: nid,
26
+ project_dir: '/home/vansin/agent-orchestra',
27
+ model: 'claude-opus-4-7', runtime: 'cli-claude-code',
28
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
29
+ },
30
+ {
31
+ alias: 'cwdOnly', status: 'idle', network_id: nid,
32
+ project_dir: '/home/vansin/some-project',
33
+ model: null, runtime: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ },
36
+ {
37
+ alias: 'bare', status: 'idle', network_id: nid,
38
+ project_dir: null, model: null, runtime: null,
39
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
40
+ },
41
+ ];
42
+ await route.fulfill({ response: r, json: { ...b, sessions } });
43
+ });
44
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
48
+ await page.waitForTimeout(400);
49
+
50
+ const titleOf = alias => page.$eval(`g[data-node="${alias}"] title`, el => el.textContent);
51
+ const full = await titleOf('full');
52
+ const cwdOnly = await titleOf('cwdOnly');
53
+ const bare = await titleOf('bare');
54
+
55
+ await browser.close();
56
+ const results = {
57
+ fullHasIdentityAndCwd: /claude-opus-4-7/.test(full) && /cwd: \/home\/vansin\/agent-orchestra/.test(full) && full.includes('\n'),
58
+ cwdOnlyShowsAliasAndCwd: cwdOnly.startsWith('cwdOnly') && /cwd: \/home\/vansin\/some-project/.test(cwdOnly),
59
+ bareShowsAliasOnly: bare === 'bare',
60
+ };
61
+ const ok = Object.values(results).every(Boolean);
62
+ console.log(`${ok ? '✅' : '❌'} cwd tooltip:`, JSON.stringify(results),
63
+ `\n full=${JSON.stringify(full)}\n cwdOnly=${JSON.stringify(cwdOnly)}\n bare=${JSON.stringify(bare)}`);
64
+ process.exit(ok ? 0 : 1);