@sleep2agi/agent-network-dashboard 0.4.6-preview.41 → 0.4.6-preview.415

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 (545) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -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 +16 -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/server/[hostname]/agents/route/app-paths-manifest.json +3 -0
  34. package/.next/server/app/api/hub/server/[hostname]/agents/route/build-manifest.json +9 -0
  35. package/.next/server/app/api/hub/server/[hostname]/agents/route/server-reference-manifest.json +4 -0
  36. package/.next/server/app/api/hub/server/[hostname]/agents/route.js +7 -0
  37. package/.next/server/app/api/hub/server/[hostname]/agents/route.js.map +5 -0
  38. package/.next/server/app/api/hub/server/[hostname]/agents/route.js.nft.json +1 -0
  39. package/.next/server/app/api/hub/server/[hostname]/agents/route_client-reference-manifest.js +3 -0
  40. package/.next/server/app/api/hub/server/[hostname]/health/route/app-paths-manifest.json +3 -0
  41. package/.next/server/app/api/hub/server/[hostname]/health/route/build-manifest.json +9 -0
  42. package/.next/server/app/api/hub/server/[hostname]/health/route/server-reference-manifest.json +4 -0
  43. package/.next/server/app/api/hub/server/[hostname]/health/route.js +7 -0
  44. package/.next/server/app/api/hub/server/[hostname]/health/route.js.map +5 -0
  45. package/.next/server/app/api/hub/server/[hostname]/health/route.js.nft.json +1 -0
  46. package/.next/server/app/api/hub/server/[hostname]/health/route_client-reference-manifest.js +3 -0
  47. package/.next/server/app/index.html +2 -2
  48. package/.next/server/app/index.rsc +14 -14
  49. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  50. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  55. package/.next/server/app/login.html +2 -2
  56. package/.next/server/app/login.rsc +14 -14
  57. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  58. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  59. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  60. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  61. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  62. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  63. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  64. package/.next/server/app/logs.html +2 -2
  65. package/.next/server/app/logs.rsc +14 -14
  66. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  67. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  68. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  69. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  70. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  71. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  72. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  73. package/.next/server/app/messages.html +2 -2
  74. package/.next/server/app/messages.rsc +14 -14
  75. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  76. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  77. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  78. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  79. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  80. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  81. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app/node.html +2 -2
  83. package/.next/server/app/node.rsc +14 -14
  84. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  85. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  86. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  87. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  88. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  89. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  90. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  91. package/.next/server/app/nodes.html +2 -2
  92. package/.next/server/app/nodes.rsc +14 -14
  93. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  94. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  95. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  96. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  97. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  98. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  99. package/.next/server/app/page_client-reference-manifest.js +1 -1
  100. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  101. package/.next/server/app/server-logs.html +2 -2
  102. package/.next/server/app/server-logs.rsc +14 -14
  103. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  104. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  105. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  106. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  107. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  108. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  109. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  110. package/.next/server/app/settings/networks.html +2 -2
  111. package/.next/server/app/settings/networks.rsc +14 -14
  112. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  113. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  114. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  115. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  116. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  117. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  118. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  119. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  120. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  121. package/.next/server/app/settings/tokens.html +2 -2
  122. package/.next/server/app/settings/tokens.rsc +14 -14
  123. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  124. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  125. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  126. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  127. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  128. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  129. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  130. package/.next/server/app/settings.html +2 -2
  131. package/.next/server/app/settings.rsc +14 -14
  132. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  133. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  134. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  135. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  136. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  137. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  138. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  139. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  140. package/.next/server/app/tasks.html +2 -2
  141. package/.next/server/app/tasks.rsc +14 -14
  142. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  143. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  144. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  145. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  146. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  147. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  148. package/.next/server/app-paths-manifest.json +2 -0
  149. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js +3 -0
  150. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js.map +1 -0
  151. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js +3 -0
  152. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js.map +1 -0
  153. package/.next/server/chunks/[root-of-the-server]__05nyivw._.js +3 -0
  154. package/.next/server/chunks/[root-of-the-server]__05nyivw._.js.map +1 -0
  155. package/.next/server/chunks/[root-of-the-server]__0floe3h._.js +3 -0
  156. package/.next/server/chunks/[root-of-the-server]__0floe3h._.js.map +1 -0
  157. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +1 -1
  158. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -1
  159. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
  160. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
  161. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  162. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  163. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
  164. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
  165. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -1
  166. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  167. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  168. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  169. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +1 -1
  170. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -1
  171. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  172. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  173. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
  174. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
  175. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
  176. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
  177. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
  178. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
  179. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
  180. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
  181. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
  182. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
  183. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
  184. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
  185. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  186. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  187. package/.next/server/middleware-build-manifest.js +3 -3
  188. package/.next/server/pages/404.html +2 -2
  189. package/.next/server/pages/500.html +1 -1
  190. package/.next/server/server-reference-manifest.js +1 -1
  191. package/.next/server/server-reference-manifest.json +1 -1
  192. package/.next/static/chunks/0-mpa_947ipeq.js +1 -0
  193. package/.next/static/chunks/01lmbsd37fybu.js +1 -0
  194. package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
  195. package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
  196. package/.next/static/chunks/05uk96gc~9mni.js +1 -0
  197. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  198. package/.next/static/chunks/0_cxs7bhk9fy-.js +1 -0
  199. package/.next/static/chunks/0aauz~36q5n2a.css +2 -0
  200. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  201. package/.next/static/chunks/0l_~q07bhpkcx.js +7 -0
  202. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  203. package/.next/static/chunks/0s7~zeuhn0t35.js +4 -0
  204. package/.next/static/chunks/0vo1q0pvypqub.js +1 -0
  205. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  206. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  207. package/.next/trace +2 -2
  208. package/.next/trace-build +1 -1
  209. package/.next/types/routes.d.ts +3 -1
  210. package/.next/types/validator.ts +18 -0
  211. package/app/api/hub/server/[hostname]/agents/route.ts +62 -0
  212. package/app/api/hub/server/[hostname]/health/route.ts +58 -0
  213. package/app/api/hub/servers/route.ts +88 -1
  214. package/app/components/ChatPopover.tsx +38 -2
  215. package/app/components/CommandPalette.tsx +107 -0
  216. package/app/components/HelpOverlay.tsx +109 -1
  217. package/app/components/ServersDrawer.tsx +181 -5
  218. package/app/components/TopoGraph.tsx +6552 -282
  219. package/app/components/utils.ts +8 -7
  220. package/app/globals.css +110 -5
  221. package/app/lib/time.ts +36 -0
  222. package/app/lib/vendorIdentity.ts +14 -3
  223. package/docs/rfc/v0.10.0-hero3-dashboard-upgrade.md +230 -0
  224. package/package.json +2 -2
  225. package/public/sleep2agi-logo.svg +11 -0
  226. package/public/vendors/claude.svg +11 -0
  227. package/public/vendors/minimax.svg +11 -0
  228. package/public/vendors/openai.svg +12 -0
  229. package/screenshots/r293-p0/after-full.png +0 -0
  230. package/screenshots/r293-p0/after-topo.png +0 -0
  231. package/screenshots/r293-p0/baseline-full.png +0 -0
  232. package/screenshots/r293-p0/baseline-topo.png +0 -0
  233. package/screenshots/r293-p0/logo-integrated-full.png +0 -0
  234. package/screenshots/r293-p0/logo-integrated-topo.png +0 -0
  235. package/screenshots/r293-p0/servers-drawer-v0.10.0-full.png +0 -0
  236. package/screenshots/r293-p0/servers-drawer-v0.10.0.png +0 -0
  237. package/screenshots/r293-p0/wave1-after-full.png +0 -0
  238. package/screenshots/r293-p0/wave1-after-topo.png +0 -0
  239. package/scripts/p0-servers-drawer-screenshot.mjs +99 -0
  240. package/scripts/p0-topo-screenshot.mjs +82 -0
  241. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  242. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  243. package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
  244. package/scripts/topo-active-links-chip-self-hover-test.mjs +171 -0
  245. package/scripts/topo-active-links-chip-test.mjs +73 -0
  246. package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
  247. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  248. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  249. package/scripts/topo-active-pulse-ease-test.mjs +120 -0
  250. package/scripts/topo-brand-logo-transition-test.mjs +94 -0
  251. package/scripts/topo-brand-watermark-test.mjs +97 -0
  252. package/scripts/topo-canvas-aria-test.mjs +108 -0
  253. package/scripts/topo-canvas-brand-fade-test.mjs +87 -0
  254. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  255. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  256. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  257. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  258. package/scripts/topo-chip-focus-test.mjs +95 -0
  259. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  260. package/scripts/topo-chip-row-gap-test.mjs +90 -0
  261. package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
  262. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  263. package/scripts/topo-chip-unit-demotion-test.mjs +93 -0
  264. package/scripts/topo-chiprow-data-weight-test.mjs +94 -0
  265. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  266. package/scripts/topo-chrome-active-weight-test.mjs +104 -0
  267. package/scripts/topo-chrome-focus-ring-unify-test.mjs +100 -0
  268. package/scripts/topo-chrome-gap-test.mjs +73 -0
  269. package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
  270. package/scripts/topo-chrome-inactive-color-test.mjs +90 -0
  271. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  272. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  273. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  274. package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
  275. package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
  276. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  277. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  278. package/scripts/topo-chrome-zoom-readout-weight-test.mjs +81 -0
  279. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  280. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  281. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  282. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  283. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  284. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  285. package/scripts/topo-corner-radius-unify-test.mjs +103 -0
  286. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  287. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  288. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  289. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  290. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  291. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  292. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  293. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  294. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  295. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  296. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  297. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  298. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  299. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  300. package/scripts/topo-edge-fadein-test.mjs +106 -0
  301. package/scripts/topo-edge-focus-test.mjs +113 -0
  302. package/scripts/topo-edge-hover-test.mjs +93 -0
  303. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  304. package/scripts/topo-edge-pill-test.mjs +105 -0
  305. package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
  306. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  307. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  308. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  309. package/scripts/topo-empty-hint-tracking-test.mjs +84 -0
  310. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  311. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  312. package/scripts/topo-empty-state-recenter-test.mjs +107 -0
  313. package/scripts/topo-empty-subhint-tracking-test.mjs +87 -0
  314. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  315. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  316. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  317. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  318. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  319. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  320. package/scripts/topo-filter-pill-count-tabular-test.mjs +135 -0
  321. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  322. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  323. package/scripts/topo-freshness-chip-test.mjs +74 -0
  324. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  325. package/scripts/topo-freshness-chip-weight-test.mjs +90 -0
  326. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  327. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  328. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  329. package/scripts/topo-group-count-test.mjs +6 -1
  330. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  331. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  332. package/scripts/topo-group-label-hover-test.mjs +119 -0
  333. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  334. package/scripts/topo-group-label-tint-test.mjs +99 -0
  335. package/scripts/topo-group-label-title-test.mjs +83 -0
  336. package/scripts/topo-group-pin-test.mjs +119 -0
  337. package/scripts/topo-group-pip-fadein-test.mjs +104 -0
  338. package/scripts/topo-group-pip-singletier-drop-test.mjs +131 -0
  339. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  340. package/scripts/topo-group-status-pips-test.mjs +103 -0
  341. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  342. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  343. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  344. package/scripts/topo-groupbox-live-test.mjs +100 -0
  345. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  346. package/scripts/topo-groupbox-test.mjs +4 -2
  347. package/scripts/topo-header-gap-test.mjs +94 -0
  348. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  349. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  350. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  351. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  352. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  353. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  354. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  355. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  356. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  357. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  358. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  359. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  360. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  361. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  362. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  363. package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
  364. package/scripts/topo-hub-a11y-test.mjs +112 -0
  365. package/scripts/topo-hub-busyness-test.mjs +100 -0
  366. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  367. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  368. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  369. package/scripts/topo-hub-fadein-test.mjs +88 -0
  370. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  371. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  372. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  373. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  374. package/scripts/topo-hub-title-test.mjs +93 -0
  375. package/scripts/topo-hub-working-count-test.mjs +99 -0
  376. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  377. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  378. package/scripts/topo-kicker-color-test.mjs +93 -0
  379. package/scripts/topo-kicker-tracking-test.mjs +101 -0
  380. package/scripts/topo-kicker-weight-test.mjs +98 -0
  381. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  382. package/scripts/topo-label-lift-test.mjs +5 -1
  383. package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
  384. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  385. package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
  386. package/scripts/topo-layout-toggle-test.mjs +131 -0
  387. package/scripts/topo-layout-trailer-mr-test.mjs +82 -0
  388. package/scripts/topo-legend-compress-test.mjs +107 -0
  389. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  390. package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
  391. package/scripts/topo-legend-count-tabular-test.mjs +87 -0
  392. package/scripts/topo-legend-count-test.mjs +88 -0
  393. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  394. package/scripts/topo-legend-count-weight-test.mjs +86 -0
  395. package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
  396. package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
  397. package/scripts/topo-legend-hover-test.mjs +113 -0
  398. package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
  399. package/scripts/topo-legend-offline-label-simplify-test.mjs +86 -0
  400. package/scripts/topo-legend-panel-count-weight-test.mjs +89 -0
  401. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  402. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  403. package/scripts/topo-legend-pin-test.mjs +128 -0
  404. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  405. package/scripts/topo-legend-row-labels-simplify-test.mjs +80 -0
  406. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  407. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  408. package/scripts/topo-legend-row-title-test.mjs +102 -0
  409. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  410. package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
  411. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  412. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  413. package/scripts/topo-minimap-radius-test.mjs +88 -0
  414. package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
  415. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  416. package/scripts/topo-mobile-chips-test.mjs +103 -0
  417. package/scripts/topo-monogram-font-test.mjs +112 -0
  418. package/scripts/topo-monogram-stroke-test.mjs +101 -0
  419. package/scripts/topo-more-flows-letterspacing-test.mjs +111 -0
  420. package/scripts/topo-node-a11y-test.mjs +107 -0
  421. package/scripts/topo-node-alias-chat-pin-test.mjs +95 -0
  422. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  423. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  424. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  425. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  426. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  427. package/scripts/topo-node-title-peers-test.mjs +107 -0
  428. package/scripts/topo-node-title-test.mjs +101 -0
  429. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  430. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  431. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  432. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  433. package/scripts/topo-panel-count-unit-test.mjs +112 -0
  434. package/scripts/topo-panel-elevation-test.mjs +76 -0
  435. package/scripts/topo-panel-fadein-test.mjs +102 -0
  436. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  437. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  438. package/scripts/topo-panel-rx-test.mjs +90 -0
  439. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  440. package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
  441. package/scripts/topo-panel-title-tracking-test.mjs +78 -0
  442. package/scripts/topo-particle-stagger-test.mjs +82 -0
  443. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  444. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  445. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  446. package/scripts/topo-pin-intersection-test.mjs +121 -0
  447. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  448. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  449. package/scripts/topo-pin-persistence-test.mjs +105 -0
  450. package/scripts/topo-popover-meta-test.mjs +78 -0
  451. package/scripts/topo-prefix-opacity-test.mjs +107 -0
  452. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  453. package/scripts/topo-pressure-hover-test.mjs +123 -0
  454. package/scripts/topo-pressure-pin-test.mjs +119 -0
  455. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  456. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  457. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  458. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  459. package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
  460. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  461. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  462. package/scripts/topo-recent-panel-count-weight-test.mjs +91 -0
  463. package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
  464. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  465. package/scripts/topo-recent-panel-hot-tabular-test.mjs +113 -0
  466. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  467. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  468. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  469. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  470. package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
  471. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  472. package/scripts/topo-recent-row-count-weight-test.mjs +112 -0
  473. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  474. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  475. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  476. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  477. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  478. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  479. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  480. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  481. package/scripts/topo-recent-row-title-test.mjs +112 -0
  482. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  483. package/scripts/topo-recent-row-ts-tabular-test.mjs +108 -0
  484. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  485. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  486. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  487. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  488. package/scripts/topo-reduced-motion-test.mjs +77 -0
  489. package/scripts/topo-reset-spin-test.mjs +120 -0
  490. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  491. package/scripts/topo-spokes-retired-test.mjs +93 -0
  492. package/scripts/topo-starfield-density-test.mjs +89 -0
  493. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  494. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  495. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  496. package/scripts/topo-svg-focus-test.mjs +112 -0
  497. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  498. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  499. package/scripts/topo-tier-guide-dashes-test.mjs +86 -0
  500. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  501. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  502. package/scripts/topo-tier-ring-test.mjs +69 -0
  503. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  504. package/scripts/topo-tier-stagger-test.mjs +89 -0
  505. package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
  506. package/scripts/topo-title-tracking-test.mjs +98 -0
  507. package/scripts/topo-titleblock-gap-test.mjs +86 -0
  508. package/scripts/topo-titleblock-margin-test.mjs +83 -0
  509. package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
  510. package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
  511. package/scripts/topo-tz-ghost-test.mjs +69 -0
  512. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  513. package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
  514. package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
  515. package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
  516. package/scripts/topo-vendor-chip-weight-test.mjs +87 -0
  517. package/scripts/topo-vendor-count-suffix-test.mjs +95 -0
  518. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  519. package/scripts/topo-vendor-pill-test.mjs +125 -0
  520. package/scripts/topo-vendor-pin-test.mjs +125 -0
  521. package/scripts/topo-view-smooth-test.mjs +131 -0
  522. package/scripts/topo-watermark-tracking-test.mjs +99 -0
  523. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  524. package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
  525. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  526. package/scripts/topo-wrapper-radius-test.mjs +73 -0
  527. package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
  528. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  529. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  530. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  531. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  532. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  533. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  534. package/.next/static/chunks/0mqwn-l7wtcdo.js +0 -7
  535. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  536. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  537. package/.next/static/chunks/0tz5gaflhax.o.js +0 -1
  538. package/.next/static/chunks/10kk9kuoj0ns9.js +0 -1
  539. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  540. package/.next/static/chunks/11l042tnn68ye.js +0 -1
  541. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  542. package/.next/static/chunks/13.zti9ix.1eh.css +0 -2
  543. /package/.next/static/{zYWeA6KAnt4mH1Rredbmu → BR2xyE5iCb0X3_HnGmumr}/_buildManifest.js +0 -0
  544. /package/.next/static/{zYWeA6KAnt4mH1Rredbmu → BR2xyE5iCb0X3_HnGmumr}/_clientMiddlewareManifest.js +0 -0
  545. /package/.next/static/{zYWeA6KAnt4mH1Rredbmu → BR2xyE5iCb0X3_HnGmumr}/_ssgManifest.js +0 -0
@@ -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,112 @@
1
+ /* Round 233 verification: edge endpoint ring (R111/R182) gains a
2
+ * stroke-width thicken (1.6 → 2.4) on edge hover, completing the
3
+ * hover-elevation gesture across the whole edge surface.
4
+ *
5
+ * Test scope:
6
+ * - Rest state (no edge hovered): every endpoint ring has
7
+ * stroke-width=1.6, data-edge-endpoint-active='false', opacity=0
8
+ * - Transition wiring: style.transition includes 'stroke-width
9
+ * 180ms', so when isEndpoint flips the value eases (not snaps)
10
+ * - Hover state (dispatched mouseenter on edge hitbox): the two
11
+ * endpoint nodes' rings flip to stroke-width=2.4, data-edge-
12
+ * endpoint-active='true', and opacity > 0
13
+ *
14
+ * Scenario: 4 working agents + 1 flow alpha→beta count=5 so a
15
+ * single edge is present with two well-defined endpoints.
16
+ */
17
+ import { chromium } from 'playwright';
18
+ import { readFileSync } from 'node:fs';
19
+
20
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
21
+ const browser = await chromium.launch({ headless: true });
22
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
23
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
24
+ await ctx.addInitScript(() => {
25
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
26
+ });
27
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
28
+ await ctx.route('**/api/hub/status*', async (route) => {
29
+ const r = await route.fetch();
30
+ const b = await r.json();
31
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
32
+ const mk = (alias) => ({
33
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
34
+ network_id: nid, project_dir: null,
35
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
36
+ });
37
+ await route.fulfill({ response: r, json: { ...b, sessions: [
38
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
39
+ ] } });
40
+ });
41
+ const now = Date.now();
42
+ const msgs = [];
43
+ for (let i = 0; i < 5; i++) {
44
+ msgs.push({
45
+ id: `m${i}`, from_alias: 'alpha', to_alias: 'beta', content: 'hi',
46
+ network_id: 'default', created_at: new Date(now - (1000 + i * 50)).toISOString(),
47
+ });
48
+ }
49
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: msgs } }));
50
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
51
+
52
+ const page = await ctx.newPage();
53
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
54
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
55
+ await page.waitForSelector('[data-edge-endpoint-ring]', { timeout: 10000, state: 'attached' });
56
+ await page.waitForTimeout(300);
57
+
58
+ const probeAll = (page) => page.evaluate(() => {
59
+ const rings = Array.from(document.querySelectorAll('[data-edge-endpoint-ring]'));
60
+ return rings.map((r) => ({
61
+ active: r.getAttribute('data-edge-endpoint-active'),
62
+ strokeAttr: r.getAttribute('stroke-width'),
63
+ strokeData: r.getAttribute('data-edge-endpoint-ring-stroke-width'),
64
+ transition: r.style.transition,
65
+ opacity: parseFloat(r.getAttribute('opacity') || ''),
66
+ }));
67
+ });
68
+
69
+ // Rest state ---
70
+ const rest = await probeAll(page);
71
+
72
+ // Hover state — recent-signal panel row also flips hoveredEdgeKey
73
+ // (see "Hover a row → set hoveredEdgeKey" comment in TopoGraph
74
+ // around line 5197). The row is a robust hover target — discrete
75
+ // rect bbox, plain HTML coords through the SVG. Hovering the row
76
+ // for the alpha→beta flow drives hoveredEdgeEndpoints = {alpha,
77
+ // beta}, which is exactly the test condition we need.
78
+ const recentRow = await page.locator('[data-recent-row]').first();
79
+ await recentRow.hover({ force: true });
80
+ await page.waitForTimeout(300);
81
+ const hover = await probeAll(page);
82
+
83
+ await browser.close();
84
+
85
+ const restAllInactive = rest.every(r => r.active === 'false');
86
+ const restStrokes = rest.map(r => r.strokeAttr);
87
+ const restAll16 = restStrokes.every(s => s === '1.6');
88
+ const restTransition = rest.every(r => /stroke-width\s+180ms/.test(r.transition || ''));
89
+
90
+ const hoverActiveCount = hover.filter(r => r.active === 'true').length;
91
+ const hoverActiveRings = hover.filter(r => r.active === 'true');
92
+ const hoverActiveStrokes = hoverActiveRings.map(r => r.strokeAttr);
93
+ const hoverActiveAll24 = hoverActiveStrokes.length > 0 && hoverActiveStrokes.every(s => s === '2.4');
94
+ const hoverActiveDataStr = hoverActiveRings.every(r => r.strokeData === '2.4');
95
+ const hoverActiveOpacityHigh = hoverActiveRings.every(r => r.opacity > 0.5);
96
+ const hoverInactiveStillStable = hover.filter(r => r.active === 'false').every(r => r.strokeAttr === '1.6');
97
+
98
+ const results = {
99
+ rest_all_inactive: restAllInactive,
100
+ rest_all_stroke_16: restAll16,
101
+ rest_all_has_transition: restTransition,
102
+ hover_two_endpoints: hoverActiveCount === 2,
103
+ hover_active_stroke_24: hoverActiveAll24,
104
+ hover_data_attr_24: hoverActiveDataStr,
105
+ hover_active_opacity_hi: hoverActiveOpacityHigh,
106
+ hover_inactive_still_16: hoverInactiveStillStable,
107
+ };
108
+ const ok = Object.values(results).every(Boolean);
109
+ console.log(`${ok ? '✅' : '❌'} endpoint ring thicken:`, JSON.stringify(results),
110
+ '\n rest:', rest.map(r => ({ a: r.active, s: r.strokeAttr })),
111
+ '\n hover:', hover.map(r => ({ a: r.active, s: r.strokeAttr, o: r.opacity })));
112
+ 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);
@@ -0,0 +1,112 @@
1
+ /* Round 73 verification: clicking the pill body (not just the ×) clears
2
+ * the pin. Matches the Notion / Linear tag UX where the whole chip is
3
+ * the clear target. The × button keeps its dedicated <button> +
4
+ * aria-label so screen readers still get a proper "Clear filter" action.
5
+ *
6
+ * - Pre-seed pin via R66 sessionStorage so pill renders on first paint.
7
+ * - Click the pill body (text portion, NOT the × button).
8
+ * - Assert: pin clears + pill removed from DOM + node opacities
9
+ * restored.
10
+ * - Re-pin; click the × button → pin also clears.
11
+ * - title attr on the pill body reads "Click to clear filter".
12
+ * - cursor:pointer set on the pill body for affordance.
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const browser = await chromium.launch({ headless: true });
19
+
20
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
21
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
22
+ await ctx.addInitScript(() => {
23
+ try {
24
+ localStorage.setItem('anet-theme', 'cyber');
25
+ sessionStorage.setItem('anet_v3_auth', '1');
26
+ sessionStorage.setItem('anet-topo-pinned-status', 'working');
27
+ } catch {}
28
+ });
29
+
30
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
31
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ const sessions = [
36
+ { alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
37
+ { alias: 'idl', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
38
+ ];
39
+ await route.fulfill({ response: r, json: { ...b, sessions } });
40
+ });
41
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
42
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
43
+ const page = await ctx.newPage();
44
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
45
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
46
+ await page.waitForTimeout(500);
47
+
48
+ const readState = () => page.evaluate(() => {
49
+ const pill = document.querySelector('[data-active-filter="status"]');
50
+ return {
51
+ pillPresent: !!pill,
52
+ pillTitle: pill?.getAttribute('title') || null,
53
+ pillCursor: pill ? getComputedStyle(pill).cursor : null,
54
+ idlOpacity: +(document.querySelector('g[data-node="idl"]')?.style.opacity || '1'),
55
+ storageStatus: sessionStorage.getItem('anet-topo-pinned-status'),
56
+ };
57
+ });
58
+
59
+ const before = await readState();
60
+
61
+ // Click on the pill body — pick a point in the text region, NOT the ×.
62
+ // The pill is a flex row; the first inner <span> holds the text. Click
63
+ // at its centre.
64
+ await page.evaluate(() => {
65
+ const pill = document.querySelector('[data-active-filter="status"]');
66
+ if (!pill) throw new Error('pill not found');
67
+ const text = pill.querySelector('span');
68
+ if (!text) throw new Error('pill text not found');
69
+ const r = text.getBoundingClientRect();
70
+ window.__bodyClickXY = { x: r.x + r.width / 2, y: r.y + r.height / 2 };
71
+ });
72
+ const xy = await page.evaluate(() => window.__bodyClickXY);
73
+ await page.mouse.click(xy.x, xy.y);
74
+ await page.waitForTimeout(250);
75
+ const afterBodyClick = await readState();
76
+
77
+ // Re-pin via sessionStorage + event.
78
+ await page.evaluate(() => {
79
+ sessionStorage.setItem('anet-topo-pinned-status', 'working');
80
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'status', value: 'working' } }));
81
+ });
82
+ await page.waitForTimeout(250);
83
+ const afterRepin = await readState();
84
+
85
+ // Click the × button.
86
+ await page.locator('[data-active-filter="status"] button').first().click();
87
+ await page.waitForTimeout(250);
88
+ const afterXClick = await readState();
89
+
90
+ await browser.close();
91
+
92
+ const bright = (v) => v != null && v >= 0.55;
93
+ const dim = (v) => v != null && v < 0.4;
94
+ const results = {
95
+ before_pillPresent: before.pillPresent === true,
96
+ before_titleHint: before.pillTitle === 'Click to clear filter',
97
+ before_cursorPointer: before.pillCursor === 'pointer',
98
+ before_idleDim: dim(before.idlOpacity),
99
+ bodyClick_pillGone: afterBodyClick.pillPresent === false,
100
+ bodyClick_storageGone: afterBodyClick.storageStatus === null,
101
+ bodyClick_idleRestored: bright(afterBodyClick.idlOpacity),
102
+ repin_pillBack: afterRepin.pillPresent === true,
103
+ xClick_pillGone: afterXClick.pillPresent === false,
104
+ xClick_storageGone: afterXClick.storageStatus === null,
105
+ };
106
+ const ok = Object.values(results).every(Boolean);
107
+ console.log(`${ok ? '✅' : '❌'} filter pill body click:`, JSON.stringify(results),
108
+ `\n before=`, before,
109
+ `\n afterBodyClick=`, afterBodyClick,
110
+ `\n afterRepin=`, afterRepin,
111
+ `\n afterXClick=`, afterXClick);
112
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,135 @@
1
+ /* Round 323 verification: filter pin pill count spans (status /
2
+ * group / vendor) AND edge filter pill count (cold + hot) all pick
3
+ * up Tailwind `tabular-nums` class. Pre-R323 the count digit
4
+ * (matchCount or link.count) widened 9→10 / 99→100 even in font-mono
5
+ * because mono fonts still have natural-vs-tabular variance; the
6
+ * trailing × clear-button shifted right a couple px.
7
+ *
8
+ * 9th surface in the info-density tabular-nums sweep:
9
+ * R224 edge badge
10
+ * R225 hub digit / panel flows-count / recent-row count
11
+ * R229 group-label count
12
+ * R230 group-label status pips
13
+ * R320 recent-row count fw=600 (left neighbour)
14
+ * R321 recent-row timestamp
15
+ * R322 panel hot count
16
+ * R323 filter pill counts (status + group + vendor + edge×2)
17
+ *
18
+ * Contract:
19
+ * - All 5 patterns gain `tabular-nums`. We can probe via a
20
+ * scenario that activates the status pin (simplest), then
21
+ * check the resulting [data-filter-pill-count] element.
22
+ * - Each `[data-filter-pill-count]` has computed
23
+ * fontVariantNumeric containing 'tabular-nums'.
24
+ * - The edge pill count test is best-effort — only active when
25
+ * a flow is pinned via R116; we don't simulate pin in this
26
+ * test, so we accept its absence.
27
+ * - R322 hot tabular + R321 ts tabular regressions intact.
28
+ * - R317/R318/R294 chrome + pulse regressions intact.
29
+ *
30
+ * Fixture: 2 working sessions, 2 idle sessions — pin status='idle'
31
+ * via initial localStorage so the status filter pill is visible.
32
+ */
33
+ import { chromium } from 'playwright';
34
+ import { readFileSync } from 'node:fs';
35
+
36
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
37
+ const browser = await chromium.launch({ headless: true });
38
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
39
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
40
+ await ctx.addInitScript(() => {
41
+ try {
42
+ localStorage.setItem('anet-theme', 'cyber');
43
+ sessionStorage.setItem('anet_v3_auth', '1');
44
+ } catch {}
45
+ });
46
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
47
+ await ctx.route('**/api/hub/status*', async (route) => {
48
+ const r = await route.fetch();
49
+ const b = await r.json();
50
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
51
+ const mk = (alias, status) => ({
52
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
53
+ network_id: nid, project_dir: null,
54
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
55
+ });
56
+ await route.fulfill({ response: r, json: { ...b, sessions: [
57
+ mk('alpha-1', 'working'),
58
+ mk('alpha-2', 'idle'),
59
+ mk('beta-1', 'working'),
60
+ mk('beta-2', 'idle'),
61
+ ] } });
62
+ });
63
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
64
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
65
+
66
+ const page = await ctx.newPage();
67
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
68
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 20000 });
69
+ await page.waitForTimeout(300);
70
+
71
+ // Programmatically trigger a status pin by clicking on the working
72
+ // status legend row. R64 pin handler responds to legend clicks.
73
+ // Best-effort: if the click target is not exposed, fall back to
74
+ // the chip-row "working chip" click which also triggers pinning.
75
+ await page.click('[data-working-chip]', { delay: 50 }).catch(() => {});
76
+ await page.waitForTimeout(400);
77
+
78
+ const probe = await page.evaluate(() => {
79
+ const pillCounts = Array.from(document.querySelectorAll('[data-filter-pill-count]'));
80
+ const edgeColdCount = document.querySelector('[data-active-filter-edge-count]');
81
+ const edgeHotCount = document.querySelector('[data-active-filter-edge-count-hot]');
82
+ const recentRowTs = document.querySelector('[data-recent-row-ts]');
83
+ const recentRowCount = document.querySelector('[data-recent-row-count]');
84
+ const recentPanelHot = document.querySelector('[data-recent-panel-hot-count]');
85
+
86
+ const cs = (el) => el ? getComputedStyle(el).fontVariantNumeric : null;
87
+
88
+ return {
89
+ pillCount: pillCounts.length,
90
+ pillCountAllTabular: pillCounts.every(p => /tabular-nums/.test(getComputedStyle(p).fontVariantNumeric || '')),
91
+ pillTexts: pillCounts.map(p => p.textContent),
92
+ edgeColdTabular: edgeColdCount ? cs(edgeColdCount) : null,
93
+ edgeHotTabular: edgeHotCount ? cs(edgeHotCount) : null,
94
+ edgeColdClass: edgeColdCount?.className ?? null,
95
+ edgeHotClass: edgeHotCount?.className ?? null,
96
+ recentRowTsTabular: cs(recentRowTs),
97
+ recentRowCountFw: recentRowCount ? getComputedStyle(recentRowCount).fontWeight : null,
98
+ recentPanelHotTab: cs(recentPanelHot),
99
+ layoutInactiveCls: document.querySelector('[data-topo-chrome-layout="grid"]')?.className ?? '',
100
+ layoutActiveCls: document.querySelector('[data-topo-chrome-layout="ring"]')?.className ?? '',
101
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
102
+ };
103
+ });
104
+ await browser.close();
105
+
106
+ const hasTab = (s) => /tabular-nums/.test(s || '');
107
+ const classHasTab = (s) => /tabular-nums/.test(s || '');
108
+
109
+ const results = {
110
+ // At least one filter pill count visible after click (the status pin).
111
+ pill_count_present: probe.pillCount >= 1,
112
+ pill_count_all_tabular: probe.pillCountAllTabular,
113
+ // The edge pills are not triggered in this test, but their CLASS
114
+ // attribute should already include `tabular-nums` from R323 even
115
+ // if not rendered (best-effort source-level probe via a snapshot
116
+ // of an active pill is not possible without simulating R116 pin).
117
+ // We accept presence-or-absence here; the main contract is the
118
+ // status/group/vendor pills.
119
+ // R322 / R321 regressions — both surfaces depend on recent-signal
120
+ // panel rendering, which requires messages. Fixture here has zero
121
+ // messages (only sessions for the pin click), so accept null OR
122
+ // tabular for both. The R322 regression is primarily covered by
123
+ // topo-recent-panel-hot-tabular-test which forces messages.
124
+ r322_panel_hot_tabular_or_absent: probe.recentPanelHotTab === null || hasTab(probe.recentPanelHotTab),
125
+ r321_ts_tabular_or_absent: probe.recentRowTsTabular === null || hasTab(probe.recentRowTsTabular),
126
+ // R317/R318 chrome regression.
127
+ r317_inactive_gray_400: probe.layoutInactiveCls.includes('text-gray-400'),
128
+ r318_active_font_medium: probe.layoutActiveCls.includes('font-medium'),
129
+ r294_pulse_absent: probe.pulseCount === 0,
130
+ };
131
+ const ok = Object.values(results).every(Boolean);
132
+ console.log(`${ok ? '✅' : '❌'} filter pill count tabular:`, JSON.stringify(results),
133
+ '\n pill count visible:', probe.pillCount,
134
+ '\n pill texts:', probe.pillTexts);
135
+ process.exit(ok ? 0 : 1);