@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,101 @@
1
+ /* Round 157 verification: minimap a11y completion.
2
+ *
3
+ * Pre-R157 the R30 minimap had role="img" (wrong — it's clickable)
4
+ * + aria-label but no tabIndex / onKeyDown. Mouse users could
5
+ * click to recenter; keyboard-only users couldn't reach or
6
+ * activate it.
7
+ *
8
+ * R157 closes the gap with the standard pattern (R116/R139/R140/
9
+ * R151/R152):
10
+ * role="button"
11
+ * tabIndex={0}
12
+ * aria-label="Topology minimap — click to recenter, Enter to reset view"
13
+ * onKeyDown(Enter/Space) → resetView()
14
+ * anet-topo-chip-focus className (R155 cyan outline)
15
+ *
16
+ * Test:
17
+ * 1. Force the canvas off-default via zoom (so minimap renders)
18
+ * 2. Verify role / tabIndex / aria-label / class on the minimap
19
+ * 3. Press Enter on focused minimap → view resets (zoom back to 100%)
20
+ */
21
+ import { chromium } from 'playwright';
22
+ import { readFileSync } from 'node:fs';
23
+
24
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
25
+ const browser = await chromium.launch({ headless: true });
26
+
27
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
28
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
29
+ await ctx.addInitScript(() => {
30
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
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) => ({
38
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
39
+ network_id: nid, project_dir: null,
40
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
41
+ });
42
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta')] } });
43
+ });
44
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
45
+ await ctx.route('**/api/hub/tasks*', (route) => route.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 === 2, { timeout: 30000 });
50
+ await page.waitForTimeout(400);
51
+
52
+ // Initial: zoom is 100%, minimap not rendered (gated to non-default view)
53
+ const minimapBefore = await page.locator('[data-topo-minimap]').count();
54
+
55
+ // Press + (zoom in) twice to render the minimap
56
+ await page.keyboard.press('+');
57
+ await page.keyboard.press('+');
58
+ await page.waitForTimeout(250);
59
+
60
+ const probe = await page.evaluate(() => {
61
+ const el = document.querySelector('[data-topo-minimap]');
62
+ if (!el) return null;
63
+ return {
64
+ role: el.getAttribute('role'),
65
+ tabIndex: el.getAttribute('tabindex'),
66
+ ariaLabel: el.getAttribute('aria-label'),
67
+ title: el.getAttribute('title'),
68
+ className: el.getAttribute('class') || '',
69
+ zoomLevel: document.querySelector('[data-topo-chrome-zoom-level]')?.textContent?.trim(),
70
+ };
71
+ });
72
+
73
+ // Focus minimap + Enter → reset view (zoom back to 100%)
74
+ await page.locator('[data-topo-minimap]').focus();
75
+ await page.keyboard.press('Enter');
76
+ await page.waitForTimeout(250);
77
+ const afterEnter = await page.evaluate(() => ({
78
+ zoomLevel: document.querySelector('[data-topo-chrome-zoom-level]')?.textContent?.trim(),
79
+ minimapHidden: !document.querySelector('[data-topo-minimap]'),
80
+ }));
81
+
82
+ await browser.close();
83
+
84
+ const results = {
85
+ minimap_hiddenAtDefault: minimapBefore === 0,
86
+ minimap_renderedAfterZoom: probe !== null,
87
+ role_button: probe?.role === 'button',
88
+ tabIndex_0: probe?.tabIndex === '0',
89
+ ariaLabel_mentionsEnter: (probe?.ariaLabel || '').includes('Enter'),
90
+ title_mentionsEnter: (probe?.title || '').includes('Enter'),
91
+ hasFocusClass: (probe?.className || '').includes('anet-topo-chip-focus'),
92
+ zoomedBeforeReset: probe?.zoomLevel !== '100%',
93
+ enter_resetsZoom: afterEnter.zoomLevel === '100%',
94
+ enter_hidesMinimap: afterEnter.minimapHidden === true,
95
+ };
96
+ const ok = Object.values(results).every(Boolean);
97
+ console.log(`${ok ? '✅' : '❌'} minimap a11y:`, JSON.stringify(results),
98
+ `\n before count=${minimapBefore}`,
99
+ `\n probe=`, probe,
100
+ `\n afterEnter=`, afterEnter);
101
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,113 @@
1
+ /* Round 198 verification: minimap node dots get smooth opacity /
2
+ * fill / r transitions so a session flipping working→idle or going
3
+ * offline doesn't snap on the minimap mirror.
4
+ *
5
+ * Pre-R198 the canvas itself eased status changes (R167 stroke-width
6
+ * transition, R10 freshness alpha ramp, R3 fade-in chain), but the
7
+ * minimap below kept snap-cut. R198 brings the minimap dots in
8
+ * rhythm with the main canvas at 200ms.
9
+ *
10
+ * The minimap only mounts when isDefaultView=false (zoomed or panned),
11
+ * so the test must trigger a zoom-in first to populate it.
12
+ *
13
+ * Test:
14
+ * Mock 3 working + 1 offline session. Default view → no minimap.
15
+ * Click zoom-in → minimap mounts.
16
+ * Probe each minimap dot:
17
+ * 1. 4 dots present with data-topo-minimap-dot=<alias>
18
+ * 2. Online dots: r=1.7, opacity=0.9, data-...-online='true'
19
+ * 3. Offline dot: r=1.2, opacity=0.5, data-...-online='false'
20
+ * 4. Every dot's style.transition includes 'opacity 200ms',
21
+ * 'fill 200ms', and 'r 200ms'
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
+
29
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
30
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
31
+ await ctx.addInitScript(() => {
32
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
33
+ });
34
+
35
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
36
+ await ctx.route('**/api/hub/status*', async (route) => {
37
+ const r = await route.fetch();
38
+ const b = await r.json();
39
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
40
+ const mk = (alias, status) => ({
41
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
42
+ network_id: nid, project_dir: null,
43
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
44
+ });
45
+ await route.fulfill({ response: r, json: { ...b, sessions: [
46
+ mk('alpha', 'working'),
47
+ mk('beta', 'working'),
48
+ mk('gamma', 'working'),
49
+ mk('delta', 'offline'),
50
+ ] } });
51
+ });
52
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
53
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
54
+
55
+ const page = await ctx.newPage();
56
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
57
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
58
+ await page.waitForSelector('[data-topo-chrome-zoom-in]', { timeout: 10000 });
59
+ await page.waitForTimeout(300);
60
+
61
+ // Default view → minimap not mounted
62
+ const minimapBeforeZoom = await page.evaluate(() =>
63
+ document.querySelector('[data-topo-minimap]') !== null);
64
+
65
+ // Trigger zoom-in to mount the minimap
66
+ await page.click('[data-topo-chrome-zoom-in]');
67
+ await page.waitForTimeout(450); // past 280ms smoothView crossfade + margin
68
+ await page.waitForSelector('[data-topo-minimap-dot]', { timeout: 5000 });
69
+ await page.waitForTimeout(200);
70
+
71
+ const minimapAfterZoom = await page.evaluate(() =>
72
+ document.querySelector('[data-topo-minimap]') !== null);
73
+
74
+ const dots = await page.evaluate(() =>
75
+ [...document.querySelectorAll('[data-topo-minimap-dot]')].map(el => ({
76
+ alias: el.getAttribute('data-topo-minimap-dot'),
77
+ online: el.getAttribute('data-topo-minimap-dot-online'),
78
+ r: parseFloat(el.getAttribute('r') || ''),
79
+ opacity: parseFloat(el.getAttribute('opacity') || ''),
80
+ fill: el.getAttribute('fill'),
81
+ transition: el.style.transition,
82
+ })));
83
+
84
+ await browser.close();
85
+
86
+ const onlineDots = dots.filter(d => d.online === 'true');
87
+ const offlineDots = dots.filter(d => d.online === 'false');
88
+
89
+ const trans = (s) => s || '';
90
+ const hasOpacityT = (d) => /opacity\s+(0\.2s|200ms)/.test(trans(d.transition));
91
+ const hasFillT = (d) => /fill\s+(0\.2s|200ms)/.test(trans(d.transition));
92
+ const hasRT = (d) => /\br\s+(0\.2s|200ms)/.test(trans(d.transition));
93
+
94
+ const results = {
95
+ minimap_hidden_at_default: minimapBeforeZoom === false,
96
+ minimap_shown_after_zoom: minimapAfterZoom === true,
97
+ four_dots_present: dots.length === 4,
98
+ three_online: onlineDots.length === 3,
99
+ one_offline: offlineDots.length === 1,
100
+ online_r_1p7: onlineDots.every(d => Math.abs(d.r - 1.7) < 0.01),
101
+ online_opacity_0p9: onlineDots.every(d => Math.abs(d.opacity - 0.9) < 0.01),
102
+ offline_r_1p2: offlineDots.every(d => Math.abs(d.r - 1.2) < 0.01),
103
+ offline_opacity_0p5: offlineDots.every(d => Math.abs(d.opacity - 0.5) < 0.01),
104
+ all_have_opacity_transition: dots.every(hasOpacityT),
105
+ all_have_fill_transition: dots.every(hasFillT),
106
+ all_have_r_transition: dots.every(hasRT),
107
+ online_fill_truthy: onlineDots.every(d => d.fill && d.fill !== 'none'),
108
+ offline_fill_truthy: offlineDots.every(d => d.fill && d.fill !== 'none'),
109
+ };
110
+ const ok = Object.values(results).every(Boolean);
111
+ console.log(`${ok ? '✅' : '❌'} minimap dot transition:`, JSON.stringify(results),
112
+ `\n dots:`, dots);
113
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,88 @@
1
+ /* Round 332 verification: minimap container rounded-md → rounded-lg
2
+ * (6 → 8 px) — extends the R330-R331 corner-radius cascade.
3
+ *
4
+ * Size-hierarchy gradient now:
5
+ * outer wrapper rounded-xl 12 px (R330)
6
+ * inner SVG panels rx=10 10 px (R331)
7
+ * minimap overlay rounded-lg 8 px (R332, this round)
8
+ * inner detail card rx=8 8 px (codex 8f981a9)
9
+ * node label card rx=6 6 px
10
+ * group label hit rx=4 4 px
11
+ * row hover rect rx=3 3 px
12
+ *
13
+ * Contract:
14
+ * - [data-topo-minimap] outer container has className containing
15
+ * 'rounded-lg' (not 'rounded-md').
16
+ * - Computed border-top-left-radius === '8px'.
17
+ * - The minimap only mounts when view is non-default (zoom != 1 or
18
+ * pan != 0); we trigger zoom-in click to surface it.
19
+ * - R331 recent-panel rx=10 + R330 wrapper rounded-xl regressions.
20
+ * - R317 / R318 / R294 chrome + pulse regressions intact.
21
+ */
22
+ import { chromium } from 'playwright';
23
+ import { readFileSync } from 'node:fs';
24
+
25
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
26
+ const browser = await chromium.launch({ headless: true });
27
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
28
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
29
+ await ctx.addInitScript(() => {
30
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
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) => ({
38
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
39
+ network_id: nid, project_dir: null,
40
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
41
+ });
42
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('alpha'), mk('beta') ] } });
43
+ });
44
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
45
+ await ctx.route('**/api/hub/tasks*', (r) => r.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.waitForSelector('[data-topo-chrome-zoom-in]', { timeout: 15000 });
50
+ // Trigger zoom-in so view goes non-default and minimap mounts.
51
+ await page.click('[data-topo-chrome-zoom-in]');
52
+ await page.waitForTimeout(300);
53
+ await page.waitForSelector('[data-topo-minimap]', { timeout: 5000 });
54
+ await page.waitForTimeout(200);
55
+
56
+ const probe = await page.evaluate(() => {
57
+ const minimap = document.querySelector('[data-topo-minimap]');
58
+ const wrapper = document.querySelector('[data-topo-wrapper]');
59
+ const recent = document.querySelector('[data-topo-panel="recent"]');
60
+ return {
61
+ minimapClass: minimap?.className ?? '',
62
+ minimapRadius: minimap ? getComputedStyle(minimap).borderTopLeftRadius : null,
63
+ wrapperClass: wrapper?.className ?? '',
64
+ recentRx: recent?.querySelector('rect')?.getAttribute('rx') ?? null,
65
+ layoutInactiveCls: document.querySelector('[data-topo-chrome-layout="grid"]')?.className ?? '',
66
+ layoutActiveCls: document.querySelector('[data-topo-chrome-layout="ring"]')?.className ?? '',
67
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
68
+ };
69
+ });
70
+ await browser.close();
71
+
72
+ const results = {
73
+ minimap_has_rounded_lg: probe.minimapClass.includes('rounded-lg') && !probe.minimapClass.includes('rounded-md'),
74
+ minimap_radius_8px: probe.minimapRadius === '8px',
75
+ // R331 panel rx=10 regression.
76
+ r331_recent_rx_10: probe.recentRx === '10' || probe.recentRx === null, // null OK if panel hidden
77
+ // R330 wrapper regression.
78
+ r330_wrapper_rounded_xl: probe.wrapperClass.includes('rounded-xl'),
79
+ // R317 / R318 chrome regression.
80
+ r317_inactive_gray_400: probe.layoutInactiveCls.includes('text-gray-400'),
81
+ r318_active_font_medium: probe.layoutActiveCls.includes('font-medium'),
82
+ r294_pulse_absent: probe.pulseCount === 0,
83
+ };
84
+ const ok = Object.values(results).every(Boolean);
85
+ console.log(`${ok ? '✅' : '❌'} minimap radius rounded-lg:`, JSON.stringify(results),
86
+ '\n minimap border-top-left-radius:', probe.minimapRadius,
87
+ '\n recent panel rx (regression):', probe.recentRx);
88
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,94 @@
1
+ /* Round 287 verification: minimap viewport rect strokeWidth bumps
2
+ * 1 → 1.5. The rect frames the user's current view in the minimap —
3
+ * it IS the wayfinding "you are here" indicator. Bumping the stroke
4
+ * by half a pixel (same micro-polish family as R283 monogram stroke)
5
+ * lifts the boundary above ambient minimap-dot density without
6
+ * crowding the miniaturised dots inside.
7
+ *
8
+ * The minimap is only rendered when view ≠ default (zoomed or
9
+ * panned). Test simulates a zoom to surface the minimap.
10
+ *
11
+ * Contract:
12
+ * - [data-topo-minimap-viewport] exists after zoom.
13
+ * - Its stroke-width attribute === "1.5".
14
+ * - data-topo-minimap-dot still rendered (≥1 dot regression).
15
+ * - R283 monogram stroke + R282 watermark intact (regression).
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, model) => ({
33
+ alias, status: 'working', model, 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', 'claude-opus-4'),
39
+ mk('beta', 'gpt-4o'),
40
+ mk('gamma', 'claude-sonnet-4'),
41
+ mk('delta', null),
42
+ ] } });
43
+ });
44
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
45
+ await ctx.route('**/api/hub/tasks*', (r) => r.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-topo-brand-watermark]', { timeout: 10000 });
51
+
52
+ // Trigger zoom-in to surface the minimap. The zoom-in button is the
53
+ // '+' control inside the chrome strip.
54
+ await page.click('[data-topo-chrome-zoom-in]');
55
+ await page.click('[data-topo-chrome-zoom-in]');
56
+ await page.waitForSelector('[data-topo-minimap-viewport]', { timeout: 5000 });
57
+ await page.waitForTimeout(400);
58
+
59
+ const probe = await page.evaluate(() => {
60
+ const rect = document.querySelector('[data-topo-minimap-viewport]');
61
+ const dots = document.querySelectorAll('[data-topo-minimap-dot]');
62
+ const watermark = document.querySelector('[data-topo-brand-watermark]');
63
+ const alphaNode = document.querySelector('g[data-node="alpha"]');
64
+ const alphaCircles = alphaNode ? [...alphaNode.querySelectorAll('circle')] : [];
65
+ const alphaAvatarCircle = alphaCircles
66
+ .map(c => ({ r: +c.getAttribute('r') || 0, sw: c.getAttribute('stroke-width'), isBadge: c.hasAttribute('data-runtime-badge') }))
67
+ .filter(c => c.r > 0 && c.r < 18 && !c.isBadge)
68
+ .sort((a, b) => b.r - a.r)[0];
69
+ return {
70
+ rectPresent: rect !== null,
71
+ rectStrokeWidth: rect?.getAttribute('stroke-width') ?? null,
72
+ rectOpacity: rect?.getAttribute('opacity') ?? null,
73
+ dotCount: dots.length,
74
+ alphaAvatarStroke: alphaAvatarCircle?.sw ?? null,
75
+ watermarkPresent: watermark !== null,
76
+ };
77
+ });
78
+ await browser.close();
79
+
80
+ const results = {
81
+ minimap_rect_present: probe.rectPresent,
82
+ minimap_rect_stroke_is_1_5: probe.rectStrokeWidth === '1.5',
83
+ minimap_rect_opacity_kept: probe.rectOpacity === '0.9',
84
+ minimap_has_dots: probe.dotCount >= 1,
85
+ r283_monogram_stroke_kept: probe.alphaAvatarStroke === '1.5',
86
+ r282_watermark_present: probe.watermarkPresent,
87
+ };
88
+ const ok = Object.values(results).every(Boolean);
89
+ console.log(`${ok ? '✅' : '❌'} minimap rect stroke:`, JSON.stringify(results),
90
+ '\n rect strokeWidth:', probe.rectStrokeWidth,
91
+ '\n rect opacity:', probe.rectOpacity,
92
+ '\n dot count:', probe.dotCount,
93
+ '\n alpha avatar (R283):', probe.alphaAvatarStroke);
94
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,124 @@
1
+ /* Round 199 verification: minimap viewport rect transitions x/y/w/h
2
+ * during R169 smoothView (discrete zoom +/− and reset). Closes the
3
+ * rhythm-mismatch R198 just fixed for minimap dots — now extended to
4
+ * the rectangle that frames them.
5
+ *
6
+ * Pre-R199 a click on zoom-in crossfaded the main canvas over R168's
7
+ * 280ms but the minimap viewport rect snap-cut to its new dimensions
8
+ * in one frame. R199 gates a `transition: x/y/w/h 280ms` on
9
+ * smoothView=true so discrete zooms ease the rect smoothly. Drag-pan
10
+ * and wheel-zoom (smoothView=false) keep snappy response — a
11
+ * persistent transition during drag would lag the cursor by 280ms.
12
+ *
13
+ * Test:
14
+ * 1. Open page with 4 sessions, zoom in once to mount minimap.
15
+ * 2. Idle (smoothView=false): rect has
16
+ * data-topo-minimap-viewport-smooth='false', style.transition='none'.
17
+ * 3. Click zoom-in again → smoothView arms to 'true' for ~350ms.
18
+ * 4. Within 100ms of click: probe rect →
19
+ * data-topo-minimap-viewport-smooth='true', style.transition
20
+ * includes 'x 280ms', 'y 280ms', 'width 280ms', 'height 280ms'.
21
+ * 5. Wait past 400ms → smoothView clears → back to 'false', no
22
+ * transition.
23
+ */
24
+ import { chromium } from 'playwright';
25
+ import { readFileSync } from 'node:fs';
26
+
27
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
28
+ const browser = await chromium.launch({ headless: true });
29
+
30
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
31
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
32
+ await ctx.addInitScript(() => {
33
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
34
+ });
35
+
36
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
37
+ await ctx.route('**/api/hub/status*', async (route) => {
38
+ const r = await route.fetch();
39
+ const b = await r.json();
40
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
41
+ const mk = (alias) => ({
42
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
43
+ network_id: nid, project_dir: null,
44
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
45
+ });
46
+ await route.fulfill({ response: r, json: { ...b, sessions: [
47
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
48
+ ] } });
49
+ });
50
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
51
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
52
+
53
+ const page = await ctx.newPage();
54
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
55
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
56
+ await page.waitForSelector('[data-topo-chrome-zoom-in]', { timeout: 10000 });
57
+ await page.waitForTimeout(300);
58
+
59
+ // First zoom-in mounts the minimap. Wait past the 350ms smoothView
60
+ // timer so the minimap rect is at idle state (smooth='false') before
61
+ // we probe.
62
+ await page.click('[data-topo-chrome-zoom-in]');
63
+ await page.waitForTimeout(450);
64
+ await page.waitForSelector('[data-topo-minimap-viewport]', { timeout: 5000 });
65
+
66
+ const probe = async () => page.evaluate(() => {
67
+ const el = document.querySelector('[data-topo-minimap-viewport]');
68
+ if (!el) return null;
69
+ return {
70
+ smooth: el.getAttribute('data-topo-minimap-viewport-smooth'),
71
+ transition: el.style.transition,
72
+ x: parseFloat(el.getAttribute('x') || ''),
73
+ y: parseFloat(el.getAttribute('y') || ''),
74
+ w: parseFloat(el.getAttribute('width') || ''),
75
+ h: parseFloat(el.getAttribute('height') || ''),
76
+ };
77
+ });
78
+
79
+ const idle = await probe();
80
+
81
+ // Click zoom-in once more — arms smoothView for ~350ms — and probe
82
+ // within the arming window.
83
+ await page.click('[data-topo-chrome-zoom-in]');
84
+ await page.waitForTimeout(80); // well inside the 350ms arming window
85
+ const armed = await probe();
86
+
87
+ await page.waitForTimeout(420); // past 350ms timer + transition
88
+ const settled = await probe();
89
+
90
+ await browser.close();
91
+
92
+ const hasAllAxes = (s) =>
93
+ /\bx\s+(?:280ms|0\.28s)/.test(s || '') &&
94
+ /\by\s+(?:280ms|0\.28s)/.test(s || '') &&
95
+ /\bwidth\s+(?:280ms|0\.28s)/.test(s || '') &&
96
+ /\bheight\s+(?:280ms|0\.28s)/.test(s || '');
97
+
98
+ const results = {
99
+ idle_smooth_false: idle?.smooth === 'false',
100
+ idle_transition_none: idle?.transition === 'none' || idle?.transition === '',
101
+ idle_has_dimensions: idle && Number.isFinite(idle.x) && Number.isFinite(idle.w) && idle.w > 0,
102
+
103
+ armed_smooth_true: armed?.smooth === 'true',
104
+ armed_transition_present: !!armed?.transition && armed.transition !== 'none',
105
+ armed_transition_has_all: hasAllAxes(armed?.transition),
106
+
107
+ settled_smooth_false: settled?.smooth === 'false',
108
+ settled_transition_none: settled?.transition === 'none' || settled?.transition === '',
109
+
110
+ // R199 actually changed dimensions between idle (zoom 120%) and
111
+ // settled (zoom 144% after second click), or x/y diverged.
112
+ zoom_changed_dimensions: idle && settled && (
113
+ Math.abs(idle.w - settled.w) > 1 ||
114
+ Math.abs(idle.h - settled.h) > 1 ||
115
+ Math.abs(idle.x - settled.x) > 1 ||
116
+ Math.abs(idle.y - settled.y) > 1
117
+ ),
118
+ };
119
+ const ok = Object.values(results).every(Boolean);
120
+ console.log(`${ok ? '✅' : '❌'} minimap viewport smooth:`, JSON.stringify(results),
121
+ `\n idle:`, idle,
122
+ `\n armed:`, armed,
123
+ `\n settled:`, settled);
124
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,112 @@
1
+ /* Round 284 verification: known-vendor monogram letter fontFamily
2
+ * swaps monospace → system sans-serif. Continuation of R283 Vincent
3
+ * 5216 vendor avatar polish arc.
4
+ *
5
+ * Contract:
6
+ * - Known vendor (Anthropic, OpenAI, …) monogram letter — rendered
7
+ * fontFamily resolves into a sans-serif stack (NOT monospace).
8
+ * The actual computed font-family will likely return the OS-
9
+ * preferred match such as -apple-system / Segoe UI / Inter / etc.
10
+ * (We assert it does NOT include 'monospace' as the active stack.)
11
+ * - data-monogram-letter attribute present on the <text> element
12
+ * with the vendor.initial as its value.
13
+ * - Prefix-group fallback (unknown vendor) keeps monospace — the
14
+ * R283 contrast pattern: designed glyph = known, code text =
15
+ * unknown.
16
+ * - R283 monogram stroke=1.5 still in place (regression).
17
+ * - R282 watermark still present (regression).
18
+ */
19
+ import { chromium } from 'playwright';
20
+ import { readFileSync } from 'node:fs';
21
+
22
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
23
+ const browser = await chromium.launch({ headless: true });
24
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript(() => {
27
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
28
+ });
29
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
30
+ await ctx.route('**/api/hub/status*', async (route) => {
31
+ const r = await route.fetch();
32
+ const b = await r.json();
33
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
34
+ const mk = (alias, model) => ({
35
+ alias, status: 'working', model, runtime: 'claude-code-cli',
36
+ network_id: nid, project_dir: null,
37
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
38
+ });
39
+ await route.fulfill({ response: r, json: { ...b, sessions: [
40
+ mk('alpha', 'claude-opus-4'), // Anthropic → monogram (sans-serif)
41
+ mk('beta', 'gpt-4o'), // OpenAI → monogram (sans-serif)
42
+ mk('gamma', null), // unknown → prefix-group (monospace)
43
+ mk('delta', null), // unknown
44
+ ] } });
45
+ });
46
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
47
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
48
+
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
52
+ await page.waitForSelector('[data-monogram-letter]', { timeout: 10000, state: 'attached' });
53
+ await page.waitForTimeout(300);
54
+
55
+ const probe = await page.evaluate(() => {
56
+ // All monogram letters carry data-monogram-letter; expect 2 (alpha + beta).
57
+ const monos = [...document.querySelectorAll('[data-monogram-letter]')];
58
+ const monoSamples = monos.map((t) => ({
59
+ letter: t.getAttribute('data-monogram-letter'),
60
+ family: getComputedStyle(t).fontFamily,
61
+ weight: getComputedStyle(t).fontWeight,
62
+ parentAlias: t.closest('g[data-node]')?.getAttribute('data-node') ?? null,
63
+ }));
64
+ // Find prefix-group fallback text — inside g[data-node='gamma'], the
65
+ // <text> WITHOUT data-monogram-letter attribute (the prefix-group one).
66
+ const gammaNode = document.querySelector('g[data-node="gamma"]');
67
+ const gammaTexts = gammaNode ? [...gammaNode.querySelectorAll('text')] : [];
68
+ // The avatar text is the one inside the avatar IIFE — look for the
69
+ // text whose font-size matches avatar radius (~10-14) and is centered
70
+ // on the node. Easiest heuristic: text-anchor=middle, no data-monogram
71
+ // attr (so it's the unknown-vendor branch).
72
+ const prefixText = gammaTexts.find((t) =>
73
+ t.getAttribute('text-anchor') === 'middle' &&
74
+ !t.hasAttribute('data-monogram-letter')
75
+ );
76
+ const prefixFamily = prefixText ? getComputedStyle(prefixText).fontFamily : null;
77
+ // Also probe alpha monogram circle stroke (R283 regression).
78
+ const alphaNode = document.querySelector('g[data-node="alpha"]');
79
+ const alphaCircles = alphaNode ? [...alphaNode.querySelectorAll('circle')] : [];
80
+ const alphaAvatarCircle = alphaCircles
81
+ .map(c => ({ r: +c.getAttribute('r') || 0, sw: c.getAttribute('stroke-width'), isBadge: c.hasAttribute('data-runtime-badge') }))
82
+ .filter(c => c.r > 0 && c.r < 18 && !c.isBadge)
83
+ .sort((a, b) => b.r - a.r)[0];
84
+ const watermark = document.querySelector('[data-topo-brand-watermark]');
85
+ return {
86
+ monoSamples,
87
+ prefixFamily,
88
+ alphaAvatarStroke: alphaAvatarCircle?.sw ?? null,
89
+ watermarkPresent: watermark !== null,
90
+ };
91
+ });
92
+ await browser.close();
93
+
94
+ const isMonospace = (s) => /(^|,)\s*monospace\s*(,|$)|"monospace"/i.test(s || '');
95
+ const isSansSerif = (s) => /(sans-serif|system|Inter|Segoe|Helvetica|Arial|-apple-system|BlinkMac)/i.test(s || '');
96
+
97
+ const results = {
98
+ two_monograms: probe.monoSamples.length === 2,
99
+ alpha_letter_A: probe.monoSamples.some(s => s.parentAlias === 'alpha' && s.letter === 'A'),
100
+ beta_letter_O: probe.monoSamples.some(s => s.parentAlias === 'beta' && s.letter === 'O'),
101
+ all_monos_sans_serif: probe.monoSamples.every(s => isSansSerif(s.family) && !isMonospace(s.family)),
102
+ prefix_group_keeps_monospace: isMonospace(probe.prefixFamily),
103
+ r283_monogram_stroke_1_5_kept: probe.alphaAvatarStroke === '1.5',
104
+ r282_watermark_present: probe.watermarkPresent,
105
+ };
106
+ const ok = Object.values(results).every(Boolean);
107
+ console.log(`${ok ? '✅' : '❌'} monogram font swap:`, JSON.stringify(results),
108
+ '\n alpha mono family:', probe.monoSamples.find(s => s.parentAlias === 'alpha')?.family,
109
+ '\n beta mono family:', probe.monoSamples.find(s => s.parentAlias === 'beta')?.family,
110
+ '\n gamma prefix family:', probe.prefixFamily,
111
+ '\n alpha avatar circle stroke (R283):', probe.alphaAvatarStroke);
112
+ process.exit(ok ? 0 : 1);