@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,82 @@
1
+ /* Round 18 verification: hovering a node in a multi-member group upgrades
2
+ * the group-box to accent treatment (solid stroke, thicker, legendAccent
3
+ * colour) and brightens the label; other groups dim to 0.28; resting
4
+ * appearance unchanged. */
5
+ import { chromium } from 'playwright';
6
+ import { readFileSync } from 'node:fs';
7
+
8
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
9
+ const browser = await chromium.launch({ headless: true });
10
+
11
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
12
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
13
+ await ctx.addInitScript(() => {
14
+ try {
15
+ localStorage.setItem('anet-theme', 'cyber');
16
+ localStorage.removeItem('anet-brand');
17
+ localStorage.removeItem('anet-topo-view');
18
+ localStorage.setItem('anet-topo-layout', 'grid');
19
+ sessionStorage.setItem('anet_v3_auth', '1');
20
+ } catch {}
21
+ });
22
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
23
+ await ctx.route('**/api/hub/status*', async (route) => {
24
+ const r = await route.fetch();
25
+ const b = await r.json();
26
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
27
+ // Two prefix groups (A站 / B站), each ≥2 members so groupBoxes renders.
28
+ const aliases = ['A站红', 'A站蓝', 'A站绿', 'B站乙', 'B站丙', 'B站丁'];
29
+ const sessions = aliases.map(a => ({
30
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
31
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
32
+ }));
33
+ await route.fulfill({ response: r, json: { ...b, sessions } });
34
+ });
35
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
36
+ const page = await ctx.newPage();
37
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
38
+ await page.waitForFunction(() => {
39
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
40
+ return !!svg && svg.querySelectorAll('g[data-group]').length === 2;
41
+ }, { timeout: 30000 }).catch(() => {});
42
+ await page.waitForTimeout(400);
43
+
44
+ const readGroup = key => page.evaluate(k => {
45
+ const g = document.querySelector(`g[data-group="${k}"]`);
46
+ if (!g) return null;
47
+ const rect = g.querySelector('rect');
48
+ const text = g.querySelector('text');
49
+ return {
50
+ opacity: parseFloat(getComputedStyle(g).opacity || '1'),
51
+ stroke: rect.getAttribute('stroke'),
52
+ strokeWidth: +rect.getAttribute('stroke-width'),
53
+ strokeDasharray: rect.getAttribute('stroke-dasharray'),
54
+ fillOpacity: rect.getAttribute('fill-opacity'),
55
+ textFill: text.getAttribute('fill'),
56
+ };
57
+ }, key);
58
+
59
+ // Resting state (no hover)
60
+ const restA = await readGroup('A站');
61
+ const restB = await readGroup('B站');
62
+
63
+ // Hover a node in group A站
64
+ await page.locator('g[data-node="A站红"]').hover();
65
+ await page.waitForTimeout(350); // allow the 200ms transition to settle
66
+
67
+ const hoverA = await readGroup('A站');
68
+ const hoverB = await readGroup('B站');
69
+
70
+ await browser.close();
71
+ const results = {
72
+ restADashedThin: restA?.strokeDasharray === '6 6' && restA?.strokeWidth === 1.5,
73
+ restBDashedThin: restB?.strokeDasharray === '6 6' && restB?.strokeWidth === 1.5,
74
+ hoveredASolidThick: hoverA?.strokeDasharray === 'none' && hoverA?.strokeWidth === 2,
75
+ hoveredAAccentStroke: hoverA?.stroke === '#67e8f9', // dark legendAccent
76
+ hoveredABrighterText: hoverA?.textFill === '#e5e7eb', // dark legendHeadline
77
+ otherBDimmedOpacity: Math.abs((hoverB?.opacity ?? 1) - 0.28) < 0.02,
78
+ otherBKeepsDashed: hoverB?.strokeDasharray === '6 6',
79
+ };
80
+ const ok = Object.values(results).every(Boolean);
81
+ console.log(`${ok ? '✅' : '❌'} group hover linkage:`, JSON.stringify(results));
82
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,119 @@
1
+ /* Round 63 verification: clicking the group label pins the group focus
2
+ * via pinnedGroup. activeGroup = hoveredGroup ?? pinnedGroup is read by
3
+ * the R8 inFocus dim mechanic and the R53 in-group edge boost.
4
+ *
5
+ * - Click a group label → that group's nodes stay bright, others dim
6
+ * via the existing inFocus formula (opacity 0.32).
7
+ * - Move mouse away → pin persists (transient hover cleared).
8
+ * - In-group edge between OTHER team members brightens 1.3× even
9
+ * without an alias hover.
10
+ * - Esc clears the pin (R62 extended).
11
+ * - aria-pressed flips on the group <text>.
12
+ */
13
+ import { chromium } from 'playwright';
14
+ import { readFileSync } from 'node:fs';
15
+
16
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
17
+ const browser = await chromium.launch({ headless: true });
18
+
19
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1500 } });
20
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
21
+ await ctx.addInitScript(() => {
22
+ try {
23
+ localStorage.setItem('anet-theme', 'cyber');
24
+ localStorage.setItem('anet-topo-layout', 'grid');
25
+ sessionStorage.setItem('anet_v3_auth', '1');
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: 'alpha1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
36
+ { alias: 'alpha2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
37
+ { alias: 'alpha3', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
38
+ { alias: 'beta', status: 'idle', 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
+ const now = new Date().toISOString();
43
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
44
+ // alpha2 → alpha3: both in alpha group, neither is the pinned-group's "exact alias"
45
+ { from_alias: 'alpha2', to_alias: 'alpha3', content: 'm', created_at: now },
46
+ // alpha1 → beta: alpha1 in pinned group, beta outside → should dim
47
+ { from_alias: 'alpha1', to_alias: 'beta', content: 'm', created_at: now },
48
+ ] } }));
49
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
50
+ const page = await ctx.newPage();
51
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
52
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
53
+ await page.waitForTimeout(600);
54
+
55
+ const nodeOpacities = () => page.evaluate(() => {
56
+ const o = {};
57
+ for (const a of ['alpha1', 'alpha2', 'alpha3', 'beta']) {
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 edgeOpacities = () => page.evaluate(() => {
64
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
65
+ const out = {};
66
+ for (const g of svg.querySelectorAll(':scope > g > g')) {
67
+ const t = g.querySelector('path[data-edge-hitbox] title');
68
+ if (!t) continue;
69
+ const route = (t.textContent || '').split('\n')[0];
70
+ const base = [...g.querySelectorAll(':scope > path')].find(
71
+ p => !p.hasAttribute('data-edge-hitbox') && p.hasAttribute('marker-end')
72
+ );
73
+ if (base) out[route] = +base.getAttribute('opacity');
74
+ }
75
+ return out;
76
+ });
77
+
78
+ const before = await nodeOpacities();
79
+ const beforeEdges = await edgeOpacities();
80
+
81
+ // Click the alpha group label via its R63 hitbox wrapper.
82
+ const alphaLabel = page.locator('g[data-group-label-hit="alpha"]').first();
83
+ const exists = await alphaLabel.count() > 0;
84
+ if (!exists) { console.log('❌ alpha group label hit not found'); process.exit(1); }
85
+ await alphaLabel.click({ force: true });
86
+ // Mouse away so we observe pin-only state.
87
+ await page.mouse.move(10, 10);
88
+ await page.waitForTimeout(300);
89
+ const afterPinNodes = await nodeOpacities();
90
+ const afterPinEdges = await edgeOpacities();
91
+ const ariaPressed = await page.evaluate(() => document.querySelector('g[data-group-label-hit="alpha"]')?.getAttribute('aria-pressed'));
92
+
93
+ // Esc to release.
94
+ await page.evaluate(() => document.activeElement && typeof document.activeElement.blur === 'function' && document.activeElement.blur());
95
+ await page.keyboard.press('Escape');
96
+ await page.waitForTimeout(300);
97
+ const afterEscNodes = await nodeOpacities();
98
+ const ariaAfterEsc = await page.evaluate(() => document.querySelector('g[data-group-label-hit="alpha"]')?.getAttribute('aria-pressed'));
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_baseline: bright(before.alpha1) && bright(before.alpha2) && bright(before.alpha3) && bright(before.beta),
106
+ pin_keepsAlphaTeam: bright(afterPinNodes.alpha1) && bright(afterPinNodes.alpha2) && bright(afterPinNodes.alpha3),
107
+ pin_dimsBeta: dim(afterPinNodes.beta),
108
+ pin_ariaPressed: ariaPressed === 'true',
109
+ pin_inGroupEdgeBoosts: afterPinEdges['alpha2 → alpha3'] > beforeEdges['alpha2 → alpha3'] * 1.15,
110
+ pin_leavingEdgeDims: afterPinEdges['alpha1 → beta'] < beforeEdges['alpha1 → beta'] * 0.6,
111
+ esc_releasesPin: bright(afterEscNodes.alpha1) && bright(afterEscNodes.beta),
112
+ esc_ariaBackToFalse: ariaAfterEsc === 'false',
113
+ };
114
+ const ok = Object.values(results).every(Boolean);
115
+ console.log(`${ok ? '✅' : '❌'} group pin:`, JSON.stringify(results),
116
+ `\n before nodes=`, before, ` edges=`, beforeEdges,
117
+ `\n afterPin nodes=`, afterPinNodes, ` edges=`, afterPinEdges, ` aria=${ariaPressed}`,
118
+ `\n afterEsc nodes=`, afterEscNodes, ` aria=${ariaAfterEsc}`);
119
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,103 @@
1
+ /* Round 58 verification: group label includes a status-mix pip strip
2
+ * showing how many members are working / idle / offline.
3
+ *
4
+ * Sessions (mixed-status alpha team + singleton beta):
5
+ * alpha1 — working
6
+ * alpha2 — working
7
+ * alpha3 — idle
8
+ * alpha4 — offline
9
+ * beta — idle (singleton, no group box)
10
+ *
11
+ * Grid layout draws a group box for alpha (4 members). Label should
12
+ * end in something like "alpha · 4 · 2w 1i 1o". Status pips render
13
+ * only when their count > 0, so a uniform group reads more simply.
14
+ */
15
+ import { chromium } from 'playwright';
16
+ import { readFileSync } from 'node:fs';
17
+
18
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
19
+ const browser = await chromium.launch({ headless: true });
20
+
21
+ async function probe(sessions) {
22
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1500 } });
23
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
24
+ await ctx.addInitScript(() => {
25
+ try {
26
+ localStorage.setItem('anet-theme', 'cyber');
27
+ localStorage.setItem('anet-topo-layout', 'grid');
28
+ sessionStorage.setItem('anet_v3_auth', '1');
29
+ } catch {}
30
+ });
31
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
32
+ const stale = new Date(Date.now() - 30 * 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 seeded = sessions.map(s => ({
38
+ alias: s.alias, status: s.status,
39
+ network_id: nid, project_dir: null,
40
+ created_at: s.status === 'offline' ? stale : fresh,
41
+ updated_at: s.status === 'offline' ? stale : fresh,
42
+ last_seen_at: s.status === 'offline' ? stale : fresh,
43
+ }));
44
+ await route.fulfill({ response: r, json: { ...b, sessions: seeded } });
45
+ });
46
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
47
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
48
+ const page = await ctx.newPage();
49
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
50
+ await page.waitForFunction(c => document.querySelectorAll('g[data-node]').length === c, sessions.length, { timeout: 30000 });
51
+ await page.waitForTimeout(500);
52
+ const labels = await page.evaluate(() => {
53
+ const out = {};
54
+ for (const t of document.querySelectorAll('text[data-group-label]')) {
55
+ out[t.getAttribute('data-group-label')] = (t.textContent || '').trim();
56
+ }
57
+ return out;
58
+ });
59
+ await ctx.close();
60
+ return labels;
61
+ }
62
+
63
+ // Grouping is consecutive-run on the sorted [...online, ...offline] array,
64
+ // so an offline member of the "alpha" prefix family ends up in a separate
65
+ // band (it falls into the offline tier rather than joining the online
66
+ // alpha group). All members that share a multi-member band are by
67
+ // construction online, so the "offline pip" case is unreachable via the
68
+ // group label and the test covers only the working/idle dimensions here.
69
+ const mixed = await probe([
70
+ { alias: 'alpha1', status: 'working' },
71
+ { alias: 'alpha2', status: 'working' },
72
+ { alias: 'alpha3', status: 'idle' },
73
+ { alias: 'beta', status: 'idle' },
74
+ ]);
75
+
76
+ const allWorking = await probe([
77
+ { alias: 'gamma1', status: 'working' },
78
+ { alias: 'gamma2', status: 'working' },
79
+ { alias: 'gamma3', status: 'working' },
80
+ { alias: 'beta', status: 'idle' },
81
+ ]);
82
+
83
+ await browser.close();
84
+
85
+ const alphaLabel = mixed.alpha || '';
86
+ const gammaLabel = allWorking.gamma || '';
87
+
88
+ const results = {
89
+ alpha_keyPresent: /^alpha/.test(alphaLabel),
90
+ alpha_countCorrect: /·\s*3/.test(alphaLabel),
91
+ alpha_workingPip: /2w/.test(alphaLabel),
92
+ alpha_idlePip: /1i/.test(alphaLabel),
93
+ alpha_noOfflinePip: !/\d+o\b/.test(alphaLabel),
94
+ // Uniform-status group: only the working pip renders.
95
+ gamma_workingPip: /3w/.test(gammaLabel),
96
+ gamma_noIdlePip: !/\d+i\b/.test(gammaLabel),
97
+ gamma_noOfflinePip: !/\d+o\b/.test(gammaLabel),
98
+ };
99
+ const ok = Object.values(results).every(Boolean);
100
+ console.log(`${ok ? '✅' : '❌'} group status pips:`, JSON.stringify(results),
101
+ `\n alpha="${alphaLabel}"`,
102
+ `\n gamma="${gammaLabel}"`);
103
+ process.exit(ok ? 0 : 1);
@@ -86,9 +86,11 @@ const results = {};
86
86
  return allGood;
87
87
  });
88
88
 
89
- // label text present for each group
89
+ // label text present for each group. R19 appended " · N" and R58 appended
90
+ // status-mix pips ("2w 1i") to the same <text> element, so the textContent
91
+ // is now "A站· 4 2w 1i" rather than bare "A站". Match by prefix.
90
92
  const labels = await page.$$eval(`${svg} g[data-group] text`, els => els.map(e => (e.textContent || '').trim()));
91
- results.gridLabels = ['A站', '书生', '通信'].every(g => labels.includes(g));
93
+ results.gridLabels = ['A站', '书生', '通信'].every(g => labels.some(l => l.startsWith(g)));
92
94
 
93
95
  await page.screenshot({ path: '/tmp/anet-issue-111/groupbox-grid-cyber.png' });
94
96
  await ctx.close();
@@ -0,0 +1,67 @@
1
+ /* Round 59 verification: HelpOverlay topology section documents the new
2
+ * R52 (hub click → fit) / R55 (legend hover) / R56 (recent-signal hover)
3
+ * interactions. R41 dbl-click clarification is also expected.
4
+ *
5
+ * Press "?" on the overview page → assert each of the three new entries
6
+ * shows up under the "Topology canvas" group heading.
7
+ */
8
+ import { chromium } from 'playwright';
9
+ import { readFileSync } from 'node:fs';
10
+
11
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
12
+ const browser = await chromium.launch({ headless: true });
13
+
14
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
15
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
16
+ await ctx.addInitScript(() => {
17
+ try {
18
+ localStorage.setItem('anet-theme', 'cyber');
19
+ sessionStorage.setItem('anet_v3_auth', '1');
20
+ } catch {}
21
+ });
22
+ await ctx.route('**/api/hub/status*', async (route) => {
23
+ const r = await route.fetch();
24
+ const b = await r.json();
25
+ await route.fulfill({ response: r, json: b });
26
+ });
27
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
28
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
29
+ const page = await ctx.newPage();
30
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
31
+ await page.waitForTimeout(800);
32
+
33
+ // Open help overlay via `?` (Shift + /).
34
+ await page.keyboard.press('?');
35
+ await page.waitForSelector('[role="dialog"][aria-label="Keyboard shortcuts"]', { timeout: 5000 });
36
+
37
+ const topology = await page.evaluate(() => {
38
+ const dialog = document.querySelector('[role="dialog"][aria-label="Keyboard shortcuts"]');
39
+ if (!dialog) return null;
40
+ // Walk the group divs — each starts with the group title text. Pick the
41
+ // one that says "Topology canvas" and capture its `<li>` items.
42
+ const groups = [...dialog.querySelectorAll('div')];
43
+ let active = null;
44
+ for (const g of groups) {
45
+ if (g.textContent && /^Topology canvas$/i.test(g.textContent.trim())) {
46
+ active = g.parentElement;
47
+ break;
48
+ }
49
+ }
50
+ if (!active) return null;
51
+ return [...active.querySelectorAll('li')].map(li => (li.textContent || '').trim());
52
+ });
53
+
54
+ await browser.close();
55
+ if (!topology) { console.log('❌ topology group not found in help overlay'); process.exit(1); }
56
+
57
+ const has = (s) => topology.some(t => t.includes(s));
58
+ const results = {
59
+ hasHubClickEntry: has('Fit topology to canvas (R52)'),
60
+ hasLegendHoverEntry: has('Highlight nodes of a status (R55)'),
61
+ hasSignalHoverEntry: has('Highlight one flow edge (R56)'),
62
+ hasDblClickClarified: has('(empty canvas only)'),
63
+ };
64
+ const ok = Object.values(results).every(Boolean);
65
+ console.log(`${ok ? '✅' : '❌'} help overlay R59:`, JSON.stringify(results),
66
+ `\n topology items=`, topology);
67
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,64 @@
1
+ /* Round 65 verification: HelpOverlay topology section catches up on
2
+ * R63 (group label pin) + R64 (active-filter pill ×).
3
+ *
4
+ * Press "?" → assert both new entries render under "Topology canvas".
5
+ * Also verify the R59/R62 prior entries still exist (no regression
6
+ * in the section).
7
+ */
8
+ import { chromium } from 'playwright';
9
+ import { readFileSync } from 'node:fs';
10
+
11
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
12
+ const browser = await chromium.launch({ headless: true });
13
+
14
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
15
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
16
+ await ctx.addInitScript(() => {
17
+ try {
18
+ localStorage.setItem('anet-theme', 'cyber');
19
+ sessionStorage.setItem('anet_v3_auth', '1');
20
+ } catch {}
21
+ });
22
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
23
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
24
+ const page = await ctx.newPage();
25
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
26
+ await page.waitForTimeout(800);
27
+
28
+ // Open help overlay via `?`.
29
+ await page.keyboard.press('?');
30
+ await page.waitForSelector('[role="dialog"][aria-label="Keyboard shortcuts"]', { timeout: 5000 });
31
+
32
+ const items = await page.evaluate(() => {
33
+ const dialog = document.querySelector('[role="dialog"][aria-label="Keyboard shortcuts"]');
34
+ if (!dialog) return [];
35
+ const groups = [...dialog.querySelectorAll('div')];
36
+ let active = null;
37
+ for (const g of groups) {
38
+ if (g.textContent && /^Topology canvas$/i.test(g.textContent.trim())) {
39
+ active = g.parentElement;
40
+ break;
41
+ }
42
+ }
43
+ if (!active) return [];
44
+ return [...active.querySelectorAll('li')].map(li => (li.textContent || '').trim());
45
+ });
46
+
47
+ await browser.close();
48
+ if (items.length === 0) { console.log('❌ topology section not found'); process.exit(1); }
49
+
50
+ const has = (s) => items.some(t => t.includes(s));
51
+ const results = {
52
+ r63_groupPin: has('Pin a team focus (R63)'),
53
+ r64_clearOne: has('Clear one filter (R64) — Esc clears all'),
54
+ // R59 priors still present (no regression in section)
55
+ r52_clickHub: has('Fit topology to canvas (R52)'),
56
+ r55_legendHover: has('Highlight nodes of a status (R55)'),
57
+ r56_signalHover: has('Highlight one flow edge (R56)'),
58
+ r60_pinChip: has('Pin a status filter (R60/R61)'),
59
+ r62_esc: has('Clear pinned filter (R62)'),
60
+ };
61
+ const ok = Object.values(results).every(Boolean);
62
+ console.log(`${ok ? '✅' : '❌'} help overlay R65:`, JSON.stringify(results),
63
+ `\n items=`, items);
64
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,63 @@
1
+ /* Round 70 verification: HelpOverlay topology section surfaces the R69
2
+ * Cmd+K palette pin actions. The 1 new entry sits alongside R65's R63
3
+ * + R64 entries; this test re-verifies the full chain so a future edit
4
+ * accidentally dropping any rung gets caught here.
5
+ */
6
+ import { chromium } from 'playwright';
7
+ import { readFileSync } from 'node:fs';
8
+
9
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
10
+ const browser = await chromium.launch({ headless: true });
11
+
12
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
13
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
14
+ await ctx.addInitScript(() => {
15
+ try {
16
+ localStorage.setItem('anet-theme', 'cyber');
17
+ sessionStorage.setItem('anet_v3_auth', '1');
18
+ } catch {}
19
+ });
20
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
21
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
22
+ const page = await ctx.newPage();
23
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
24
+ await page.waitForTimeout(800);
25
+
26
+ await page.keyboard.press('?');
27
+ await page.waitForSelector('[role="dialog"][aria-label="Keyboard shortcuts"]', { timeout: 5000 });
28
+
29
+ const items = await page.evaluate(() => {
30
+ const dialog = document.querySelector('[role="dialog"][aria-label="Keyboard shortcuts"]');
31
+ if (!dialog) return [];
32
+ const groups = [...dialog.querySelectorAll('div')];
33
+ let active = null;
34
+ for (const g of groups) {
35
+ if (g.textContent && /^Topology canvas$/i.test(g.textContent.trim())) {
36
+ active = g.parentElement;
37
+ break;
38
+ }
39
+ }
40
+ return active ? [...active.querySelectorAll('li')].map(li => (li.textContent || '').trim()) : [];
41
+ });
42
+
43
+ await browser.close();
44
+ if (items.length === 0) { console.log('❌ topology section not found'); process.exit(1); }
45
+
46
+ const has = (s) => items.some(t => t.includes(s));
47
+ const results = {
48
+ r70_cmdkPin: has('Pin / clear filters via palette') && has('(R69)'),
49
+ // R65 priors
50
+ r63_groupPin: has('Pin a team focus (R63)'),
51
+ r64_clearOne: has('Clear one filter (R64)'),
52
+ // R62 priors
53
+ r60_pinChip: has('Pin a status filter (R60/R61)'),
54
+ r62_esc: has('Clear pinned filter (R62)'),
55
+ // R59 priors
56
+ r52_clickHub: has('Fit topology to canvas (R52)'),
57
+ r55_legendHover: has('Highlight nodes of a status (R55)'),
58
+ r56_signalHover: has('Highlight one flow edge (R56)'),
59
+ };
60
+ const ok = Object.values(results).every(Boolean);
61
+ console.log(`${ok ? '✅' : '❌'} help overlay R70:`, JSON.stringify(results),
62
+ `\n items=`, items);
63
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,59 @@
1
+ /* Round 78 verification: HelpOverlay surfaces the R73 (click pill body
2
+ * to clear) + R77 (hover N active links to brighten all flows) gestures.
3
+ * Both shipped as real user gestures but weren't in the discoverability
4
+ * surface.
5
+ */
6
+ import { chromium } from 'playwright';
7
+ import { readFileSync } from 'node:fs';
8
+
9
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
10
+ const browser = await chromium.launch({ headless: true });
11
+
12
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
13
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
14
+ await ctx.addInitScript(() => {
15
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
16
+ });
17
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
18
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
19
+ const page = await ctx.newPage();
20
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
21
+ await page.waitForTimeout(800);
22
+
23
+ await page.keyboard.press('?');
24
+ await page.waitForSelector('[role="dialog"][aria-label="Keyboard shortcuts"]', { timeout: 5000 });
25
+
26
+ const items = await page.evaluate(() => {
27
+ const dialog = document.querySelector('[role="dialog"][aria-label="Keyboard shortcuts"]');
28
+ const groups = [...(dialog?.querySelectorAll('div') || [])];
29
+ let active = null;
30
+ for (const g of groups) {
31
+ if (g.textContent && /^Topology canvas$/i.test(g.textContent.trim())) {
32
+ active = g.parentElement;
33
+ break;
34
+ }
35
+ }
36
+ return active ? [...active.querySelectorAll('li')].map(li => (li.textContent || '').trim()) : [];
37
+ });
38
+
39
+ await browser.close();
40
+
41
+ const has = (s) => items.some(t => t.includes(s));
42
+ const results = {
43
+ r78_pillBodyClick: has('Clear filter — click anywhere on the pill body (R73)'),
44
+ r78_chipHover: has('Brighten all active flows — hover'),
45
+ // R74 and R70 priors should still appear (no regression)
46
+ r74_cmdkLayout: has('Toggle layout / Fit canvas via palette (R74)'),
47
+ r70_cmdkPin: has('(R69)'),
48
+ // R65 priors
49
+ r63_groupPin: has('Pin a team focus (R63)'),
50
+ r64_clearOne: has('Clear one filter (R64)'),
51
+ // R59 priors
52
+ r52_clickHub: has('Fit topology to canvas (R52)'),
53
+ r55_legendHover: has('Highlight nodes of a status (R55)'),
54
+ r56_signalHover: has('Highlight one flow edge (R56)'),
55
+ };
56
+ const ok = Object.values(results).every(Boolean);
57
+ console.log(`${ok ? '✅' : '❌'} help overlay R78:`, JSON.stringify(results),
58
+ `\n items=`, items);
59
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,58 @@
1
+ /* Round 81 verification: HelpOverlay surfaces R79 (status chip hover) +
2
+ * R80 (vendor chip letter hover). Both gestures landed without entries
3
+ * in the discoverability surface. Continues the R59/R62/R65/R70/R78
4
+ * docs-catch-up cadence.
5
+ */
6
+ import { chromium } from 'playwright';
7
+ import { readFileSync } from 'node:fs';
8
+
9
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
10
+ const browser = await chromium.launch({ headless: true });
11
+
12
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
13
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
14
+ await ctx.addInitScript(() => {
15
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
16
+ });
17
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
18
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
19
+ const page = await ctx.newPage();
20
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
21
+ await page.waitForTimeout(800);
22
+
23
+ await page.keyboard.press('?');
24
+ await page.waitForSelector('[role="dialog"][aria-label="Keyboard shortcuts"]', { timeout: 5000 });
25
+
26
+ const items = await page.evaluate(() => {
27
+ const dialog = document.querySelector('[role="dialog"][aria-label="Keyboard shortcuts"]');
28
+ const groups = [...(dialog?.querySelectorAll('div') || [])];
29
+ let active = null;
30
+ for (const g of groups) {
31
+ if (g.textContent && /^Topology canvas$/i.test(g.textContent.trim())) {
32
+ active = g.parentElement;
33
+ break;
34
+ }
35
+ }
36
+ return active ? [...active.querySelectorAll('li')].map(li => (li.textContent || '').trim()) : [];
37
+ });
38
+
39
+ await browser.close();
40
+
41
+ const has = (s) => items.some(t => t.includes(s));
42
+ const results = {
43
+ r81_workingChip: has('Highlight working nodes — chip-row sibling of legend (R79)'),
44
+ r81_vendorChip: has('Highlight one vendor — hover A/O/书/? in the chip row (R80)'),
45
+ // R78 priors
46
+ r73_pillBodyClick: has('Clear filter — click anywhere on the pill body (R73)'),
47
+ r77_activeLinks: has('Brighten all active flows — hover'),
48
+ // R74 / R70 / R65 / R59 priors
49
+ r74_cmdkLayout: has('Toggle layout / Fit canvas via palette (R74)'),
50
+ r70_cmdkPin: has('(R69)'),
51
+ r63_groupPin: has('Pin a team focus (R63)'),
52
+ r60_pinChip: has('Pin a status filter (R60/R61)'),
53
+ r52_clickHub: has('Fit topology to canvas (R52)'),
54
+ };
55
+ const ok = Object.values(results).every(Boolean);
56
+ console.log(`${ok ? '✅' : '❌'} help overlay R81:`, JSON.stringify(results),
57
+ `\n items=`, items);
58
+ process.exit(ok ? 0 : 1);