@sleep2agi/agent-network-dashboard 0.4.6-preview.13 → 0.4.6-preview.131

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 (335) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +41 -41
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/routes-manifest.json +6 -0
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +2 -2
  17. package/.next/server/app/_not-found.rsc +12 -12
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  19. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  20. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  24. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  25. package/.next/server/app/admin.html +2 -2
  26. package/.next/server/app/admin.rsc +14 -14
  27. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  28. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  29. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  30. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  31. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  32. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  33. package/.next/server/app/api/hub/servers/route/app-paths-manifest.json +3 -0
  34. package/.next/server/app/api/hub/servers/route/build-manifest.json +9 -0
  35. package/.next/server/app/api/hub/servers/route/server-reference-manifest.json +4 -0
  36. package/.next/server/app/api/hub/servers/route.js +7 -0
  37. package/.next/server/app/api/hub/servers/route.js.map +5 -0
  38. package/.next/server/app/api/hub/servers/route.js.nft.json +1 -0
  39. package/.next/server/app/api/hub/servers/route_client-reference-manifest.js +3 -0
  40. package/.next/server/app/index.html +2 -2
  41. package/.next/server/app/index.rsc +14 -14
  42. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  43. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  44. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  45. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  46. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  47. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/login.html +2 -2
  49. package/.next/server/app/login.rsc +14 -14
  50. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  55. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  56. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/logs.html +2 -2
  58. package/.next/server/app/logs.rsc +14 -14
  59. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  60. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  61. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  62. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  64. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  65. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app/messages.html +2 -2
  67. package/.next/server/app/messages.rsc +14 -14
  68. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  69. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  70. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  71. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  73. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  74. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/node.html +2 -2
  76. package/.next/server/app/node.rsc +14 -14
  77. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  78. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  79. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  80. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  82. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  83. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/nodes.html +2 -2
  85. package/.next/server/app/nodes.rsc +14 -14
  86. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  87. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  88. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  89. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  90. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  91. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  92. package/.next/server/app/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/server-logs.html +2 -2
  95. package/.next/server/app/server-logs.rsc +14 -14
  96. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  97. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  98. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  99. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  100. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  101. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  102. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  103. package/.next/server/app/settings/networks.html +2 -2
  104. package/.next/server/app/settings/networks.rsc +14 -14
  105. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  106. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  107. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  108. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  109. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  110. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  111. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  112. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  113. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  114. package/.next/server/app/settings/tokens.html +2 -2
  115. package/.next/server/app/settings/tokens.rsc +14 -14
  116. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  117. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  118. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  119. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  120. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  121. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  122. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  123. package/.next/server/app/settings.html +2 -2
  124. package/.next/server/app/settings.rsc +14 -14
  125. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  126. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  127. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  128. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  130. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  131. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  132. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  133. package/.next/server/app/tasks.html +2 -2
  134. package/.next/server/app/tasks.rsc +14 -14
  135. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  136. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  137. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  138. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  139. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  140. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  141. package/.next/server/app-paths-manifest.json +1 -0
  142. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js +3 -0
  143. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js.map +1 -0
  144. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +3 -0
  145. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -0
  146. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
  147. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
  148. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  149. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  150. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
  151. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
  152. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -1
  153. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  154. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  155. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  156. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +1 -1
  157. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -1
  158. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  159. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  160. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
  161. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
  162. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
  163. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
  164. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
  165. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
  166. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
  167. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
  168. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
  169. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
  170. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
  171. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
  172. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  173. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  174. package/.next/server/middleware-build-manifest.js +3 -3
  175. package/.next/server/pages/404.html +2 -2
  176. package/.next/server/pages/500.html +1 -1
  177. package/.next/server/server-reference-manifest.js +1 -1
  178. package/.next/server/server-reference-manifest.json +1 -1
  179. package/.next/static/chunks/0-mpa_947ipeq.js +1 -0
  180. package/.next/static/chunks/01lmbsd37fybu.js +1 -0
  181. package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
  182. package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
  183. package/.next/static/chunks/05uk96gc~9mni.js +1 -0
  184. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  185. package/.next/static/chunks/09pxnkf-1.1fj.js +1 -0
  186. package/.next/static/chunks/0eo167c-wlmfz.js +4 -0
  187. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  188. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  189. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  190. package/.next/static/chunks/0yv1jpx~5v038.js +7 -0
  191. package/.next/static/chunks/10tq~cwj-obp4.js +1 -0
  192. package/.next/static/chunks/1450fa_~v9qpc.css +2 -0
  193. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  194. package/.next/trace +2 -2
  195. package/.next/trace-build +1 -1
  196. package/.next/types/routes.d.ts +2 -1
  197. package/.next/types/validator.ts +9 -0
  198. package/app/api/hub/servers/route.ts +39 -0
  199. package/app/components/ChatPopover.tsx +38 -2
  200. package/app/components/CommandPalette.tsx +107 -0
  201. package/app/components/HelpOverlay.tsx +87 -0
  202. package/app/components/ServersDrawer.tsx +64 -27
  203. package/app/components/TopoGraph.tsx +2419 -163
  204. package/app/components/utils.ts +8 -7
  205. package/app/globals.css +19 -1
  206. package/app/lib/time.ts +36 -0
  207. package/package.json +1 -1
  208. package/scripts/help-overlay-topo-test.mjs +44 -0
  209. package/scripts/servers-drawer-live-test.mjs +96 -0
  210. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  211. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  212. package/scripts/topo-active-links-chip-test.mjs +73 -0
  213. package/scripts/topo-active-links-tooltip-test.mjs +79 -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-chat-target-breath-test.mjs +84 -0
  219. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  220. package/scripts/topo-click-ripple-test.mjs +66 -0
  221. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  222. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  223. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  224. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  225. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  226. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  227. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  228. package/scripts/topo-dispatch-pulse-test.mjs +83 -0
  229. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  230. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  231. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  232. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  233. package/scripts/topo-edge-focus-test.mjs +113 -0
  234. package/scripts/topo-edge-hover-test.mjs +93 -0
  235. package/scripts/topo-edge-pill-test.mjs +105 -0
  236. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  237. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  238. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  239. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  240. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  241. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  242. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  243. package/scripts/topo-fit-key-test.mjs +106 -0
  244. package/scripts/topo-freshness-chip-test.mjs +74 -0
  245. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  246. package/scripts/topo-group-count-test.mjs +68 -0
  247. package/scripts/topo-group-hover-test.mjs +82 -0
  248. package/scripts/topo-group-label-hover-test.mjs +119 -0
  249. package/scripts/topo-group-label-tint-test.mjs +99 -0
  250. package/scripts/topo-group-label-title-test.mjs +83 -0
  251. package/scripts/topo-group-pin-test.mjs +119 -0
  252. package/scripts/topo-group-status-pips-test.mjs +103 -0
  253. package/scripts/topo-groupbox-live-test.mjs +100 -0
  254. package/scripts/topo-groupbox-test.mjs +4 -2
  255. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  256. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  257. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  258. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  259. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  260. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  261. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  262. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  263. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  264. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  265. package/scripts/topo-hub-busyness-test.mjs +100 -0
  266. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  267. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  268. package/scripts/topo-hub-title-test.mjs +93 -0
  269. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  270. package/scripts/topo-label-lift-test.mjs +119 -0
  271. package/scripts/topo-label-scale-test.mjs +78 -0
  272. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  273. package/scripts/topo-layout-key-test.mjs +91 -0
  274. package/scripts/topo-legend-count-test.mjs +88 -0
  275. package/scripts/topo-legend-hover-test.mjs +113 -0
  276. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  277. package/scripts/topo-legend-pin-test.mjs +128 -0
  278. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  279. package/scripts/topo-minimap-test.mjs +68 -0
  280. package/scripts/topo-mobile-chips-test.mjs +103 -0
  281. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  282. package/scripts/topo-node-title-test.mjs +101 -0
  283. package/scripts/topo-offline-dim-test.mjs +70 -0
  284. package/scripts/topo-overlap-test.mjs +32 -5
  285. package/scripts/topo-pan-cursor-test.mjs +62 -0
  286. package/scripts/topo-panel-elevation-test.mjs +76 -0
  287. package/scripts/topo-particle-stagger-test.mjs +82 -0
  288. package/scripts/topo-pin-intersection-test.mjs +121 -0
  289. package/scripts/topo-pin-persistence-test.mjs +105 -0
  290. package/scripts/topo-popover-meta-test.mjs +78 -0
  291. package/scripts/topo-pressure-bar-test.mjs +69 -0
  292. package/scripts/topo-pressure-hover-test.mjs +123 -0
  293. package/scripts/topo-pressure-pin-test.mjs +119 -0
  294. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  295. package/scripts/topo-pulse-rate-test.mjs +71 -0
  296. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  297. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  298. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  299. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  300. package/scripts/topo-recent-row-ts-test.mjs +92 -0
  301. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  302. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  303. package/scripts/topo-reduced-motion-test.mjs +77 -0
  304. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  305. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  306. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  307. package/scripts/topo-tier-ring-test.mjs +69 -0
  308. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  309. package/scripts/topo-tier-stagger-test.mjs +89 -0
  310. package/scripts/topo-tz-ghost-test.mjs +69 -0
  311. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  312. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  313. package/scripts/topo-vendor-pill-test.mjs +125 -0
  314. package/scripts/topo-vendor-pin-test.mjs +125 -0
  315. package/scripts/topo-wheel-zoom-test.mjs +79 -0
  316. package/scripts/topo-working-halo-breath-test.mjs +86 -0
  317. package/scripts/topo-zoom-keys-test.mjs +89 -0
  318. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  319. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  320. package/.next/static/chunks/03u_oa2o2jrr~.js +0 -1
  321. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  322. package/.next/static/chunks/0dttftqqehpdj.js +0 -1
  323. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  324. package/.next/static/chunks/0fqy_0wj_1xa7.js +0 -7
  325. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  326. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  327. package/.next/static/chunks/0q_z8ad2uojv5.js +0 -1
  328. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  329. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  330. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  331. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  332. package/.next/static/chunks/17h~0~zsxjx6x.css +0 -2
  333. /package/.next/static/{nhazRrlYzcsAAYMwx8NFz → A3ssjbOXf7a3BaCWBznPU}/_buildManifest.js +0 -0
  334. /package/.next/static/{nhazRrlYzcsAAYMwx8NFz → A3ssjbOXf7a3BaCWBznPU}/_clientMiddlewareManifest.js +0 -0
  335. /package/.next/static/{nhazRrlYzcsAAYMwx8NFz → A3ssjbOXf7a3BaCWBznPU}/_ssgManifest.js +0 -0
@@ -0,0 +1,93 @@
1
+ /* Round 40 verification: hovering a node brightens edges touching it
2
+ * and dims the rest. Three edges in the fleet — hover one node and
3
+ * compare base-path opacity of touching vs non-touching edges. */
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
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
11
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
12
+ await ctx.addInitScript(() => {
13
+ try {
14
+ localStorage.setItem('anet-theme', 'cyber');
15
+ localStorage.setItem('anet-topo-layout', 'grid');
16
+ sessionStorage.setItem('anet_v3_auth', '1');
17
+ } catch {}
18
+ });
19
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
20
+ await ctx.route('**/api/hub/status*', async (route) => {
21
+ const r = await route.fetch();
22
+ const b = await r.json();
23
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
24
+ const sessions = ['alpha', 'beta', 'gamma', 'delta'].map(a => ({
25
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
26
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
27
+ }));
28
+ await route.fulfill({ response: r, json: { ...b, sessions } });
29
+ });
30
+ // Edges:
31
+ // alpha → beta (touches alpha + beta)
32
+ // alpha → gamma (touches alpha + gamma)
33
+ // delta → gamma (touches delta + gamma — does NOT touch alpha)
34
+ const now = new Date().toISOString();
35
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
36
+ { from_alias: 'alpha', to_alias: 'beta', content: 'm', created_at: now },
37
+ { from_alias: 'alpha', to_alias: 'gamma', content: 'm', created_at: now },
38
+ { from_alias: 'delta', to_alias: 'gamma', content: 'm', created_at: now },
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(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
44
+ await page.waitForTimeout(600);
45
+
46
+ const edgeOpacities = () => page.evaluate(() => {
47
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
48
+ // R48 reordered the children of each flow-link <g>:
49
+ // path[data-edge-hitbox] <-- transparent hover surface, carries <title>
50
+ // path[marker-end] <-- the visible base edge with opacity
51
+ // path[id^=flow-path-] <-- the dashed runway
52
+ // Read the route from the hitbox's <title>, but the opacity from the
53
+ // visible base path. Picking by marker-end keeps the selector resilient
54
+ // to future siblings.
55
+ return [...svg.querySelectorAll(':scope > g > g')]
56
+ .map(g => {
57
+ const hitbox = g.querySelector('path[data-edge-hitbox]');
58
+ const title = hitbox?.querySelector('title');
59
+ const base = [...g.querySelectorAll(':scope > path')].find(
60
+ p => !p.hasAttribute('data-edge-hitbox') && p.hasAttribute('marker-end')
61
+ );
62
+ if (!title || !base) return null;
63
+ return { route: title.textContent.split('\n')[0], opacity: +base.getAttribute('opacity') };
64
+ })
65
+ .filter(Boolean);
66
+ });
67
+
68
+ const before = await edgeOpacities();
69
+ // Hover alpha → its two outgoing edges should brighten, delta→gamma should dim.
70
+ await page.locator('g[data-node="alpha"]').hover();
71
+ await page.waitForTimeout(350);
72
+ const after = await edgeOpacities();
73
+
74
+ await browser.close();
75
+ const get = (rows, r) => rows.find(e => e.route === r)?.opacity ?? null;
76
+ const b_ab = get(before, 'alpha → beta');
77
+ const b_ag = get(before, 'alpha → gamma');
78
+ const b_dg = get(before, 'delta → gamma');
79
+ const a_ab = get(after, 'alpha → beta');
80
+ const a_ag = get(after, 'alpha → gamma');
81
+ const a_dg = get(after, 'delta → gamma');
82
+
83
+ const results = {
84
+ baselineMatches: b_ab !== null && b_ag !== null && b_dg !== null && Math.abs(b_ab - b_dg) < 0.001,
85
+ hoverBrightensAlphaBeta: a_ab !== null && a_ab > b_ab + 0.05,
86
+ hoverBrightensAlphaGamma: a_ag !== null && a_ag > b_ag + 0.05,
87
+ hoverDimsDeltaGamma: a_dg !== null && a_dg < b_dg - 0.05,
88
+ };
89
+ const ok = Object.values(results).every(Boolean);
90
+ console.log(`${ok ? '✅' : '❌'} edge hover:`, JSON.stringify(results),
91
+ `\n before α→β=${b_ab} α→γ=${b_ag} δ→γ=${b_dg}`,
92
+ `\n after α→β=${a_ab} α→γ=${a_ag} δ→γ=${a_dg}`);
93
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,105 @@
1
+ /* Round 119 verification: when a recent-signal row is pinned (R116),
2
+ * the chip row renders a 4th "filter: from→to · N" pill matching
3
+ * the R64 / R89 pattern for status / group / vendor pins. Closes the
4
+ * pill-pattern gap left after R116.
5
+ *
6
+ * Verifies:
7
+ * - Before pin: no edge pill
8
+ * - Click row → pill shows correct from→to and count
9
+ * - Click pill body → clears
10
+ * - × button also clears
11
+ * - data-filter-match-aliases lists the two endpoints
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: 1400, 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 { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
23
+ });
24
+
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ const r = await route.fetch();
28
+ const b = await r.json();
29
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
30
+ const mk = (alias) => ({
31
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
32
+ network_id: nid, project_dir: null,
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
34
+ });
35
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta'), mk('gamma')] } });
36
+ });
37
+
38
+ const now = Date.now();
39
+ const mkMsg = (id, from_alias, to_alias, ageMs) => ({
40
+ id, from_alias, to_alias, content: 'hi', network_id: 'default',
41
+ created_at: new Date(now - ageMs).toISOString(),
42
+ });
43
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
44
+ mkMsg('m1', 'alpha', 'beta', 30 * 1000),
45
+ mkMsg('m2', 'alpha', 'beta', 40 * 1000),
46
+ mkMsg('m3', 'alpha', 'beta', 50 * 1000),
47
+ ] } }));
48
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
52
+ await page.waitForSelector('[data-recent-row]', { timeout: 10000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ const readPill = () => page.evaluate(() => {
56
+ const p = document.querySelector('[data-active-filter="edge"]');
57
+ if (!p) return null;
58
+ return {
59
+ text: (p.textContent || '').trim(),
60
+ count: p.getAttribute('data-filter-match-count'),
61
+ aliases: p.getAttribute('data-filter-match-aliases') || '',
62
+ title: p.getAttribute('title') || '',
63
+ };
64
+ });
65
+
66
+ const before = await readPill();
67
+
68
+ // Click row to pin.
69
+ await page.locator('[data-recent-row="alpha->beta"]').click();
70
+ await page.mouse.move(10, 10);
71
+ await page.waitForTimeout(300);
72
+ const afterPin = await readPill();
73
+
74
+ // Click pill body to clear.
75
+ await page.locator('[data-active-filter="edge"]').click();
76
+ await page.mouse.move(10, 10);
77
+ await page.waitForTimeout(300);
78
+ const afterBodyClear = await readPill();
79
+
80
+ // Pin again, then click × button.
81
+ await page.locator('[data-recent-row="alpha->beta"]').click();
82
+ await page.mouse.move(10, 10);
83
+ await page.waitForTimeout(300);
84
+ await page.locator('[data-active-filter="edge"] button').click();
85
+ await page.waitForTimeout(300);
86
+ const afterXClear = await readPill();
87
+
88
+ await browser.close();
89
+
90
+ const results = {
91
+ before_noPill: before === null,
92
+ pin_pillRendered: afterPin !== null,
93
+ pin_textContainsArrow: /alpha→beta/.test(afterPin?.text || ''),
94
+ pin_count3: afterPin?.count === '3',
95
+ pin_aliasesMatch: afterPin?.aliases === 'alpha,beta',
96
+ pin_titleHasFromTo: /alpha → beta \(3 msgs\)/.test(afterPin?.title || ''),
97
+ bodyClear_pillGone: afterBodyClear === null,
98
+ xClear_pillGone: afterXClear === null,
99
+ };
100
+ const ok = Object.values(results).every(Boolean);
101
+ console.log(`${ok ? '✅' : '❌'} edge pill:`, JSON.stringify(results),
102
+ `\n afterPin=`, afterPin,
103
+ `\n afterBodyClear=`, afterBodyClear,
104
+ `\n afterXClear=`, afterXClear);
105
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,70 @@
1
+ /* Round 39 verification: each flow-link path carries a `<title>` with
2
+ * "from → to\nN messages · last <relative>". Tests:
3
+ * - tooltip text shape (route + count + relative time)
4
+ * - singular vs. plural ("1 message" vs "5 messages")
5
+ * - missing last_at falls through cleanly */
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
+ localStorage.setItem('anet-topo-layout', 'grid');
18
+ sessionStorage.setItem('anet_v3_auth', '1');
19
+ } catch {}
20
+ });
21
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
22
+ await ctx.route('**/api/hub/status*', async (route) => {
23
+ const r = await route.fetch();
24
+ const b = await r.json();
25
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
26
+ const sessions = ['alpha', 'beta', 'gamma'].map(a => ({
27
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
28
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
29
+ }));
30
+ await route.fulfill({ response: r, json: { ...b, sessions } });
31
+ });
32
+ // Inject messages: 1 between alpha→beta (singular), 5 between alpha→gamma (plural).
33
+ const old = new Date(Date.now() - 8 * 60 * 1000).toISOString();
34
+ const recent = new Date(Date.now() - 30 * 1000).toISOString();
35
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
36
+ { from_alias: 'alpha', to_alias: 'beta', content: 'hello', created_at: old },
37
+ { from_alias: 'alpha', to_alias: 'gamma', content: 'm1', created_at: recent },
38
+ { from_alias: 'alpha', to_alias: 'gamma', content: 'm2', created_at: recent },
39
+ { from_alias: 'alpha', to_alias: 'gamma', content: 'm3', created_at: recent },
40
+ { from_alias: 'alpha', to_alias: 'gamma', content: 'm4', created_at: recent },
41
+ { from_alias: 'alpha', to_alias: 'gamma', content: 'm5', created_at: recent },
42
+ ] } }));
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 === 3, { timeout: 30000 });
47
+ await page.waitForTimeout(600);
48
+
49
+ // Collect every flow-link tooltip (each base path with a child <title>).
50
+ const tooltips = await page.evaluate(() => {
51
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
52
+ // Two paths per flow link — the base one carries the <title>.
53
+ const titled = [...svg.querySelectorAll('path > title')];
54
+ return titled.map(t => t.textContent);
55
+ });
56
+
57
+ await browser.close();
58
+ const has = (route, count, plural) => tooltips.some(t =>
59
+ t.includes(route) &&
60
+ (plural ? t.includes(`${count} messages`) : t.includes(`${count} message`)) &&
61
+ /last \w+ ago/.test(t)
62
+ );
63
+ const results = {
64
+ singularAlphaToBeta: has('alpha → beta', 1, false),
65
+ pluralAlphaToGamma: has('alpha → gamma', 5, true),
66
+ tooltipCount: tooltips.length === 2,
67
+ };
68
+ const ok = Object.values(results).every(Boolean);
69
+ console.log(`${ok ? '✅' : '❌'} edge tooltip:`, JSON.stringify(results), '\n tooltips=', tooltips);
70
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,93 @@
1
+ /* Round 111 verification: when an edge is hovered, BOTH endpoint
2
+ * nodes get an accent stroke ring (data-edge-endpoint-ring) while
3
+ * non-endpoint nodes don't. R49 already kept endpoints at opacity 1
4
+ * but offered no positive cue; the ring closes that.
5
+ *
6
+ * Fleet: alpha → beta with 4 messages (sole flow), gamma idle (no
7
+ * flows). Hover the edge → alpha + beta each carry a ring; gamma
8
+ * carries none. Mouseleave → all rings vanish.
9
+ */
10
+ import { chromium } from 'playwright';
11
+ import { readFileSync } from 'node:fs';
12
+
13
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
14
+ const browser = await chromium.launch({ headless: true });
15
+
16
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
17
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
18
+ await ctx.addInitScript(() => {
19
+ try {
20
+ localStorage.setItem('anet-theme', 'cyber');
21
+ localStorage.setItem('anet-topo-layout', 'ring');
22
+ sessionStorage.setItem('anet_v3_auth', '1');
23
+ } catch {}
24
+ });
25
+
26
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
27
+ await ctx.route('**/api/hub/status*', async (route) => {
28
+ const r = await route.fetch();
29
+ const b = await r.json();
30
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
31
+ const mk = (alias) => ({
32
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
33
+ network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ });
36
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta'), mk('gamma')] } });
37
+ });
38
+
39
+ const now = Date.now();
40
+ const mkMsg = (id, from_alias, to_alias, ageMs) => ({
41
+ id, from_alias, to_alias, content: 'hi', network_id: 'default',
42
+ created_at: new Date(now - ageMs).toISOString(),
43
+ });
44
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
45
+ mkMsg('m1', 'alpha', 'beta', 30 * 1000),
46
+ mkMsg('m2', 'alpha', 'beta', 40 * 1000),
47
+ mkMsg('m3', 'alpha', 'beta', 50 * 1000),
48
+ mkMsg('m4', 'alpha', 'beta', 60 * 1000),
49
+ ] } }));
50
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
51
+ const page = await ctx.newPage();
52
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
53
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
54
+ await page.waitForTimeout(400);
55
+
56
+ const ringByAlias = () => page.evaluate(() => {
57
+ const out = {};
58
+ for (const alias of ['alpha', 'beta', 'gamma']) {
59
+ const g = document.querySelector(`g[data-node="${alias}"]`);
60
+ out[alias] = !!g?.querySelector('[data-edge-endpoint-ring]');
61
+ }
62
+ return out;
63
+ });
64
+
65
+ const before = await ringByAlias();
66
+
67
+ // Hover the edge hitbox (the invisible 16-px stroke that owns the
68
+ // hover surface per R48). The recent-signal row provides a stable
69
+ // way to set hoveredEdgeKey without finding the path on the canvas.
70
+ await page.locator('[data-recent-row="alpha->beta"]').hover();
71
+ await page.waitForTimeout(300);
72
+ const onHover = await ringByAlias();
73
+
74
+ // Move away.
75
+ await page.mouse.move(10, 10);
76
+ await page.waitForTimeout(300);
77
+ const afterLeave = await ringByAlias();
78
+
79
+ await browser.close();
80
+
81
+ const results = {
82
+ before_noneHaveRing: !before.alpha && !before.beta && !before.gamma,
83
+ hover_alphaRing: onHover.alpha === true,
84
+ hover_betaRing: onHover.beta === true,
85
+ hover_gammaNoRing: onHover.gamma === false,
86
+ leave_allClear: !afterLeave.alpha && !afterLeave.beta && !afterLeave.gamma,
87
+ };
88
+ const ok = Object.values(results).every(Boolean);
89
+ console.log(`${ok ? '✅' : '❌'} edge-endpoint ring:`, JSON.stringify(results),
90
+ `\n before=`, before,
91
+ `\n onHover=`, onHover,
92
+ `\n afterLeave=`, afterLeave);
93
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,98 @@
1
+ /* Round 62 verification: pressing Esc clears pinnedStatus when no chat
2
+ * is open. Composes cleanly with the R136 ChatPopover Esc handler
3
+ * (which only mounts while chatAlias is set).
4
+ *
5
+ * - Pin via legend row (R61) → assert filter active.
6
+ * - Press Esc → assert pin released, all nodes restore.
7
+ * - aria-pressed flips back to false.
8
+ */
9
+ import { chromium } from 'playwright';
10
+ import { readFileSync } from 'node:fs';
11
+
12
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
13
+ const browser = await chromium.launch({ headless: true });
14
+
15
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
16
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
17
+ await ctx.addInitScript(() => {
18
+ try {
19
+ localStorage.setItem('anet-theme', 'cyber');
20
+ localStorage.setItem('anet-topo-layout', 'ring');
21
+ sessionStorage.setItem('anet_v3_auth', '1');
22
+ } catch {}
23
+ });
24
+
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ const r = await route.fetch();
28
+ const b = await r.json();
29
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
30
+ const sessions = [
31
+ { alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
32
+ { alias: 'idl1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
33
+ { alias: 'idl2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
34
+ ];
35
+ await route.fulfill({ response: r, json: { ...b, sessions } });
36
+ });
37
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
38
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
39
+ const page = await ctx.newPage();
40
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
41
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
42
+ await page.waitForTimeout(500);
43
+
44
+ const opacities = () => page.evaluate(() => {
45
+ const o = {};
46
+ for (const a of ['wkr', 'idl1', 'idl2']) {
47
+ const g = document.querySelector(`g[data-node="${a}"]`);
48
+ o[a] = g ? +(g.style.opacity || '1') : null;
49
+ }
50
+ return o;
51
+ });
52
+ const ariaPressed = (status) => page.evaluate(s => {
53
+ const g = document.querySelector(`g[data-legend-status="${s}"]`);
54
+ return g?.getAttribute('aria-pressed');
55
+ }, status);
56
+
57
+ const before = await opacities();
58
+
59
+ // Pin working via legend row click.
60
+ await page.locator('g[data-legend-status="working"]').first().click({ force: true });
61
+ // Move mouse away so we observe pin-only (not hover-driven) filter.
62
+ await page.mouse.move(10, 10);
63
+ await page.waitForTimeout(250);
64
+ const afterPin = await opacities();
65
+ const ariaAfterPin = await ariaPressed('working');
66
+
67
+ // Press Esc. Body should have focus so the document-level keydown
68
+ // handler fires.
69
+ await page.evaluate(() => document.activeElement && typeof document.activeElement.blur === 'function' && document.activeElement.blur());
70
+ await page.keyboard.press('Escape');
71
+ await page.waitForTimeout(250);
72
+ const afterEsc = await opacities();
73
+ const ariaAfterEsc = await ariaPressed('working');
74
+
75
+ // Press Esc again — should be a no-op (no pin, no chat).
76
+ await page.keyboard.press('Escape');
77
+ await page.waitForTimeout(150);
78
+ const afterEsc2 = await opacities();
79
+
80
+ await browser.close();
81
+
82
+ const bright = (v) => v != null && v >= 0.55;
83
+ const dim = (v) => v != null && v < 0.4;
84
+ const results = {
85
+ before_baseline: bright(before.wkr) && bright(before.idl1) && bright(before.idl2),
86
+ pin_keepsWorking: bright(afterPin.wkr),
87
+ pin_dimsIdle: dim(afterPin.idl1) && dim(afterPin.idl2),
88
+ pin_ariaTrue: ariaAfterPin === 'true',
89
+ esc_releasesPin: bright(afterEsc.wkr) && bright(afterEsc.idl1) && bright(afterEsc.idl2),
90
+ esc_ariaBackToFalse: ariaAfterEsc === 'false',
91
+ escAgain_isNoOp: bright(afterEsc2.wkr) && bright(afterEsc2.idl1),
92
+ };
93
+ const ok = Object.values(results).every(Boolean);
94
+ console.log(`${ok ? '✅' : '❌'} esc clears pin:`, JSON.stringify(results),
95
+ `\n before=`, before,
96
+ `\n afterPin=`, afterPin, ` aria=${ariaAfterPin}`,
97
+ `\n afterEsc=`, afterEsc, ` aria=${ariaAfterEsc}`);
98
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,121 @@
1
+ /* Round 97 verification: each active-filter pill carries the matched
2
+ * alias list in its title attribute + data-filter-match-aliases for
3
+ * tests. Pre-R97 the pill said "filter: working · 3" but you had to
4
+ * eyeball the dimmed canvas to find which 3 nodes were the matches.
5
+ *
6
+ * Drive each pin type and read the alias data attr. Fleet has known
7
+ * mix so the expected alias sets are deterministic.
8
+ *
9
+ * Truncation at 8 aliases is tested via a fleet with > 8 working
10
+ * nodes — confirms the data attr stores the full list (for test
11
+ * stability) while the visible title shows a "+N more" suffix.
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: 1400, 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
+ sessionStorage.removeItem('anet-topo-pinned-status');
27
+ sessionStorage.removeItem('anet-topo-pinned-group');
28
+ sessionStorage.removeItem('anet-topo-pinned-vendor');
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 mk = (alias, status, model) => ({
38
+ alias, status, model, runtime: 'cli-claude-code',
39
+ network_id: nid, project_dir: null,
40
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
41
+ });
42
+ // Fleet: 3 working (alpha1/2 + beta1), 3 idle (alpha3 + beta2 + gamma1),
43
+ // 2 vendors A/O. alpha-prefix = 3 members, beta-prefix = 2, gamma = 1.
44
+ const sessions = [
45
+ mk('alpha1', 'working', 'claude-opus-4'),
46
+ mk('alpha2', 'working', 'claude-opus-4'),
47
+ mk('alpha3', 'idle', 'claude-opus-4'),
48
+ mk('beta1', 'working', 'gpt-5'),
49
+ mk('beta2', 'idle', 'gpt-5'),
50
+ mk('gamma1', 'idle', 'claude-opus-4'),
51
+ ];
52
+ await route.fulfill({ response: r, json: { ...b, sessions } });
53
+ });
54
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
55
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
56
+ const page = await ctx.newPage();
57
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
58
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 6, { timeout: 30000 });
59
+ await page.waitForTimeout(400);
60
+
61
+ const readPill = (kind) => page.evaluate((k) => {
62
+ const p = document.querySelector(`[data-active-filter="${k}"]`);
63
+ if (!p) return null;
64
+ return {
65
+ title: p.getAttribute('title') || '',
66
+ aliases: p.getAttribute('data-filter-match-aliases') || '',
67
+ count: p.getAttribute('data-filter-match-count') || '',
68
+ };
69
+ }, kind);
70
+
71
+ const pin = async (kind, value) => {
72
+ await page.evaluate(({ kind, value }) => {
73
+ if (kind === 'status') sessionStorage.setItem('anet-topo-pinned-status', value);
74
+ if (kind === 'group') sessionStorage.setItem('anet-topo-pinned-group', value);
75
+ if (kind === 'vendor') sessionStorage.setItem('anet-topo-pinned-vendor', value);
76
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind, value } }));
77
+ }, { kind, value });
78
+ await page.waitForTimeout(200);
79
+ };
80
+
81
+ // Status pin → working should match alpha1, alpha2, beta1.
82
+ await pin('status', 'working');
83
+ const statusPill = await readPill('status');
84
+
85
+ // Vendor pin → A should match all claude-opus-4 sessions (alphas + gamma).
86
+ await page.locator('[data-vendor-letter="A"]').click();
87
+ await page.mouse.move(10, 10);
88
+ await page.waitForTimeout(250);
89
+ const vendorPill = await readPill('vendor');
90
+
91
+ // Group pin → alpha should match alpha1/2/3.
92
+ await page.evaluate(() => {
93
+ sessionStorage.setItem('anet-topo-pinned-group', 'alpha');
94
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'group', value: 'alpha' } }));
95
+ });
96
+ await page.waitForTimeout(250);
97
+ const groupPill = await readPill('group');
98
+
99
+ await browser.close();
100
+
101
+ const sameSet = (csv, expected) => {
102
+ const got = new Set(csv.split(',').filter(Boolean));
103
+ const exp = new Set(expected);
104
+ return got.size === exp.size && [...exp].every(x => got.has(x));
105
+ };
106
+
107
+ const results = {
108
+ status_aliasesMatch: sameSet(statusPill?.aliases || '', ['alpha1', 'alpha2', 'beta1']),
109
+ status_titleListsThem: /alpha1.*alpha2.*beta1|alpha2.*alpha1.*beta1/.test(statusPill?.title || ''),
110
+ status_titleHasClear: /click to clear/i.test(statusPill?.title || ''),
111
+ group_aliasesMatch: sameSet(groupPill?.aliases || '', ['alpha1', 'alpha2', 'alpha3']),
112
+ group_titleListsThem: /alpha1.*alpha2.*alpha3/.test(groupPill?.title || ''),
113
+ vendor_aliasesMatch: sameSet(vendorPill?.aliases || '', ['alpha1', 'alpha2', 'alpha3', 'gamma1']),
114
+ vendor_titleListsThem: /alpha1.*alpha2.*alpha3.*gamma1/.test(vendorPill?.title || ''),
115
+ };
116
+ const ok = Object.values(results).every(Boolean);
117
+ console.log(`${ok ? '✅' : '❌'} filter pill aliases:`, JSON.stringify(results),
118
+ `\n status=`, statusPill,
119
+ `\n group=`, groupPill,
120
+ `\n vendor=`, vendorPill);
121
+ process.exit(ok ? 0 : 1);