@sleep2agi/agent-network-dashboard 0.4.6 → 0.5.0

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 (493) 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 +31 -31
  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/agent-network-dashboard_09kk21a._.js +4 -2
  160. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  161. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  162. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  163. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  164. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  165. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  166. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  167. package/.next/server/middleware-build-manifest.js +3 -3
  168. package/.next/server/pages/404.html +2 -2
  169. package/.next/server/pages/500.html +1 -1
  170. package/.next/server/server-reference-manifest.js +1 -1
  171. package/.next/server/server-reference-manifest.json +1 -1
  172. package/.next/static/chunks/0aauz~36q5n2a.css +2 -0
  173. package/.next/static/chunks/0f~0kgy2pycyc.js +1 -0
  174. package/.next/static/chunks/0l_~q07bhpkcx.js +7 -0
  175. package/.next/static/chunks/1175-dp18a1e_.js +1 -0
  176. package/.next/static/chunks/1552j0415oeal.js +4 -0
  177. package/.next/trace +2 -2
  178. package/.next/trace-build +1 -1
  179. package/.next/types/routes.d.ts +3 -1
  180. package/.next/types/validator.ts +18 -0
  181. package/app/api/hub/server/[hostname]/agents/route.ts +62 -0
  182. package/app/api/hub/server/[hostname]/health/route.ts +58 -0
  183. package/app/api/hub/servers/route.ts +88 -1
  184. package/app/components/CommandPalette.tsx +107 -0
  185. package/app/components/HelpOverlay.tsx +109 -1
  186. package/app/components/ServersDrawer.tsx +181 -5
  187. package/app/components/TopoGraph.tsx +6409 -286
  188. package/app/globals.css +110 -5
  189. package/app/lib/vendorIdentity.ts +14 -3
  190. package/docs/rfc/v0.10.0-hero3-dashboard-upgrade.md +230 -0
  191. package/package.json +2 -2
  192. package/public/sleep2agi-logo.svg +11 -0
  193. package/public/vendors/claude.svg +11 -0
  194. package/public/vendors/minimax.svg +11 -0
  195. package/public/vendors/openai.svg +12 -0
  196. package/screenshots/r293-p0/after-full.png +0 -0
  197. package/screenshots/r293-p0/after-topo.png +0 -0
  198. package/screenshots/r293-p0/baseline-full.png +0 -0
  199. package/screenshots/r293-p0/baseline-topo.png +0 -0
  200. package/screenshots/r293-p0/logo-integrated-full.png +0 -0
  201. package/screenshots/r293-p0/logo-integrated-topo.png +0 -0
  202. package/screenshots/r293-p0/servers-drawer-v0.10.0-full.png +0 -0
  203. package/screenshots/r293-p0/servers-drawer-v0.10.0.png +0 -0
  204. package/screenshots/r293-p0/wave1-after-full.png +0 -0
  205. package/screenshots/r293-p0/wave1-after-topo.png +0 -0
  206. package/scripts/p0-servers-drawer-screenshot.mjs +99 -0
  207. package/scripts/p0-topo-screenshot.mjs +82 -0
  208. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  209. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  210. package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
  211. package/scripts/topo-active-links-chip-self-hover-test.mjs +171 -0
  212. package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
  213. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  214. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  215. package/scripts/topo-active-pulse-ease-test.mjs +120 -0
  216. package/scripts/topo-brand-logo-transition-test.mjs +94 -0
  217. package/scripts/topo-brand-watermark-test.mjs +97 -0
  218. package/scripts/topo-canvas-aria-test.mjs +108 -0
  219. package/scripts/topo-canvas-brand-fade-test.mjs +87 -0
  220. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  221. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  222. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  223. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  224. package/scripts/topo-chip-focus-test.mjs +95 -0
  225. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  226. package/scripts/topo-chip-row-gap-test.mjs +90 -0
  227. package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
  228. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  229. package/scripts/topo-chip-unit-demotion-test.mjs +93 -0
  230. package/scripts/topo-chiprow-data-weight-test.mjs +94 -0
  231. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  232. package/scripts/topo-chrome-active-weight-test.mjs +104 -0
  233. package/scripts/topo-chrome-focus-ring-unify-test.mjs +100 -0
  234. package/scripts/topo-chrome-gap-test.mjs +73 -0
  235. package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
  236. package/scripts/topo-chrome-inactive-color-test.mjs +90 -0
  237. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  238. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  239. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  240. package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
  241. package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
  242. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  243. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  244. package/scripts/topo-chrome-zoom-readout-weight-test.mjs +81 -0
  245. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  246. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  247. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  248. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  249. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  250. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  251. package/scripts/topo-corner-radius-unify-test.mjs +103 -0
  252. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  253. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  254. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  255. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  256. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  257. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  258. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  259. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  260. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  261. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  262. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  263. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  264. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  265. package/scripts/topo-edge-fadein-test.mjs +106 -0
  266. package/scripts/topo-edge-focus-test.mjs +113 -0
  267. package/scripts/topo-edge-hover-test.mjs +13 -5
  268. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  269. package/scripts/topo-edge-pill-test.mjs +105 -0
  270. package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
  271. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  272. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  273. package/scripts/topo-empty-hint-tracking-test.mjs +84 -0
  274. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  275. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  276. package/scripts/topo-empty-state-recenter-test.mjs +107 -0
  277. package/scripts/topo-empty-subhint-tracking-test.mjs +87 -0
  278. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  279. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  280. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  281. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  282. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  283. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  284. package/scripts/topo-filter-pill-count-tabular-test.mjs +135 -0
  285. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  286. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  287. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  288. package/scripts/topo-freshness-chip-weight-test.mjs +90 -0
  289. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  290. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  291. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  292. package/scripts/topo-group-count-test.mjs +6 -1
  293. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  294. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  295. package/scripts/topo-group-label-hover-test.mjs +119 -0
  296. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  297. package/scripts/topo-group-label-tint-test.mjs +99 -0
  298. package/scripts/topo-group-label-title-test.mjs +83 -0
  299. package/scripts/topo-group-pin-test.mjs +119 -0
  300. package/scripts/topo-group-pip-fadein-test.mjs +104 -0
  301. package/scripts/topo-group-pip-singletier-drop-test.mjs +131 -0
  302. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  303. package/scripts/topo-group-status-pips-test.mjs +103 -0
  304. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  305. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  306. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  307. package/scripts/topo-groupbox-live-test.mjs +100 -0
  308. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  309. package/scripts/topo-groupbox-test.mjs +4 -2
  310. package/scripts/topo-header-gap-test.mjs +94 -0
  311. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  312. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  313. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  314. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  315. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  316. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  317. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  318. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  319. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  320. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  321. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  322. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  323. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  324. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  325. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  326. package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
  327. package/scripts/topo-hub-a11y-test.mjs +112 -0
  328. package/scripts/topo-hub-busyness-test.mjs +100 -0
  329. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  330. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  331. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  332. package/scripts/topo-hub-fadein-test.mjs +88 -0
  333. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  334. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  335. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  336. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  337. package/scripts/topo-hub-title-test.mjs +7 -2
  338. package/scripts/topo-hub-working-count-test.mjs +99 -0
  339. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  340. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  341. package/scripts/topo-kicker-color-test.mjs +93 -0
  342. package/scripts/topo-kicker-tracking-test.mjs +101 -0
  343. package/scripts/topo-kicker-weight-test.mjs +98 -0
  344. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  345. package/scripts/topo-label-lift-test.mjs +5 -1
  346. package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
  347. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  348. package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
  349. package/scripts/topo-layout-toggle-test.mjs +131 -0
  350. package/scripts/topo-layout-trailer-mr-test.mjs +82 -0
  351. package/scripts/topo-legend-compress-test.mjs +107 -0
  352. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  353. package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
  354. package/scripts/topo-legend-count-tabular-test.mjs +87 -0
  355. package/scripts/topo-legend-count-test.mjs +88 -0
  356. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  357. package/scripts/topo-legend-count-weight-test.mjs +86 -0
  358. package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
  359. package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
  360. package/scripts/topo-legend-hover-test.mjs +113 -0
  361. package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
  362. package/scripts/topo-legend-offline-label-simplify-test.mjs +86 -0
  363. package/scripts/topo-legend-panel-count-weight-test.mjs +89 -0
  364. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  365. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  366. package/scripts/topo-legend-pin-test.mjs +128 -0
  367. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  368. package/scripts/topo-legend-row-labels-simplify-test.mjs +80 -0
  369. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  370. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  371. package/scripts/topo-legend-row-title-test.mjs +102 -0
  372. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  373. package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
  374. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  375. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  376. package/scripts/topo-minimap-radius-test.mjs +88 -0
  377. package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
  378. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  379. package/scripts/topo-monogram-font-test.mjs +112 -0
  380. package/scripts/topo-monogram-stroke-test.mjs +101 -0
  381. package/scripts/topo-more-flows-letterspacing-test.mjs +111 -0
  382. package/scripts/topo-node-a11y-test.mjs +107 -0
  383. package/scripts/topo-node-alias-chat-pin-test.mjs +95 -0
  384. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  385. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  386. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  387. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  388. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  389. package/scripts/topo-node-title-peers-test.mjs +107 -0
  390. package/scripts/topo-node-title-test.mjs +101 -0
  391. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  392. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  393. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  394. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  395. package/scripts/topo-panel-count-unit-test.mjs +112 -0
  396. package/scripts/topo-panel-elevation-test.mjs +76 -0
  397. package/scripts/topo-panel-fadein-test.mjs +102 -0
  398. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  399. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  400. package/scripts/topo-panel-rx-test.mjs +90 -0
  401. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  402. package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
  403. package/scripts/topo-panel-title-tracking-test.mjs +78 -0
  404. package/scripts/topo-particle-stagger-test.mjs +82 -0
  405. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  406. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  407. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  408. package/scripts/topo-pin-intersection-test.mjs +121 -0
  409. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  410. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  411. package/scripts/topo-pin-persistence-test.mjs +105 -0
  412. package/scripts/topo-prefix-opacity-test.mjs +107 -0
  413. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  414. package/scripts/topo-pressure-hover-test.mjs +123 -0
  415. package/scripts/topo-pressure-pin-test.mjs +119 -0
  416. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  417. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  418. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  419. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  420. package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
  421. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  422. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  423. package/scripts/topo-recent-panel-count-weight-test.mjs +91 -0
  424. package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
  425. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  426. package/scripts/topo-recent-panel-hot-tabular-test.mjs +113 -0
  427. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  428. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  429. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  430. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  431. package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
  432. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  433. package/scripts/topo-recent-row-count-weight-test.mjs +112 -0
  434. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  435. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  436. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  437. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  438. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  439. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  440. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  441. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  442. package/scripts/topo-recent-row-title-test.mjs +112 -0
  443. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  444. package/scripts/topo-recent-row-ts-tabular-test.mjs +108 -0
  445. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  446. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  447. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  448. package/scripts/topo-reset-spin-test.mjs +120 -0
  449. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  450. package/scripts/topo-spokes-retired-test.mjs +93 -0
  451. package/scripts/topo-starfield-density-test.mjs +89 -0
  452. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  453. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  454. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  455. package/scripts/topo-svg-focus-test.mjs +112 -0
  456. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  457. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  458. package/scripts/topo-tier-guide-dashes-test.mjs +86 -0
  459. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  460. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  461. package/scripts/topo-tier-ring-test.mjs +69 -0
  462. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  463. package/scripts/topo-tier-stagger-test.mjs +89 -0
  464. package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
  465. package/scripts/topo-title-tracking-test.mjs +98 -0
  466. package/scripts/topo-titleblock-gap-test.mjs +86 -0
  467. package/scripts/topo-titleblock-margin-test.mjs +83 -0
  468. package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
  469. package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
  470. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  471. package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
  472. package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
  473. package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
  474. package/scripts/topo-vendor-chip-weight-test.mjs +87 -0
  475. package/scripts/topo-vendor-count-suffix-test.mjs +95 -0
  476. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  477. package/scripts/topo-vendor-pill-test.mjs +125 -0
  478. package/scripts/topo-vendor-pin-test.mjs +125 -0
  479. package/scripts/topo-view-smooth-test.mjs +131 -0
  480. package/scripts/topo-watermark-tracking-test.mjs +99 -0
  481. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  482. package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
  483. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  484. package/scripts/topo-wrapper-radius-test.mjs +73 -0
  485. package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
  486. package/.next/static/chunks/088ouqy7tekty.js +0 -2
  487. package/.next/static/chunks/0hyl7mdapsz38.js +0 -1
  488. package/.next/static/chunks/0mqwn-l7wtcdo.js +0 -7
  489. package/.next/static/chunks/127kw7~wsnyu7.css +0 -2
  490. package/.next/static/chunks/16yfxp9ak2gdm.js +0 -1
  491. /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_buildManifest.js +0 -0
  492. /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_clientMiddlewareManifest.js +0 -0
  493. /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_ssgManifest.js +0 -0
@@ -0,0 +1,83 @@
1
+ /* Round 299 verification: title block container bottom margin
2
+ * mb-3 (12px) → mb-4 (16px). After R298 tightened title-block
3
+ * INTERNAL gap (12→10px), the OUTER margin to the topology canvas
4
+ * widens so the title block reads as a deliberate compact badge
5
+ * with breathing room before the canvas frame begins.
6
+ *
7
+ * Contract:
8
+ * - The container wrapping [data-topo-section-kicker] +
9
+ * [data-topo-section-title] has computed margin-bottom 16px.
10
+ * - R298 internal gap === 10px still in place (regression).
11
+ * - R297 brand-logo transition + R296 kicker color + R295 swatch
12
+ * r=6 + R294 pulse absent intact.
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
+ const ctx = await browser.newContext({ viewport: { width: 1500, 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
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
25
+ await ctx.route('**/api/hub/status*', async (route) => {
26
+ const r = await route.fetch();
27
+ const b = await r.json();
28
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
29
+ const mk = (alias, model) => ({
30
+ alias, status: 'working', model, runtime: 'claude-code-cli',
31
+ network_id: nid, project_dir: null,
32
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
33
+ });
34
+ await route.fulfill({ response: r, json: { ...b, sessions: [
35
+ mk('alpha', 'claude-opus-4'),
36
+ mk('beta', 'gpt-4o'),
37
+ ] } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
40
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
41
+
42
+ const page = await ctx.newPage();
43
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
44
+ await page.waitForSelector('[data-topo-brand-logo]', { timeout: 15000 });
45
+ await page.waitForTimeout(300);
46
+
47
+ const probe = await page.evaluate(() => {
48
+ const logo = document.querySelector('[data-topo-brand-logo]');
49
+ // Title-block flex container is logo.parentElement. The OUTER margin
50
+ // belongs to logo.parentElement.parentElement (the title-block wrapper
51
+ // that holds left flex + right chip-row).
52
+ const titleBlockContainer = logo?.parentElement?.parentElement;
53
+ const tbCs = titleBlockContainer ? getComputedStyle(titleBlockContainer) : null;
54
+ const flexParent = logo?.parentElement;
55
+ const flexCs = flexParent ? getComputedStyle(flexParent) : null;
56
+ const swatch = document.querySelector('[data-legend-swatch="working"]');
57
+ return {
58
+ titleBlockMarginBottom: tbCs?.marginBottom ?? null,
59
+ titleBlockClass: titleBlockContainer?.className ?? null,
60
+ leftFlexGap: flexCs?.columnGap ?? null,
61
+ brandLogoTransition: logo ? getComputedStyle(logo).transition : null,
62
+ swatchR: swatch?.getAttribute('r') ?? null,
63
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
64
+ };
65
+ });
66
+ await browser.close();
67
+
68
+ const mb = parseFloat(probe.titleBlockMarginBottom) || 0;
69
+ const gap = parseFloat(probe.leftFlexGap) || 0;
70
+
71
+ const results = {
72
+ margin_bottom_16px: mb >= 15.5 && mb <= 16.5,
73
+ margin_not_12px: !(mb >= 11.5 && mb <= 12.5),
74
+ r298_left_gap_10px_kept: gap >= 9.5 && gap <= 10.5,
75
+ r297_transition_kept: /color/.test(probe.brandLogoTransition || ''),
76
+ r295_swatch_r_6: probe.swatchR === '6',
77
+ r294_pulse_absent: probe.pulseCount === 0,
78
+ };
79
+ const ok = Object.values(results).every(Boolean);
80
+ console.log(`${ok ? '✅' : '❌'} title-block margin:`, JSON.stringify(results),
81
+ '\n margin-bottom:', probe.titleBlockMarginBottom,
82
+ '\n left flex gap (R298):', probe.leftFlexGap);
83
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,110 @@
1
+ /* Round 270 verification: chrome strip TOGGLE buttons align inactive
2
+ * hover-bg to the Layout toggle's R163 cyan-preview pattern.
3
+ *
4
+ * Pre-R270:
5
+ * Layout Ring/Grid inactive: 'hover:bg-cyan-500/5 active:bg-cyan-500/15' ← R163 cyan preview
6
+ * nodeSize S/M/L inactive: 'hover:bg-white/5 active:bg-white/10' ← neutral white
7
+ * fullscreen inactive: 'hover:bg-white/5 active:bg-white/10' ← neutral white
8
+ * zoom -/+ + reset: 'hover:bg-white/5 active:bg-white/10' ← actions, no state
9
+ *
10
+ * The TOGGLES (Layout, nodeSize, fullscreen) split into two hover vocabularies:
11
+ * Layout uses cyan-preview, the others use neutral white. R270 unifies the
12
+ * other toggles to the cyan-preview pattern so all chrome TOGGLES preview
13
+ * their active state on hover. Pure ACTIONS (zoom, reset) stay white.
14
+ *
15
+ * Test scope:
16
+ * 1. nodeSize INACTIVE button(s) have 'hover:bg-cyan-500/5' class. With
17
+ * nodeScale=1 (L active), S and M are inactive — probe one of them.
18
+ * 2. nodeSize ACTIVE button still has 'hover:bg-cyan-500/20' (unchanged).
19
+ * 3. Fullscreen INACTIVE has 'hover:bg-cyan-500/5' (we're not in fullscreen).
20
+ * 4. Pure ACTIONS (zoom-out, reset) STILL use 'hover:bg-white/5' (regression).
21
+ * 5. R268 layout toggle border still uses pal.containerBorder (regression).
22
+ */
23
+ import { chromium } from 'playwright';
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
27
+ const browser = await chromium.launch({ headless: true });
28
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
32
+ });
33
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
34
+ await ctx.route('**/api/hub/status*', async (route) => {
35
+ const r = await route.fetch();
36
+ const b = await r.json();
37
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
38
+ const mk = (alias) => ({
39
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
40
+ network_id: nid, project_dir: null,
41
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
42
+ });
43
+ await route.fulfill({ response: r, json: { ...b, sessions: [
44
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
45
+ ] } });
46
+ });
47
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
48
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
49
+
50
+ const page = await ctx.newPage();
51
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
52
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
53
+ await page.waitForSelector('[data-topo-chrome-nodesize="S"]', { timeout: 10000 });
54
+ await page.waitForSelector('[data-topo-chrome-nodesize="L"]', { timeout: 10000 });
55
+ await page.waitForSelector('[data-topo-chrome-fullscreen]', { timeout: 10000 });
56
+ await page.waitForSelector('[data-topo-chrome-zoom-out]', { timeout: 10000 });
57
+ await page.waitForSelector('[data-topo-chrome-reset]', { timeout: 10000 });
58
+ await page.waitForSelector('[data-topo-chrome-layout-trailer]',{ timeout: 10000 });
59
+ await page.waitForTimeout(300);
60
+
61
+ const probe = await page.evaluate(() => {
62
+ // With default state, nodeScale=1 → L active, S+M inactive.
63
+ const s = document.querySelector('[data-topo-chrome-nodesize="S"]');
64
+ const m = document.querySelector('[data-topo-chrome-nodesize="M"]');
65
+ const l = document.querySelector('[data-topo-chrome-nodesize="L"]');
66
+ const fullscreen = document.querySelector('[data-topo-chrome-fullscreen]');
67
+ const zoomOut = document.querySelector('[data-topo-chrome-zoom-out]');
68
+ const reset = document.querySelector('[data-topo-chrome-reset]');
69
+ const layoutWrap = document.querySelector('[data-topo-chrome-layout-trailer]');
70
+ return {
71
+ sClasses: s ? s.className.toString() : null,
72
+ mClasses: m ? m.className.toString() : null,
73
+ lClasses: l ? l.className.toString() : null,
74
+ fullscreenClasses: fullscreen ? fullscreen.className.toString() : null,
75
+ zoomOutClasses: zoomOut ? zoomOut.className.toString() : null,
76
+ resetClasses: reset ? reset.className.toString() : null,
77
+ layoutBorderColor: layoutWrap ? window.getComputedStyle(layoutWrap).borderTopColor : null,
78
+ };
79
+ });
80
+ await browser.close();
81
+
82
+ const has = (s, cls) => (s || '').includes(cls);
83
+
84
+ // nodeScale defaults to 0.84 (M is active). S and L are inactive.
85
+ const results = {
86
+ // Inactive nodeSize buttons (S + L when M is default-active) → cyan hover
87
+ s_inactive_has_cyan_hover: has(probe.sClasses, 'hover:bg-cyan-500/5'),
88
+ l_inactive_has_cyan_hover: has(probe.lClasses, 'hover:bg-cyan-500/5'),
89
+ // Active nodeSize (M @ 0.84 default) → unchanged cyan-500/20 hover (active variant)
90
+ m_active_has_cyan20_hover: has(probe.mClasses, 'hover:bg-cyan-500/20'),
91
+ // No more white/5 on inactive nodeSize
92
+ s_inactive_no_white_hover: !has(probe.sClasses, 'hover:bg-white/5'),
93
+ // Fullscreen INACTIVE (not in fullscreen) picks up cyan hover
94
+ fullscreen_inactive_has_cyan: has(probe.fullscreenClasses, 'hover:bg-cyan-500/5'),
95
+ fullscreen_no_white_hover: !has(probe.fullscreenClasses, 'hover:bg-white/5'),
96
+ // Pure ACTIONS stay white
97
+ zoom_out_keeps_white_hover: has(probe.zoomOutClasses, 'hover:bg-white/5'),
98
+ reset_keeps_white_hover: has(probe.resetClasses, 'hover:bg-white/5'),
99
+ // R268 regression: layout border still uses pal.containerBorder
100
+ r268_layout_border_unified: probe.layoutBorderColor === 'rgb(42, 42, 74)',
101
+ };
102
+ const ok = Object.values(results).every(Boolean);
103
+ console.log(`${ok ? '✅' : '❌'} toggle inactive cyan hover:`, JSON.stringify(results),
104
+ '\n S classes:', probe.sClasses,
105
+ '\n L (active):', probe.lClasses,
106
+ '\n fullscreen (inactive):', probe.fullscreenClasses,
107
+ '\n zoom-out:', probe.zoomOutClasses,
108
+ '\n reset:', probe.resetClasses,
109
+ '\n layout border:', probe.layoutBorderColor);
110
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,89 @@
1
+ /* Round 265 verification: canvas top-rail gradient gains theme-toggle
2
+ * transition — closes the last canvas-envelope theme-snap.
3
+ *
4
+ * Pre-R265 the top-rail was:
5
+ * <div className={`absolute inset-x-0 top-0 h-px bg-gradient-to-r ${pal.topRailGradient}`} />
6
+ *
7
+ * pal.topRailGradient is theme-conditional:
8
+ * cyber: 'from-transparent via-cyan-400/70 to-transparent'
9
+ * light: 'from-transparent via-emerald-500/40 to-transparent'
10
+ *
11
+ * On theme toggle the className swap changes the gradient color but no
12
+ * inline transition existed — so the 1px-tall bright line at the
13
+ * canvas top edge SNAPPED while the wrapper bg (R254), border (R254),
14
+ * and shadow (R263) all eased. Small visible holdover.
15
+ *
16
+ * R265 adds `transition: background-image 200ms ease-out`. CSS
17
+ * gradient interpolation works on browsers ≥ Chrome 89 / Safari 14.1 /
18
+ * FF 96 when both gradients share matching stop structure — both
19
+ * gradients here are `from-transparent via-X to-transparent`
20
+ * (identical 3-stop layout).
21
+ *
22
+ * Test scope:
23
+ * 1. Top-rail element present at `[data-topo-top-rail]`.
24
+ * 2. Inline transition contains `background-image` at 200ms (or 0.2s).
25
+ * 3. Computed backgroundImage is a non-empty linear-gradient (not
26
+ * 'none' / empty — confirms the className gradient applied).
27
+ * 4. R263 wrapper box-shadow transition still in place (regression).
28
+ */
29
+ import { chromium } from 'playwright';
30
+ import { readFileSync } from 'node:fs';
31
+
32
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
33
+ const browser = await chromium.launch({ headless: true });
34
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
35
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
36
+ await ctx.addInitScript(() => {
37
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
38
+ });
39
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
40
+ await ctx.route('**/api/hub/status*', async (route) => {
41
+ const r = await route.fetch();
42
+ const b = await r.json();
43
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
44
+ const mk = (alias) => ({
45
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
46
+ network_id: nid, project_dir: null,
47
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
48
+ });
49
+ await route.fulfill({ response: r, json: { ...b, sessions: [
50
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
51
+ ] } });
52
+ });
53
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
54
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
55
+
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 === 4, { timeout: 30000 });
59
+ await page.waitForSelector('[data-topo-top-rail]', { timeout: 10000 });
60
+ await page.waitForSelector('[data-topo-wrapper]', { timeout: 10000 });
61
+ await page.waitForTimeout(300);
62
+
63
+ const probe = await page.evaluate(() => {
64
+ const rail = document.querySelector('[data-topo-top-rail]');
65
+ const wrapper = document.querySelector('[data-topo-wrapper]');
66
+ return {
67
+ railTransition: rail ? rail.style.transition : null,
68
+ railBackgroundImage: rail ? window.getComputedStyle(rail).backgroundImage : null,
69
+ wrapperTransition: wrapper ? wrapper.style.transition : null,
70
+ };
71
+ });
72
+ await browser.close();
73
+
74
+ const has = (s, prop) => new RegExp(`${prop}\\s+(?:200ms|0\\.2s)`).test(s || '');
75
+
76
+ const results = {
77
+ rail_present: probe.railTransition !== null,
78
+ rail_transition_has_bg_image_200: has(probe.railTransition, 'background-image'),
79
+ rail_has_non_empty_gradient: probe.railBackgroundImage != null
80
+ && probe.railBackgroundImage !== 'none'
81
+ && probe.railBackgroundImage.includes('linear-gradient'),
82
+ r263_wrapper_has_box_shadow_200: has(probe.wrapperTransition, 'box-shadow'),
83
+ };
84
+ const ok = Object.values(results).every(Boolean);
85
+ console.log(`${ok ? '✅' : '❌'} top-rail theme ease:`, JSON.stringify(results),
86
+ '\n rail transition: ', probe.railTransition,
87
+ '\n rail backgroundImage: ', probe.railBackgroundImage,
88
+ '\n wrapper transition: ', probe.wrapperTransition);
89
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,111 @@
1
+ /* Round 80 verification: hovering a vendor letter in the distribution
2
+ * chip dims nodes from other vendors. Composes with R55/R77/R79 dim
3
+ * mechanic — same 0.28 dim weight, distinct filter dimension.
4
+ *
5
+ * Fleet: 2 Claude nodes (alpha, gamma) + 1 GPT node (beta). The chip
6
+ * renders "C:2 G:1" (with the unknown bucket if any models don't
7
+ * resolve). Hover `C` → alpha + gamma stay bright, beta dims. Hover
8
+ * `G` → beta stays, alpha + gamma dim. Mouseleave restores.
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
+ sessionStorage.removeItem('anet-topo-pinned-status');
24
+ sessionStorage.removeItem('anet-topo-pinned-group');
25
+ } catch {}
26
+ });
27
+
28
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
29
+ await ctx.route('**/api/hub/status*', async (route) => {
30
+ const r = await route.fetch();
31
+ const b = await r.json();
32
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
33
+ const sessions = [
34
+ { alias: 'alpha', status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
35
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
36
+ { alias: 'beta', status: 'idle', model: 'gpt-5', runtime: 'codex-cli',
37
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
38
+ { alias: 'gamma', status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
39
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
40
+ ];
41
+ await route.fulfill({ response: r, json: { ...b, sessions } });
42
+ });
43
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
44
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
48
+ await page.waitForTimeout(500);
49
+
50
+ const opacities = () => page.evaluate(() => {
51
+ const o = {};
52
+ for (const a of ['alpha', 'beta', 'gamma']) {
53
+ const g = document.querySelector(`g[data-node="${a}"]`);
54
+ o[a] = g ? +(g.style.opacity || '1') : null;
55
+ }
56
+ return o;
57
+ });
58
+ const vendorLetters = await page.evaluate(() => {
59
+ return [...document.querySelectorAll('[data-vendor-letter]')].map(el => el.getAttribute('data-vendor-letter'));
60
+ });
61
+
62
+ const before = await opacities();
63
+
64
+ // vendorIdentity maps Anthropic→`A`, OpenAI→`O` (initials, not first
65
+ // letter of the model name). Find which sessions land in which bucket
66
+ // dynamically — `alpha` + `gamma` use claude-opus-4 (Anthropic = A),
67
+ // `beta` uses gpt-5 (OpenAI = O).
68
+ const aLetter = vendorLetters.find(l => l === 'A') || vendorLetters[0];
69
+ const oLetter = vendorLetters.find(l => l === 'O') || vendorLetters[1];
70
+
71
+ await page.locator(`[data-vendor-letter="${aLetter}"]`).hover();
72
+ await page.waitForTimeout(250);
73
+ const onClaude = await opacities();
74
+
75
+ await page.mouse.move(10, 10);
76
+ await page.waitForTimeout(250);
77
+ const afterClaude = await opacities();
78
+
79
+ if (oLetter) {
80
+ await page.locator(`[data-vendor-letter="${oLetter}"]`).hover();
81
+ await page.waitForTimeout(250);
82
+ }
83
+ const onGpt = await opacities();
84
+
85
+ await page.mouse.move(10, 10);
86
+ await page.waitForTimeout(250);
87
+ const afterGpt = await opacities();
88
+
89
+ await browser.close();
90
+
91
+ const bright = (v) => v != null && v >= 0.55;
92
+ const dim = (v) => v != null && v < 0.4;
93
+ const results = {
94
+ before_baseline: bright(before.alpha) && bright(before.beta) && bright(before.gamma),
95
+ vendorLetters_atLeast2: vendorLetters.length >= 2,
96
+ anthropicLetter_A: vendorLetters.includes('A'),
97
+ openaiLetter_O: vendorLetters.includes('O'),
98
+ hoverA_keepsClaudes: bright(onClaude.alpha) && bright(onClaude.gamma),
99
+ hoverA_dimsGPT: dim(onClaude.beta),
100
+ leaveA_restoresAll: bright(afterClaude.alpha) && bright(afterClaude.beta) && bright(afterClaude.gamma),
101
+ hoverO_keepsGPT: bright(onGpt.beta),
102
+ hoverO_dimsClaudes: dim(onGpt.alpha) && dim(onGpt.gamma),
103
+ leaveO_restoresAll: bright(afterGpt.alpha) && bright(afterGpt.beta) && bright(afterGpt.gamma),
104
+ };
105
+ const ok = Object.values(results).every(Boolean);
106
+ console.log(`${ok ? '✅' : '❌'} vendor-chip hover:`, JSON.stringify(results),
107
+ `\n vendorLetters=`, vendorLetters,
108
+ `\n before=`, before,
109
+ `\n onClaude=`, onClaude,
110
+ `\n onGpt=`, onGpt);
111
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,131 @@
1
+ /* Round 202 verification: vendor letter chips (A/O/书/M/?) deepen
2
+ * their OWN vendor colour on hover, completing the chip-row family
3
+ * R193/R195/R201 opened.
4
+ *
5
+ * Pre-R202 hover fired R88 canvas dim via setHoveredVendor but the
6
+ * chip itself stayed at bg=transparent. R202 tints the chip with
7
+ * color-mix(in srgb, <vendor-hue> 12%, transparent) only when hovered
8
+ * (and not pinned — pinned uses R180's stronger inset-ring treatment).
9
+ *
10
+ * Test (cyber dark, 2 claude + 2 gpt sessions → vendorDist={A:2, O:2}):
11
+ * 1. vendor letter chips A + O both render (vendorDist.length > 1).
12
+ * 2. Idle: data-vendor-hovered='false', backgroundColor='transparent'
13
+ * / 'rgba(0,0,0,0)'.
14
+ * 3. Inline transition includes 'box-shadow 150ms' (R180 still there)
15
+ * AND 'background-color 200ms' (R202 splice).
16
+ * 4. Hover 'A' chip — wait past 220ms.
17
+ * 5. 'A' data-vendor-hovered='true', backgroundColor is a non-empty
18
+ * tint string (color-mix or rendered fallback) different from
19
+ * idle. 'O' chip stays 'false' + transparent.
20
+ * 6. Move away — 'A' settles back to 'false' + transparent.
21
+ * 7. R88 canvas-effect verification — hover 'A' sets hoveredVendor
22
+ * which composes into the canvas dim logic; we probe via a
23
+ * surrogate signal that the chip's R55-like effect fires:
24
+ * hoveredVendor is exposed elsewhere via data attributes; the
25
+ * simplest gate is that the chip itself reflects the hover via
26
+ * its own data-vendor-hovered attribute already asserted above
27
+ * (event handler clearly ran).
28
+ */
29
+ import { chromium } from 'playwright';
30
+ import { readFileSync } from 'node:fs';
31
+
32
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
33
+ const browser = await chromium.launch({ headless: true });
34
+
35
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
36
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
37
+ await ctx.addInitScript(() => {
38
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
39
+ });
40
+
41
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
42
+ await ctx.route('**/api/hub/status*', async (route) => {
43
+ const r = await route.fetch();
44
+ const b = await r.json();
45
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
46
+ const mk = (alias, model) => ({
47
+ alias, status: 'working', model, runtime: 'cli-claude-code',
48
+ network_id: nid, project_dir: null,
49
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
50
+ });
51
+ // 3+ vendor types (R281 threshold tightened to >2): one Anthropic +
52
+ // one OpenAI + one InternLM (书生) + one unknown → vendorDist 4 types.
53
+ await route.fulfill({ response: r, json: { ...b, sessions: [
54
+ mk('alpha', 'claude-opus-4'),
55
+ mk('beta', 'gpt-4o'),
56
+ mk('gamma', 'internlm/internlm2'),
57
+ mk('delta', 'some-unknown-model'),
58
+ ] } });
59
+ });
60
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
61
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
62
+
63
+ const page = await ctx.newPage();
64
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
65
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
66
+ await page.waitForSelector('[data-vendor-letter="A"]', { timeout: 10000 });
67
+ await page.waitForSelector('[data-vendor-letter="O"]', { timeout: 5000 });
68
+ await page.waitForTimeout(300);
69
+
70
+ const probe = async (initial) => page.evaluate((i) => {
71
+ const el = document.querySelector(`[data-vendor-letter="${i}"]`);
72
+ if (!el) return null;
73
+ const cs = getComputedStyle(el);
74
+ return {
75
+ initial: i,
76
+ hovered: el.getAttribute('data-vendor-hovered'),
77
+ pinned: el.getAttribute('data-vendor-pinned'),
78
+ bg: cs.backgroundColor,
79
+ inlineBg: el.style.backgroundColor,
80
+ transition: el.style.transition,
81
+ };
82
+ }, initial);
83
+
84
+ const idleA = await probe('A');
85
+ const idleO = await probe('O');
86
+
87
+ await page.hover('[data-vendor-letter="A"]');
88
+ await page.waitForTimeout(260);
89
+ const hoverA = await probe('A');
90
+ const otherO = await probe('O');
91
+
92
+ await page.mouse.move(0, 0);
93
+ await page.waitForTimeout(280);
94
+ const settledA = await probe('A');
95
+
96
+ await browser.close();
97
+
98
+ const isTransparent = (s) =>
99
+ !s || s === 'transparent' || s === 'rgba(0, 0, 0, 0)' || s === 'rgba(0,0,0,0)';
100
+
101
+ const hasColorMix = (s) => /color-mix\s*\(/i.test(s || '');
102
+ const isNonEmptyColor = (s) => !!s && !isTransparent(s);
103
+
104
+ const results = {
105
+ A_chip_present: !!idleA,
106
+ O_chip_present: !!idleO,
107
+
108
+ idle_A_not_hovered: idleA?.hovered === 'false',
109
+ idle_A_transparent_bg: isTransparent(idleA?.bg),
110
+ idle_A_transition_boxshadow: /box-shadow\s+(?:150ms|0\.15s)/.test(idleA?.transition || ''),
111
+ idle_A_transition_bgcolor: /background-color\s+(?:200ms|0\.2s)/.test(idleA?.transition || ''),
112
+
113
+ idle_O_not_hovered: idleO?.hovered === 'false',
114
+
115
+ hover_A_hovered_attr: hoverA?.hovered === 'true',
116
+ hover_A_inline_color_mix: hasColorMix(hoverA?.inlineBg),
117
+ hover_A_computed_non_transparent: isNonEmptyColor(hoverA?.bg) && !isTransparent(hoverA?.bg),
118
+ hover_A_bg_changed: idleA?.bg !== hoverA?.bg,
119
+ hover_O_stays_idle: otherO?.hovered === 'false' && isTransparent(otherO?.bg),
120
+
121
+ settled_A_not_hovered: settledA?.hovered === 'false',
122
+ settled_A_transparent_bg: isTransparent(settledA?.bg),
123
+ };
124
+ const ok = Object.values(results).every(Boolean);
125
+ console.log(`${ok ? '✅' : '❌'} vendor chip self-hover:`, JSON.stringify(results),
126
+ `\n idleA:`, idleA,
127
+ `\n idleO:`, idleO,
128
+ `\n hoverA:`, hoverA,
129
+ `\n otherO:`, otherO,
130
+ `\n settledA:`, settledA);
131
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,92 @@
1
+ /* Round 234 verification: vendor letter chip (the inline chips
2
+ * showing 'A:3', 'C:2', etc. listing per-vendor session counts)
3
+ * picks up the Tailwind `tabular-nums` utility so its ':N' suffix
4
+ * digit doesn't reflow the chip row at the 9→10 boundary.
5
+ *
6
+ * 8th surface in the info-density tabular-nums sweep, completing
7
+ * the HTML chip-side after R232's working/online/active-links.
8
+ *
9
+ * Scenario: 4 working agents split across two vendors — 3 on
10
+ * claude-opus-4 (initial 'A') and 1 on gpt-4 (initial 'O') — so
11
+ * vendorDist.length === 2, which is the condition gating the
12
+ * chip strip render (line ~2116, `vendorDist.length > 1 && ...`).
13
+ * Expect two chips: A:3 and O:1.
14
+ */
15
+ import { chromium } from 'playwright';
16
+ import { readFileSync } from 'node:fs';
17
+
18
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
19
+ const browser = await chromium.launch({ headless: true });
20
+ const ctx = await browser.newContext({ viewport: { width: 1500, 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 { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
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, model) => ({
31
+ alias, status: 'working', model, runtime: 'claude-code-cli',
32
+ network_id: nid, project_dir: null,
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
34
+ });
35
+ // R281 tightened chip threshold from >1 to >2 — need 3+ vendor types.
36
+ // 2 claude (A) + 1 gpt (O) + 1 InternLM (书) → vendorDist.length === 3.
37
+ await route.fulfill({ response: r, json: { ...b, sessions: [
38
+ mk('alpha', 'claude-opus-4'),
39
+ mk('beta', 'claude-opus-4'),
40
+ mk('gamma', 'gpt-4'),
41
+ mk('delta', 'internlm/internlm2'),
42
+ ] } });
43
+ });
44
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
45
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
46
+
47
+ const page = await ctx.newPage();
48
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
49
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
50
+ await page.waitForSelector('[data-vendor-letter]', { timeout: 10000, state: 'attached' });
51
+ await page.waitForTimeout(300);
52
+
53
+ const out = await page.evaluate(() => {
54
+ const chips = Array.from(document.querySelectorAll('[data-vendor-letter]'));
55
+ return chips.map((c) => ({
56
+ initial: c.getAttribute('data-vendor-letter'),
57
+ countAttr: c.getAttribute('data-vendor-letter-count'),
58
+ classListHas: c.className.split(/\s+/).includes('tabular-nums'),
59
+ fontVarNumeric: getComputedStyle(c).fontVariantNumeric,
60
+ text: (c.textContent || '').trim(),
61
+ // Inner ':N' suffix span — verify font-variant-numeric inherits
62
+ innerFvn: (() => {
63
+ const inner = c.querySelectorAll('span');
64
+ // last span is the gray ':N' suffix
65
+ const last = inner[inner.length - 1];
66
+ return last ? getComputedStyle(last).fontVariantNumeric : null;
67
+ })(),
68
+ }));
69
+ });
70
+ await browser.close();
71
+
72
+ const hasTab = (s) => /tabular-nums/.test(s || '');
73
+
74
+ // Build a map of initial → count from the chips we found.
75
+ const countByInitial = Object.fromEntries(out.map(o => [o.initial, o.countAttr]));
76
+ // R281 fixture uses 2 claude (A:2) + 1 gpt (O:1) + 1 InternLM (书:1)
77
+ // to meet the new vendorDist.length > 2 threshold for chip render.
78
+ const results = {
79
+ three_chips: out.length === 3,
80
+ all_have_initial: out.every(o => typeof o.initial === 'string' && o.initial.length === 1),
81
+ anthropic_count_2: countByInitial['A'] === '2',
82
+ openai_count_1: countByInitial['O'] === '1',
83
+ internlm_count_1: countByInitial['书'] === '1',
84
+ all_class_utility: out.every(o => o.classListHas === true),
85
+ all_fvn_tabular: out.every(o => hasTab(o.fontVarNumeric)),
86
+ all_inner_fvn_inherits: out.every(o => hasTab(o.innerFvn)),
87
+ texts_match_counts: out.every(o => new RegExp(`:${o.countAttr}$`).test(o.text)),
88
+ };
89
+ const ok = Object.values(results).every(Boolean);
90
+ console.log(`${ok ? '✅' : '❌'} vendor chip tabular:`, JSON.stringify(results),
91
+ '\n chips:', out);
92
+ process.exit(ok ? 0 : 1);