@sleep2agi/agent-network-dashboard 0.4.6-preview.31 → 0.4.6-preview.310

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 (477) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/diagnostics/route-bundle-stats.json +41 -41
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/server/app/_global-error.html +1 -1
  7. package/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/_not-found.html +2 -2
  15. package/.next/server/app/_not-found.rsc +12 -12
  16. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  17. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  18. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  21. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/admin.html +2 -2
  24. package/.next/server/app/admin.rsc +14 -14
  25. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  26. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  27. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  28. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  29. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  30. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  31. package/.next/server/app/index.html +2 -2
  32. package/.next/server/app/index.rsc +14 -14
  33. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  34. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  35. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  36. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  37. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  38. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  39. package/.next/server/app/login.html +2 -2
  40. package/.next/server/app/login.rsc +14 -14
  41. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  42. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  43. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  44. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  45. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  46. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  47. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/logs.html +2 -2
  49. package/.next/server/app/logs.rsc +14 -14
  50. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  55. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  56. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/messages.html +2 -2
  58. package/.next/server/app/messages.rsc +14 -14
  59. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  60. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  61. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  62. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  64. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  65. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app/node.html +2 -2
  67. package/.next/server/app/node.rsc +14 -14
  68. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  69. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  70. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  71. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  73. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  74. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/nodes.html +2 -2
  76. package/.next/server/app/nodes.rsc +14 -14
  77. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  78. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  79. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  80. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  82. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  83. package/.next/server/app/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  85. package/.next/server/app/server-logs.html +2 -2
  86. package/.next/server/app/server-logs.rsc +14 -14
  87. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  88. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  89. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  90. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  91. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  92. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  93. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/settings/networks.html +2 -2
  95. package/.next/server/app/settings/networks.rsc +14 -14
  96. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  97. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  98. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  99. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  100. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  101. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  102. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  103. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  104. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  105. package/.next/server/app/settings/tokens.html +2 -2
  106. package/.next/server/app/settings/tokens.rsc +14 -14
  107. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  108. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  109. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  110. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  111. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  112. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  113. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  114. package/.next/server/app/settings.html +2 -2
  115. package/.next/server/app/settings.rsc +14 -14
  116. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  117. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  118. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  119. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  120. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  121. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  122. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  123. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  124. package/.next/server/app/tasks.html +2 -2
  125. package/.next/server/app/tasks.rsc +14 -14
  126. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  127. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  128. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  129. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  130. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  131. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  132. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
  133. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
  134. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  135. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  136. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
  137. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
  138. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -1
  139. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  140. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  141. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  142. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +1 -1
  143. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -1
  144. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  145. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  146. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
  147. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
  148. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
  149. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
  150. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
  151. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
  152. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
  153. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
  154. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
  155. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
  156. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
  157. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
  158. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  159. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  160. package/.next/server/middleware-build-manifest.js +3 -3
  161. package/.next/server/pages/404.html +2 -2
  162. package/.next/server/pages/500.html +1 -1
  163. package/.next/server/server-reference-manifest.js +1 -1
  164. package/.next/server/server-reference-manifest.json +1 -1
  165. package/.next/static/chunks/0-mpa_947ipeq.js +1 -0
  166. package/.next/static/chunks/01lmbsd37fybu.js +1 -0
  167. package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
  168. package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
  169. package/.next/static/chunks/05uk96gc~9mni.js +1 -0
  170. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  171. package/.next/static/chunks/0ggj5ummw2a84.js +7 -0
  172. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  173. package/.next/static/chunks/0o40uf-rn28q6.js +1 -0
  174. package/.next/static/chunks/0q3hr1~viwzkt.js +4 -0
  175. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  176. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  177. package/.next/static/chunks/0~ks9b48lw8hx.css +2 -0
  178. package/.next/static/chunks/13lvf-h-j8bmc.js +1 -0
  179. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  180. package/.next/trace +2 -2
  181. package/.next/trace-build +1 -1
  182. package/app/components/ChatPopover.tsx +38 -2
  183. package/app/components/CommandPalette.tsx +107 -0
  184. package/app/components/HelpOverlay.tsx +126 -0
  185. package/app/components/TopoGraph.tsx +5927 -258
  186. package/app/components/utils.ts +8 -7
  187. package/app/globals.css +110 -5
  188. package/app/lib/time.ts +36 -0
  189. package/app/lib/vendorIdentity.ts +14 -3
  190. package/package.json +2 -2
  191. package/public/sleep2agi-logo.svg +11 -0
  192. package/public/vendors/claude.svg +11 -0
  193. package/public/vendors/minimax.svg +11 -0
  194. package/public/vendors/openai.svg +12 -0
  195. package/screenshots/r293-p0/after-full.png +0 -0
  196. package/screenshots/r293-p0/after-topo.png +0 -0
  197. package/screenshots/r293-p0/baseline-full.png +0 -0
  198. package/screenshots/r293-p0/baseline-topo.png +0 -0
  199. package/screenshots/r293-p0/logo-integrated-full.png +0 -0
  200. package/screenshots/r293-p0/logo-integrated-topo.png +0 -0
  201. package/scripts/help-overlay-topo-test.mjs +44 -0
  202. package/scripts/p0-topo-screenshot.mjs +82 -0
  203. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  204. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  205. package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
  206. package/scripts/topo-active-links-chip-self-hover-test.mjs +171 -0
  207. package/scripts/topo-active-links-chip-test.mjs +73 -0
  208. package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
  209. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  210. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  211. package/scripts/topo-active-pulse-ease-test.mjs +120 -0
  212. package/scripts/topo-auto-fit-test.mjs +73 -0
  213. package/scripts/topo-brand-watermark-test.mjs +97 -0
  214. package/scripts/topo-canvas-aria-test.mjs +108 -0
  215. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  216. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  217. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  218. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  219. package/scripts/topo-chip-focus-test.mjs +95 -0
  220. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  221. package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
  222. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  223. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  224. package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
  225. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  226. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  227. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  228. package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
  229. package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
  230. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  231. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  232. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  233. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  234. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  235. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  236. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  237. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  238. package/scripts/topo-corner-radius-unify-test.mjs +103 -0
  239. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  240. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  241. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  242. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  243. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  244. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  245. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  246. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  247. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  248. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  249. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  250. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  251. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  252. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  253. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  254. package/scripts/topo-edge-fadein-test.mjs +106 -0
  255. package/scripts/topo-edge-focus-test.mjs +113 -0
  256. package/scripts/topo-edge-hover-test.mjs +93 -0
  257. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  258. package/scripts/topo-edge-pill-test.mjs +105 -0
  259. package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
  260. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  261. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  262. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  263. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  264. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  265. package/scripts/topo-empty-state-recenter-test.mjs +107 -0
  266. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  267. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  268. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  269. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  270. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  271. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  272. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  273. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  274. package/scripts/topo-fit-key-test.mjs +106 -0
  275. package/scripts/topo-freshness-chip-test.mjs +74 -0
  276. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  277. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  278. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  279. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  280. package/scripts/topo-group-count-test.mjs +6 -1
  281. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  282. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  283. package/scripts/topo-group-label-hover-test.mjs +119 -0
  284. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  285. package/scripts/topo-group-label-tint-test.mjs +99 -0
  286. package/scripts/topo-group-label-title-test.mjs +83 -0
  287. package/scripts/topo-group-pin-test.mjs +119 -0
  288. package/scripts/topo-group-pip-fadein-test.mjs +100 -0
  289. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  290. package/scripts/topo-group-status-pips-test.mjs +103 -0
  291. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  292. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  293. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  294. package/scripts/topo-groupbox-live-test.mjs +100 -0
  295. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  296. package/scripts/topo-groupbox-test.mjs +4 -2
  297. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  298. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  299. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  300. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  301. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  302. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  303. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  304. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  305. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  306. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  307. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  308. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  309. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  310. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  311. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  312. package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
  313. package/scripts/topo-hub-a11y-test.mjs +112 -0
  314. package/scripts/topo-hub-busyness-test.mjs +100 -0
  315. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  316. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  317. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  318. package/scripts/topo-hub-fadein-test.mjs +88 -0
  319. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  320. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  321. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  322. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  323. package/scripts/topo-hub-title-test.mjs +93 -0
  324. package/scripts/topo-hub-working-count-test.mjs +99 -0
  325. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  326. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  327. package/scripts/topo-kicker-color-test.mjs +93 -0
  328. package/scripts/topo-kicker-tracking-test.mjs +101 -0
  329. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  330. package/scripts/topo-label-lift-test.mjs +119 -0
  331. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  332. package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
  333. package/scripts/topo-layout-key-test.mjs +91 -0
  334. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  335. package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
  336. package/scripts/topo-layout-toggle-test.mjs +131 -0
  337. package/scripts/topo-legend-compress-test.mjs +107 -0
  338. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  339. package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
  340. package/scripts/topo-legend-count-tabular-test.mjs +87 -0
  341. package/scripts/topo-legend-count-test.mjs +88 -0
  342. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  343. package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
  344. package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
  345. package/scripts/topo-legend-hover-test.mjs +113 -0
  346. package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
  347. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  348. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  349. package/scripts/topo-legend-pin-test.mjs +128 -0
  350. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  351. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  352. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  353. package/scripts/topo-legend-row-title-test.mjs +102 -0
  354. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  355. package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
  356. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  357. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  358. package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
  359. package/scripts/topo-minimap-test.mjs +68 -0
  360. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  361. package/scripts/topo-mobile-chips-test.mjs +103 -0
  362. package/scripts/topo-monogram-font-test.mjs +112 -0
  363. package/scripts/topo-monogram-stroke-test.mjs +101 -0
  364. package/scripts/topo-node-a11y-test.mjs +107 -0
  365. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  366. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  367. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  368. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  369. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  370. package/scripts/topo-node-title-peers-test.mjs +107 -0
  371. package/scripts/topo-node-title-test.mjs +101 -0
  372. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  373. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  374. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  375. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  376. package/scripts/topo-overlap-test.mjs +32 -5
  377. package/scripts/topo-panel-elevation-test.mjs +76 -0
  378. package/scripts/topo-panel-fadein-test.mjs +102 -0
  379. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  380. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  381. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  382. package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
  383. package/scripts/topo-particle-stagger-test.mjs +82 -0
  384. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  385. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  386. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  387. package/scripts/topo-pin-intersection-test.mjs +121 -0
  388. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  389. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  390. package/scripts/topo-pin-persistence-test.mjs +105 -0
  391. package/scripts/topo-popover-meta-test.mjs +78 -0
  392. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  393. package/scripts/topo-pressure-bar-test.mjs +69 -0
  394. package/scripts/topo-pressure-hover-test.mjs +123 -0
  395. package/scripts/topo-pressure-pin-test.mjs +119 -0
  396. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  397. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  398. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  399. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  400. package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
  401. package/scripts/topo-pulse-rate-test.mjs +71 -0
  402. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  403. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  404. package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
  405. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  406. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  407. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  408. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  409. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  410. package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
  411. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  412. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  413. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  414. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  415. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  416. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  417. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  418. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  419. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  420. package/scripts/topo-recent-row-title-test.mjs +112 -0
  421. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  422. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  423. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  424. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  425. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  426. package/scripts/topo-reduced-motion-test.mjs +77 -0
  427. package/scripts/topo-reset-spin-test.mjs +120 -0
  428. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  429. package/scripts/topo-spokes-retired-test.mjs +93 -0
  430. package/scripts/topo-starfield-density-test.mjs +89 -0
  431. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  432. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  433. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  434. package/scripts/topo-svg-focus-test.mjs +112 -0
  435. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  436. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  437. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  438. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  439. package/scripts/topo-tier-ring-test.mjs +69 -0
  440. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  441. package/scripts/topo-tier-stagger-test.mjs +89 -0
  442. package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
  443. package/scripts/topo-title-tracking-test.mjs +98 -0
  444. package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
  445. package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
  446. package/scripts/topo-tz-ghost-test.mjs +69 -0
  447. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  448. package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
  449. package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
  450. package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
  451. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  452. package/scripts/topo-vendor-pill-test.mjs +125 -0
  453. package/scripts/topo-vendor-pin-test.mjs +125 -0
  454. package/scripts/topo-view-smooth-test.mjs +131 -0
  455. package/scripts/topo-watermark-tracking-test.mjs +99 -0
  456. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  457. package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
  458. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  459. package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
  460. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  461. package/.next/static/chunks/01u5zo8b7r_r3.js +0 -1
  462. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  463. package/.next/static/chunks/09nd.t_hd.y6t.css +0 -2
  464. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  465. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  466. package/.next/static/chunks/0fqit.b4j_6rz.js +0 -1
  467. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  468. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  469. package/.next/static/chunks/0pl7l_.2im.j0.js +0 -7
  470. package/.next/static/chunks/0qcfcgdthhos6.js +0 -1
  471. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  472. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  473. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  474. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  475. /package/.next/static/{mgs4E0Fu2JlZsA5DB0XUu → Kkkh4U0z1GCAKfQMsfmjt}/_buildManifest.js +0 -0
  476. /package/.next/static/{mgs4E0Fu2JlZsA5DB0XUu → Kkkh4U0z1GCAKfQMsfmjt}/_clientMiddlewareManifest.js +0 -0
  477. /package/.next/static/{mgs4E0Fu2JlZsA5DB0XUu → Kkkh4U0z1GCAKfQMsfmjt}/_ssgManifest.js +0 -0
@@ -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,103 @@
1
+ /* Round 47 verification: mobile chip collapse.
2
+ * - At narrow viewport (375px), pressure / vendor-dist / active-links /
3
+ * freshness chips are display:none; only essential chips render.
4
+ * - At sm+ (≥640px), the full chip row is visible.
5
+ * - Essential chips (Ring/Grid toggle + working + online) render at both. */
6
+ import { chromium } from 'playwright';
7
+ import { readFileSync } from 'node:fs';
8
+
9
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
10
+ const browser = await chromium.launch({ headless: true });
11
+
12
+ async function probe(width) {
13
+ const ctx = await browser.newContext({ viewport: { width, height: 900 } });
14
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
15
+ await ctx.addInitScript(() => {
16
+ try {
17
+ localStorage.setItem('anet-theme', 'cyber');
18
+ localStorage.setItem('anet-topo-layout', 'grid');
19
+ sessionStorage.setItem('anet_v3_auth', '1');
20
+ } catch {}
21
+ });
22
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
23
+ await ctx.route('**/api/hub/status*', async (route) => {
24
+ const r = await route.fetch();
25
+ const b = await r.json();
26
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
27
+ const sessions = [
28
+ { alias: 'wkr', status: 'working', network_id: nid, project_dir: null,
29
+ model: 'claude-opus-4', runtime: 'cli-claude-code',
30
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh },
31
+ { alias: 'idl', status: 'idle', network_id: nid, project_dir: null,
32
+ model: 'gpt-5', runtime: 'codex-cli',
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh },
34
+ ];
35
+ await route.fulfill({ response: r, json: { ...b, sessions } });
36
+ });
37
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
38
+ { from_alias: 'wkr', to_alias: 'idl', content: 'm', created_at: new Date().toISOString() },
39
+ ] } }));
40
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
41
+ const page = await ctx.newPage();
42
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
43
+ // The Overview page auto-hides the topology when window.innerWidth < 1024
44
+ // (the lg breakpoint). On narrow viewports the user clicks "Show
45
+ // Topology" to surface it; mimic that here so the chip-row tests reach
46
+ // the actual code path. Wait for SWR to deliver sessions first so the
47
+ // button renders before we look for it.
48
+ await page.waitForTimeout(1500);
49
+ const showBtn = page.locator('button', { hasText: /Show Topology/i });
50
+ if (await showBtn.count() > 0) await showBtn.first().click();
51
+ await page.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 30000 });
52
+ await page.waitForTimeout(600);
53
+ const visible = (sel) => page.evaluate(s => {
54
+ const el = document.querySelector(s);
55
+ if (!el) return null;
56
+ const r = el.getBoundingClientRect();
57
+ return r.width > 0 && r.height > 0;
58
+ }, sel);
59
+ const result = {
60
+ workingChipVisible: await page.evaluate(() => {
61
+ // "{N} working" chip — find the green-tinted one
62
+ const spans = [...document.querySelectorAll('span')];
63
+ const chip = spans.find(s => /^\d+ working$/.test((s.textContent || '').trim()));
64
+ if (!chip) return false;
65
+ const r = chip.getBoundingClientRect();
66
+ return r.width > 0 && r.height > 0;
67
+ }),
68
+ onlineChipVisible: await page.evaluate(() => {
69
+ const spans = [...document.querySelectorAll('span')];
70
+ const chip = spans.find(s => /^\d+ online$/.test((s.textContent || '').trim()));
71
+ if (!chip) return false;
72
+ const r = chip.getBoundingClientRect();
73
+ return r.width > 0 && r.height > 0;
74
+ }),
75
+ pressureVisible: await visible('[data-fleet-pressure]'),
76
+ activeLinksVisible: await page.evaluate(() => {
77
+ const spans = [...document.querySelectorAll('span')];
78
+ const chip = spans.find(s => /active link/.test(s.textContent || ''));
79
+ if (!chip) return false;
80
+ const r = chip.getBoundingClientRect();
81
+ return r.width > 0 && r.height > 0;
82
+ }),
83
+ freshnessVisible: await visible('[data-freshness-chip]'),
84
+ };
85
+ await ctx.close();
86
+ return result;
87
+ }
88
+
89
+ const mobile = await probe(375);
90
+ const desktop = await probe(900);
91
+ await browser.close();
92
+
93
+ const results = {
94
+ mobileEssentialsVisible: mobile.workingChipVisible && mobile.onlineChipVisible,
95
+ mobilePressureHidden: mobile.pressureVisible === false,
96
+ mobileActiveLinksHidden: mobile.activeLinksVisible === false,
97
+ mobileFreshnessHidden: mobile.freshnessVisible === false,
98
+ desktopAllVisible: desktop.workingChipVisible && desktop.onlineChipVisible && desktop.pressureVisible && desktop.activeLinksVisible && desktop.freshnessVisible,
99
+ };
100
+ const ok = Object.values(results).every(Boolean);
101
+ console.log(`${ok ? '✅' : '❌'} mobile chips:`, JSON.stringify(results),
102
+ `\n mobile=`, mobile, `\n desktop=`, desktop);
103
+ 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);
@@ -0,0 +1,101 @@
1
+ /* Round 283 verification: vendor monogram circle strokeWidth bumps
2
+ * 1 → 1.5 per Vincent 5216 "vendor avatar quality" ask increment.
3
+ *
4
+ * Pre-R283: monogram circle (known vendor, no logo asset) rendered
5
+ * with strokeWidth="1". The prefix-group fallback (unknown vendor)
6
+ * also uses strokeWidth="1".
7
+ *
8
+ * Post-R283: monogram (known vendor) bumps to strokeWidth="1.5";
9
+ * prefix-group fallback stays at 1 (less weight = "we don't know
10
+ * what this is" signal preserved).
11
+ *
12
+ * Test trap encountered first draft: each g[data-node] contains
13
+ * THREE small (<r=18) circles —
14
+ * 1. avatar/monogram (r=10-14): strokeWidth 1 or 1.5 per vendor
15
+ * 2. runtime badge (r=5.5-7): strokeWidth ALWAYS 1.5
16
+ * 3. pulse dot (r=2.5): no stroke (null)
17
+ * To isolate the avatar circle, filter by:
18
+ * - r is the largest of the small circles (avatar) OR
19
+ * - exclude elements with [data-runtime-badge] attribute
20
+ * We use the data-attr filter — cleaner intent.
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, model) => ({
38
+ alias, status: 'working', model, runtime: 'claude-code-cli',
39
+ network_id: nid, project_dir: null,
40
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
41
+ });
42
+ // Mix: known vendors (Anthropic + OpenAI monogram) + unknown vendor
43
+ // (no model field).
44
+ await route.fulfill({ response: r, json: { ...b, sessions: [
45
+ mk('alpha', 'claude-opus-4'), // Anthropic, monogram
46
+ mk('beta', 'gpt-4o'), // OpenAI, monogram
47
+ mk('gamma', null), // unknown, prefix-group fallback
48
+ mk('delta', null), // unknown
49
+ ] } });
50
+ });
51
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
52
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
53
+
54
+ const page = await ctx.newPage();
55
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
56
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
57
+ await page.waitForSelector('[data-topo-brand-watermark]', { timeout: 10000 });
58
+ await page.waitForTimeout(300);
59
+
60
+ const probe = await page.evaluate(() => {
61
+ // For each node, find the AVATAR circle specifically:
62
+ // - r < 18 (excludes status ring with r ~22-26)
63
+ // - NOT a runtime badge (no data-runtime-badge attr)
64
+ // - has stroke (excludes pulse dot)
65
+ const nodes = [...document.querySelectorAll('g[data-node]')];
66
+ const samples = nodes.map((g) => {
67
+ const alias = g.getAttribute('data-node');
68
+ const circles = [...g.querySelectorAll('circle')];
69
+ const avatar = circles
70
+ .map((c) => ({
71
+ r: +c.getAttribute('r') || 0,
72
+ sw: c.getAttribute('stroke-width'),
73
+ stroke: c.getAttribute('stroke'),
74
+ isBadge: c.hasAttribute('data-runtime-badge'),
75
+ }))
76
+ .filter((c) => c.r > 0 && c.r < 18 && !c.isBadge && c.stroke && c.stroke !== 'none');
77
+ // Of the remaining, the avatar/monogram is the largest-r one.
78
+ avatar.sort((a, b) => b.r - a.r);
79
+ return { alias, avatarStroke: avatar[0]?.sw ?? null, avatarR: avatar[0]?.r ?? null };
80
+ });
81
+ const watermark = document.querySelector('[data-topo-brand-watermark]');
82
+ return { samples, watermarkPresent: watermark !== null };
83
+ });
84
+ await browser.close();
85
+
86
+ const alphaSample = probe.samples.find(s => s.alias === 'alpha');
87
+ const betaSample = probe.samples.find(s => s.alias === 'beta');
88
+ const gammaSample = probe.samples.find(s => s.alias === 'gamma');
89
+
90
+ const results = {
91
+ alpha_anthropic_mono_1_5: alphaSample?.avatarStroke === '1.5',
92
+ beta_openai_mono_1_5: betaSample?.avatarStroke === '1.5',
93
+ gamma_prefix_stays_1: gammaSample?.avatarStroke === '1',
94
+ r282_watermark_present: probe.watermarkPresent,
95
+ };
96
+ const ok = Object.values(results).every(Boolean);
97
+ console.log(`${ok ? '✅' : '❌'} monogram stroke:`, JSON.stringify(results),
98
+ '\n alpha (claude): stroke=', alphaSample?.avatarStroke, ' r=', alphaSample?.avatarR,
99
+ '\n beta (gpt-4o): stroke=', betaSample?.avatarStroke, ' r=', betaSample?.avatarR,
100
+ '\n gamma (unknown): stroke=', gammaSample?.avatarStroke, ' r=', gammaSample?.avatarR);
101
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,107 @@
1
+ /* Round 151 verification: node <g> gains a11y compliance.
2
+ *
3
+ * Node clickable for chat since R45, but tab-unreachable +
4
+ * screen-reader-unannounced. R151 matches the pattern R116 /
5
+ * R139 / R140 / R148 / R149 applied to other interactive
6
+ * surfaces:
7
+ * role="button"
8
+ * tabIndex={0}
9
+ * aria-label="Chat with {alias} ({status})"
10
+ * aria-pressed={chatAlias === session.alias}
11
+ * onKeyDown(Enter / Space) → setChatAlias
12
+ *
13
+ * Three checks:
14
+ * 1. All nodes carry role=button, tabIndex=0, aria-label, aria-
15
+ * pressed=false by default
16
+ * 2. Pressing Enter on a focused node opens chat (aria-pressed
17
+ * flips to true, chat popover renders)
18
+ * 3. Pressing Space on a different node moves the open chat
19
+ * target — only one aria-pressed=true at a time
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
+
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, status) => ({
39
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
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', 'working'), mk('beta', 'idle'), mk('gamma', 'working'),
45
+ ] } });
46
+ });
47
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
48
+ await ctx.route('**/api/hub/tasks*', (route) => route.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 === 3, { timeout: 30000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ const inspectAll = () => page.evaluate(() => {
56
+ const nodes = [...document.querySelectorAll('g[data-node]')];
57
+ return nodes.map(n => ({
58
+ alias: n.getAttribute('data-node'),
59
+ role: n.getAttribute('role'),
60
+ tabIndex: n.getAttribute('tabindex'),
61
+ ariaLabel: n.getAttribute('aria-label'),
62
+ ariaPressed: n.getAttribute('aria-pressed'),
63
+ }));
64
+ });
65
+
66
+ const before = await inspectAll();
67
+
68
+ // Focus alpha node and press Enter → opens chat
69
+ await page.locator('g[data-node="alpha"]').focus();
70
+ await page.keyboard.press('Enter');
71
+ await page.waitForTimeout(250);
72
+ const afterAlpha = await inspectAll();
73
+ const popoverPresentA = await page.locator('.anet-chat-popover, [data-chat-popover]').count() > 0
74
+ || await page.evaluate(() => !!document.querySelector('[role="dialog"]'));
75
+
76
+ // Focus gamma node and press Space → moves chat target (only one pressed)
77
+ await page.locator('g[data-node="gamma"]').focus();
78
+ await page.keyboard.press(' ');
79
+ await page.waitForTimeout(250);
80
+ const afterGamma = await inspectAll();
81
+
82
+ await browser.close();
83
+
84
+ const results = {
85
+ // Baseline shape
86
+ threeNodes: before.length === 3,
87
+ allRoleButton: before.every(n => n.role === 'button'),
88
+ allTabIndex0: before.every(n => n.tabIndex === '0'),
89
+ allAriaLabel: before.every(n => /^Chat with /.test(n.ariaLabel || '')),
90
+ allAriaPressedFalse: before.every(n => n.ariaPressed === 'false'),
91
+
92
+ // After Enter on alpha
93
+ enterOpensChat: afterAlpha.find(n => n.alias === 'alpha')?.ariaPressed === 'true',
94
+ popoverShown: popoverPresentA,
95
+ othersStillFalse: afterAlpha.filter(n => n.alias !== 'alpha').every(n => n.ariaPressed === 'false'),
96
+
97
+ // After Space on gamma (chat target moved)
98
+ spaceMovesTarget: afterGamma.find(n => n.alias === 'gamma')?.ariaPressed === 'true',
99
+ alphaUnpressed: afterGamma.find(n => n.alias === 'alpha')?.ariaPressed === 'false',
100
+ onlyOnePressed: afterGamma.filter(n => n.ariaPressed === 'true').length === 1,
101
+ };
102
+ const ok = Object.values(results).every(Boolean);
103
+ console.log(`${ok ? '✅' : '❌'} node a11y:`, JSON.stringify(results),
104
+ `\n before=`, before,
105
+ `\n afterAlpha=`, afterAlpha,
106
+ `\n afterGamma=`, afterGamma);
107
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,85 @@
1
+ /* Round 51 verification: hovered node <g> lifts -2px via CSS transform.
2
+ * - At rest, every node's CSS transform is "none" (or matrix with 0 ty).
3
+ * - On hover, the hovered <g> reports `translateY(-2px)` (matrix(1,0,0,1,0,-2))
4
+ * via getComputedStyle.transform, while siblings stay at identity.
5
+ * - Release: returns to identity.
6
+ * - With prefers-reduced-motion forced, NO lift even on hover.
7
+ */
8
+ import { chromium } from 'playwright';
9
+ import { readFileSync } from 'node:fs';
10
+
11
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
12
+ const browser = await chromium.launch({ headless: true });
13
+
14
+ async function probe(reducedMotion) {
15
+ const ctx = await browser.newContext({
16
+ viewport: { width: 1280, height: 900 },
17
+ reducedMotion: reducedMotion ? 'reduce' : 'no-preference',
18
+ });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try {
22
+ localStorage.setItem('anet-theme', 'cyber');
23
+ localStorage.setItem('anet-topo-layout', 'ring');
24
+ sessionStorage.setItem('anet_v3_auth', '1');
25
+ } 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 sessions = ['alpha', 'beta', 'gamma'].map(a => ({
33
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ }));
36
+ await route.fulfill({ response: r, json: { ...b, sessions } });
37
+ });
38
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
39
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
40
+ const page = await ctx.newPage();
41
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
42
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
43
+ await page.waitForTimeout(500);
44
+
45
+ const readTy = (alias) => page.evaluate(a => {
46
+ const el = document.querySelector(`g[data-node="${a}"]`);
47
+ if (!el) return null;
48
+ const m = getComputedStyle(el).transform;
49
+ if (!m || m === 'none') return 0;
50
+ // matrix(a, b, c, d, tx, ty) — extract ty
51
+ const parts = m.match(/matrix\(([^)]+)\)/);
52
+ if (!parts) return null;
53
+ const arr = parts[1].split(',').map(s => parseFloat(s.trim()));
54
+ return arr[5] ?? null;
55
+ }, alias);
56
+
57
+ const restAlpha = await readTy('alpha');
58
+ const restBeta = await readTy('beta');
59
+ await page.locator('g[data-node="alpha"]').hover({ force: true });
60
+ await page.waitForTimeout(300);
61
+ const hoveredAlpha = await readTy('alpha');
62
+ const hoveredBeta = await readTy('beta');
63
+ await page.mouse.move(10, 10);
64
+ await page.waitForTimeout(300);
65
+ const releasedAlpha = await readTy('alpha');
66
+
67
+ await ctx.close();
68
+ return { restAlpha, restBeta, hoveredAlpha, hoveredBeta, releasedAlpha };
69
+ }
70
+
71
+ const normal = await probe(false);
72
+ const reduced = await probe(true);
73
+ await browser.close();
74
+
75
+ const results = {
76
+ normal_restAtZero: normal.restAlpha === 0 && normal.restBeta === 0,
77
+ normal_hoveredLifts: normal.hoveredAlpha === -2,
78
+ normal_siblingStays: normal.hoveredBeta === 0,
79
+ normal_released: normal.releasedAlpha === 0,
80
+ reduced_noLift: reduced.hoveredAlpha === 0,
81
+ };
82
+ const ok = Object.values(results).every(Boolean);
83
+ console.log(`${ok ? '✅' : '❌'} node hover lift:`, JSON.stringify(results),
84
+ `\n normal=`, normal, `\n reduced=`, reduced);
85
+ process.exit(ok ? 0 : 1);