@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,119 @@
1
+ /* Round 138 verification: recent-signal row typography unifies
2
+ * with the rest of the topology UI.
3
+ *
4
+ * Pre-R138 row text: "alpha -> beta / 12 / hi"
5
+ * Post-R138 row text: "alpha → beta · 12 · hi"
6
+ *
7
+ * Why: filter pills (R119), node tooltips (R98), active-links
8
+ * chip tooltip (R114), edge-badge titles all use the unicode
9
+ * arrow `→` and the data-delimiter ` · `. The recent-signal row
10
+ * was the lone holdout using ASCII `->` and ` / `. R138 brings
11
+ * it in line so the row reads like every other surface.
12
+ *
13
+ * Checks:
14
+ * - Row text contains "→" (unicode arrow)
15
+ * - Row text does NOT contain "->" (ASCII)
16
+ * - Row text uses " · " (middle dot) as separator
17
+ * - Row text does NOT contain " / " (forward slash)
18
+ * - Hot count tspan still works (R127 contract preserved)
19
+ * - Row pin still works (R116 contract preserved)
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', 'working'), mk('gamma', 'idle'),
45
+ ] } });
46
+ });
47
+
48
+ const now = Date.now();
49
+ const mkMsg = (id, from, to, ageMs) => ({
50
+ id, from_alias: from, to_alias: to, content: 'hello',
51
+ network_id: 'default', created_at: new Date(now - ageMs).toISOString(),
52
+ });
53
+ const msgs = [];
54
+ // alpha→beta: 12 msgs (hot)
55
+ for (let i = 0; i < 12; i++) msgs.push(mkMsg(`a${i}`, 'alpha', 'beta', 20000 + i * 500));
56
+ // beta→gamma: 4 msgs (warm)
57
+ for (let i = 0; i < 4; i++) msgs.push(mkMsg(`b${i}`, 'beta', 'gamma', 30000 + i * 500));
58
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
59
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
60
+
61
+ const page = await ctx.newPage();
62
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
63
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
64
+ await page.waitForSelector('[data-recent-row="alpha->beta"]', { timeout: 10000 });
65
+ await page.waitForTimeout(400);
66
+
67
+ const inspect = (key) => page.evaluate((k) => {
68
+ const row = document.querySelector(`[data-recent-row="${k}"]`);
69
+ const text = row?.querySelector('text');
70
+ return {
71
+ fullText: text?.textContent,
72
+ pinned: row?.getAttribute('data-recent-row-pinned'),
73
+ hot: row?.getAttribute('data-recent-row-hot'),
74
+ };
75
+ }, key);
76
+
77
+ const a2b = await inspect('alpha->beta');
78
+ const b2g = await inspect('beta->gamma');
79
+
80
+ // Verify pin still works (R116 invariant)
81
+ await page.locator('[data-recent-row="alpha->beta"]').click();
82
+ await page.waitForTimeout(200);
83
+ const a2bPinned = await inspect('alpha->beta');
84
+ await page.keyboard.press('Escape');
85
+ await page.waitForTimeout(200);
86
+
87
+ await browser.close();
88
+
89
+ // Note: data-recent-row attribute itself still uses "alpha->beta" as
90
+ // the link.key (computed from from + '->' + to upstream). That's
91
+ // internal data, not user-visible text — R138 only touches the
92
+ // rendered <text> content.
93
+ const hasUnicodeArrow = (s) => !!s && s.includes('→');
94
+ const lacksAsciiArrow = (s) => !!s && !s.includes('->');
95
+ const usesMiddleDot = (s) => !!s && s.includes('·');
96
+ const lacksSlash = (s) => !!s && !/\s\/\s/.test(s);
97
+
98
+ const results = {
99
+ a2b_hasUnicodeArrow: hasUnicodeArrow(a2b.fullText),
100
+ a2b_lacksAsciiArrow: lacksAsciiArrow(a2b.fullText),
101
+ a2b_usesMiddleDot: usesMiddleDot(a2b.fullText),
102
+ a2b_lacksSlash: lacksSlash(a2b.fullText),
103
+
104
+ b2g_hasUnicodeArrow: hasUnicodeArrow(b2g.fullText),
105
+ b2g_usesMiddleDot: usesMiddleDot(b2g.fullText),
106
+
107
+ // R127 hot preserved
108
+ a2b_hotAttr: a2b.hot === 'true',
109
+ b2g_notHot: b2g.hot === 'false',
110
+
111
+ // R116 pin preserved
112
+ pin_landed: a2bPinned.pinned === 'true',
113
+ };
114
+ const ok = Object.values(results).every(Boolean);
115
+ console.log(`${ok ? '✅' : '❌'} recent-row typography:`, JSON.stringify(results),
116
+ `\n a2b=`, a2b,
117
+ `\n b2g=`, b2g,
118
+ `\n a2bPinned=`, a2bPinned);
119
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,73 @@
1
+ /* Round 110 verification: recent-signal empty placeholder gains a
2
+ * sub-text hint so the empty state reads as intentional invitation
3
+ * rather than a stuck-loading state. Primary line "no flow yet"
4
+ * stays at the same position semantics (now y=54 instead of y=60
5
+ * to balance the two-line stack within the panel); sub-line at
6
+ * y=68 says "send a message between agents".
7
+ *
8
+ * Verifies:
9
+ * - both lines render when flowLinks is empty
10
+ * - both lines disappear when flowLinks has content
11
+ * - data-recent-signal-empty + data-recent-signal-empty-hint
12
+ * attributes are stable test hooks
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const browser = await chromium.launch({ headless: true });
19
+ const fresh = new Date(Date.now() - 30 * 1000).toISOString();
20
+
21
+ const probe = async (withFlow) => {
22
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1200 } });
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
+ await ctx.route('**/api/hub/status*', async (route) => {
28
+ const r = await route.fetch();
29
+ const b = await r.json();
30
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
31
+ const mk = (alias) => ({
32
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
33
+ network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ });
36
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta')] } });
37
+ });
38
+ const now = Date.now();
39
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: withFlow ? [
40
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'hi',
41
+ network_id: 'default', created_at: new Date(now - 10000).toISOString() },
42
+ ] : [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
46
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
47
+ await page.waitForTimeout(600);
48
+ const data = await page.evaluate(() => ({
49
+ empty: document.querySelector('[data-recent-signal-empty]')?.textContent || null,
50
+ emptyHint: document.querySelector('[data-recent-signal-empty-hint]')?.textContent || null,
51
+ rowCount: document.querySelectorAll('[data-recent-row]').length,
52
+ }));
53
+ await ctx.close();
54
+ return data;
55
+ };
56
+
57
+ const empty = await probe(false);
58
+ const full = await probe(true);
59
+ await browser.close();
60
+
61
+ const results = {
62
+ empty_primaryLineShown: empty.empty === 'no flow yet',
63
+ empty_hintShown: /^send a message between agents$/.test(empty.emptyHint || ''),
64
+ empty_noRows: empty.rowCount === 0,
65
+ full_primaryGone: full.empty === null,
66
+ full_hintGone: full.emptyHint === null,
67
+ full_rowsRendered: full.rowCount >= 1,
68
+ };
69
+ const ok = Object.values(results).every(Boolean);
70
+ console.log(`${ok ? '✅' : '❌'} recent-signal empty hint:`, JSON.stringify(results),
71
+ `\n empty=`, empty,
72
+ `\n full=`, full);
73
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,63 @@
1
+ /* Round 45 verification: recent-signal panel shows "no flow yet" placeholder
2
+ * when flowLinks is empty; renders normal link rows when populated. */
3
+ import { chromium } from 'playwright';
4
+ import { readFileSync } from 'node:fs';
5
+
6
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
7
+ const browser = await chromium.launch({ headless: true });
8
+
9
+ async function probe({ msgs }) {
10
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
11
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
12
+ await ctx.addInitScript(() => {
13
+ try {
14
+ localStorage.setItem('anet-theme', 'cyber');
15
+ localStorage.setItem('anet-topo-layout', 'grid');
16
+ sessionStorage.setItem('anet_v3_auth', '1');
17
+ } catch {}
18
+ });
19
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
20
+ await ctx.route('**/api/hub/status*', async (route) => {
21
+ const r = await route.fetch();
22
+ const b = await r.json();
23
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
24
+ const sessions = ['a', 'b'].map(a => ({
25
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
26
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
27
+ }));
28
+ await route.fulfill({ response: r, json: { ...b, sessions } });
29
+ });
30
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
31
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
32
+ const page = await ctx.newPage();
33
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
34
+ await page.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 30000 });
35
+ await page.waitForTimeout(500);
36
+ const data = await page.evaluate(() => {
37
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
38
+ const empty = svg.querySelector('text[data-recent-signal-empty]');
39
+ // count flow-link description rows in the recent-signal panel (translate(16,16))
40
+ const panel = [...svg.querySelectorAll(':scope > g[transform^="translate(16, 16)"]')][0];
41
+ const linkRows = panel ? [...panel.querySelectorAll('text')].filter(t => /->.*\/.*\//.test(t.textContent || '')).length : 0;
42
+ return { emptyShown: !!empty, emptyText: empty?.textContent || null, linkRows };
43
+ });
44
+ await ctx.close();
45
+ return data;
46
+ }
47
+
48
+ const noFlow = await probe({ msgs: [] });
49
+ const withFlow = await probe({ msgs: [
50
+ { from_alias: 'a', to_alias: 'b', content: 'hello', created_at: new Date().toISOString() },
51
+ ] });
52
+ await browser.close();
53
+
54
+ const results = {
55
+ emptyShowsPlaceholder: noFlow.emptyShown === true && /no flow yet/i.test(noFlow.emptyText || ''),
56
+ emptyHidesLinkRows: noFlow.linkRows === 0,
57
+ populatedHidesPlaceholder: withFlow.emptyShown === false,
58
+ populatedShowsLinkRows: withFlow.linkRows >= 1,
59
+ };
60
+ const ok = Object.values(results).every(Boolean);
61
+ console.log(`${ok ? '✅' : '❌'} recent-signal empty state:`, JSON.stringify(results),
62
+ `\n noFlow=`, noFlow, `\n withFlow=`, withFlow);
63
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,77 @@
1
+ /* Round 36 verification: prefers-reduced-motion disables all SMIL
2
+ * animations in the topology. Round 29 zeroed CSS via media query;
3
+ * SVG <animate> / <animateMotion> elements need JS to opt out and
4
+ * this round wires that via the useReducedMotion hook. */
5
+ import { chromium } from 'playwright';
6
+ import { readFileSync } from 'node:fs';
7
+
8
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
9
+ const browser = await chromium.launch({ headless: true });
10
+
11
+ async function probe({ reduce, layout }) {
12
+ const ctx = await browser.newContext({
13
+ viewport: { width: 1280, height: 1600 },
14
+ reducedMotion: reduce ? 'reduce' : 'no-preference',
15
+ });
16
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
17
+ await ctx.addInitScript((lay) => {
18
+ try {
19
+ localStorage.setItem('anet-theme', 'cyber');
20
+ localStorage.setItem('anet-topo-layout', lay);
21
+ localStorage.setItem('anet-topo-view', JSON.stringify({ zoom: 1, x: 0, y: 0 }));
22
+ sessionStorage.setItem('anet_v3_auth', '1');
23
+ } catch {}
24
+ }, layout);
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ const r = await route.fetch();
28
+ const b = await r.json();
29
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
30
+ // Include a 'working' status so the working pulse path renders.
31
+ const sessions = [
32
+ { alias: 'wkr', status: 'working', network_id: nid, project_dir: null,
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh },
34
+ { alias: 'idl', status: 'idle', 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
+ });
39
+ // Inject a tiny message so a flow link + particle render.
40
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
41
+ { from_alias: 'wkr', to_alias: 'idl', content: 'hi', created_at: fresh },
42
+ ] } }));
43
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
46
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
47
+ await page.waitForTimeout(600);
48
+ // Click a node so the click ripple is invoked (it should NOT render under reduced motion).
49
+ await page.locator('g[data-node="wkr"]').click().catch(() => {});
50
+ await page.waitForTimeout(120);
51
+ const counts = await page.evaluate(() => {
52
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
53
+ return {
54
+ animate: svg.querySelectorAll('animate').length,
55
+ animateMotion: svg.querySelectorAll('animateMotion').length,
56
+ };
57
+ });
58
+ await ctx.close();
59
+ return counts;
60
+ }
61
+
62
+ // Baseline (no reduce) — many SMIL elements expected.
63
+ const normalRing = await probe({ reduce: false, layout: 'ring' });
64
+ // Reduced motion — every SMIL element should be gone.
65
+ const reducedRing = await probe({ reduce: true, layout: 'ring' });
66
+ const reducedGrid = await probe({ reduce: true, layout: 'grid' });
67
+
68
+ await browser.close();
69
+ const results = {
70
+ baselineHasAnimations: normalRing.animate + normalRing.animateMotion > 0,
71
+ reducedRingDropsAll: reducedRing.animate === 0 && reducedRing.animateMotion === 0,
72
+ reducedGridDropsAll: reducedGrid.animate === 0 && reducedGrid.animateMotion === 0,
73
+ };
74
+ const ok = Object.values(results).every(Boolean);
75
+ console.log(`${ok ? '✅' : '❌'} reduced motion:`, JSON.stringify(results),
76
+ `\n normalRing=`, normalRing, `\n reducedRing=`, reducedRing, `\n reducedGrid=`, reducedGrid);
77
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,120 @@
1
+ /* Round 184 verification: chrome reset button gets a one-shot
2
+ * icon spin on click — visual confirmation that the click
3
+ * registered.
4
+ *
5
+ * Pre-R184 the reset button triggered R168 smoothView crossfade
6
+ * on the canvas but the button itself had no on-button feedback.
7
+ * R184 arms resetSpinning for 460ms on click; the SVG icon
8
+ * picks up `.anet-reset-spin` CSS animation (450ms ease-out,
9
+ * -360°).
10
+ *
11
+ * Test:
12
+ * 1. Idle: data-topo-chrome-reset-spinning='false',
13
+ * SVG icon has NO 'anet-reset-spin' className
14
+ * 2. Click reset → flag='true' within 50ms,
15
+ * SVG icon has 'anet-reset-spin' className
16
+ * 3. Wait 500ms → flag='false', className removed
17
+ * 4. CSS animation keyframe 'anet-reset-spin' exists in stylesheet
18
+ * (rotates -360deg)
19
+ */
20
+ import { chromium } from 'playwright';
21
+ import { readFileSync } from 'node:fs';
22
+
23
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
24
+ const browser = await chromium.launch({ headless: true });
25
+
26
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
27
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
28
+ await ctx.addInitScript(() => {
29
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
30
+ });
31
+
32
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
33
+ await ctx.route('**/api/hub/status*', async (route) => {
34
+ const r = await route.fetch();
35
+ const b = await r.json();
36
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
37
+ const mk = (alias) => ({
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'), mk('gamma')] } });
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 === 3, { timeout: 30000 });
50
+ await page.waitForSelector('[data-topo-chrome-reset]', { timeout: 10000 });
51
+ await page.waitForTimeout(400);
52
+
53
+ const probe = () => page.evaluate(() => {
54
+ const btn = document.querySelector('[data-topo-chrome-reset]');
55
+ const icon = document.querySelector('[data-topo-chrome-reset-icon]');
56
+ return {
57
+ btnSpinningAttr: btn?.getAttribute('data-topo-chrome-reset-spinning'),
58
+ iconClass: icon?.getAttribute('class') || '',
59
+ };
60
+ });
61
+
62
+ const idle = await probe();
63
+
64
+ // First zoom in so reset has visible effect (not strictly needed for the
65
+ // spin test, but exercises the resetView path alongside the spin)
66
+ await page.keyboard.press('+');
67
+ await page.keyboard.press('+');
68
+ await page.waitForTimeout(200);
69
+
70
+ // Click reset button — spin should arm
71
+ await page.locator('[data-topo-chrome-reset]').click();
72
+ await page.waitForTimeout(50);
73
+ const duringSpin = await probe();
74
+
75
+ // Wait past the 460ms window
76
+ await page.waitForTimeout(500);
77
+ const afterSpin = await probe();
78
+
79
+ // Verify the CSS keyframe exists in the stylesheet (rotates negatively)
80
+ const cssCheck = await page.evaluate(() => {
81
+ for (const sheet of document.styleSheets) {
82
+ let rules;
83
+ try { rules = sheet.cssRules; } catch { continue; }
84
+ if (!rules) continue;
85
+ for (const rule of rules) {
86
+ // CSSKeyframesRule has .name and .cssText listing keyframes
87
+ if (rule.type === 7 /* KEYFRAMES_RULE */ && rule.name === 'anet-reset-spin') {
88
+ return {
89
+ found: true,
90
+ cssText: rule.cssText,
91
+ };
92
+ }
93
+ }
94
+ }
95
+ return { found: false, cssText: '' };
96
+ });
97
+
98
+ await browser.close();
99
+
100
+ const results = {
101
+ idle_attr_false: idle.btnSpinningAttr === 'false',
102
+ idle_no_spin_class: !idle.iconClass.includes('anet-reset-spin'),
103
+
104
+ spin_attr_true: duringSpin.btnSpinningAttr === 'true',
105
+ spin_has_class: duringSpin.iconClass.includes('anet-reset-spin'),
106
+
107
+ after_attr_false: afterSpin.btnSpinningAttr === 'false',
108
+ after_no_spin_class: !afterSpin.iconClass.includes('anet-reset-spin'),
109
+
110
+ keyframe_found: cssCheck.found,
111
+ keyframe_negative_360: cssCheck.cssText.includes('-360deg') ||
112
+ cssCheck.cssText.includes('rotate(-360'),
113
+ };
114
+ const ok = Object.values(results).every(Boolean);
115
+ console.log(`${ok ? '✅' : '❌'} reset-spin:`, JSON.stringify(results),
116
+ `\n idle:`, idle,
117
+ `\n duringSpin:`, duringSpin,
118
+ `\n afterSpin:`, afterSpin,
119
+ `\n keyframe:`, cssCheck);
120
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,114 @@
1
+ /* Round 208 verification: runtime badge (CLI/SDK/HTTP icon at the
2
+ * bottom-right of each node avatar) grows r + thickens stroke on
3
+ * parent-node hover. Completes the micro-lift radius-axis family:
4
+ * R177 hub ring r 14 → 17 on hub-hover
5
+ * R197 legend swatch r 5.5 → 7 on row-hover/pin
6
+ * R208 runtime badge r 7 → 8 on node-hover (online)
7
+ * (r 5.5 → 6.5 offline)
8
+ *
9
+ * Test (cyber dark, 4 working sessions with runtime='cli-claude-code'
10
+ * which maps to a runtime identity → badge renders):
11
+ * 1. 4 [data-runtime-badge] badges present (one per node).
12
+ * 2. Idle: all data-runtime-badge-active='false', computed r ≈ 7,
13
+ * strokeWidth ≈ 1.5.
14
+ * 3. Transition includes 'r 150ms' AND 'stroke-width 150ms'.
15
+ * 4. Hover g[data-node="alpha"] → wait past 180ms.
16
+ * 5. alpha badge: active='true', r ≈ 8, strokeWidth ≈ 2.
17
+ * 6. Other (non-hovered) badges stay active='false', r ≈ 7,
18
+ * strokeWidth ≈ 1.5.
19
+ * 7. Move away — alpha badge back to 'false', r ≈ 7.
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: 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
+
33
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
34
+ await ctx.route('**/api/hub/status*', async (route) => {
35
+ const r = await route.fetch();
36
+ const b = await r.json();
37
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
38
+ const mk = (alias) => ({
39
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
40
+ network_id: nid, project_dir: null,
41
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
42
+ });
43
+ await route.fulfill({ response: r, json: { ...b, sessions: [
44
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
45
+ ] } });
46
+ });
47
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
48
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
49
+
50
+ const page = await ctx.newPage();
51
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
52
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
53
+ // SVG <circle> doesn't pass Playwright's default visibility check the
54
+ // same way HTML elements do — wait for DOM attachment instead.
55
+ await page.waitForSelector('[data-runtime-badge]', { timeout: 10000, state: 'attached' });
56
+ await page.waitForTimeout(300);
57
+
58
+ const probeAll = async () => page.evaluate(() => {
59
+ return [...document.querySelectorAll('[data-runtime-badge]')].map(el => {
60
+ const cs = getComputedStyle(el);
61
+ return {
62
+ alias: el.getAttribute('data-runtime-badge'),
63
+ active: el.getAttribute('data-runtime-badge-active'),
64
+ r: parseFloat(cs.r),
65
+ strokeWidth: parseFloat(cs.strokeWidth),
66
+ transition: el.style.transition,
67
+ };
68
+ });
69
+ });
70
+
71
+ const idle = await probeAll();
72
+
73
+ await page.hover('g[data-node="alpha"]');
74
+ await page.waitForTimeout(200);
75
+ const hovered = await probeAll();
76
+
77
+ await page.mouse.move(0, 0);
78
+ await page.waitForTimeout(200);
79
+ const settled = await probeAll();
80
+
81
+ await browser.close();
82
+
83
+ const idleAlpha = idle.find(b => b.alias === 'alpha');
84
+ const idleBeta = idle.find(b => b.alias === 'beta');
85
+ const hoverAlpha = hovered.find(b => b.alias === 'alpha');
86
+ const hoverBeta = hovered.find(b => b.alias === 'beta');
87
+ const settledAlpha = settled.find(b => b.alias === 'alpha');
88
+
89
+ const near = (got, want, tol = 0.4) => Math.abs(got - want) < tol;
90
+
91
+ const results = {
92
+ four_badges_present: idle.length === 4,
93
+ idle_all_inactive: idle.every(b => b.active === 'false'),
94
+ idle_all_r_7: idle.every(b => near(b.r, 7)),
95
+ idle_all_stroke_1p5: idle.every(b => near(b.strokeWidth, 1.5)),
96
+ transition_has_r: idle.every(b => /\br\s+(?:150ms|0\.15s)/.test(b.transition || '')),
97
+ transition_has_stroke_width: idle.every(b => /stroke-width\s+(?:150ms|0\.15s)/.test(b.transition || '')),
98
+
99
+ hover_alpha_active: hoverAlpha?.active === 'true',
100
+ hover_alpha_r_grown: near(hoverAlpha?.r ?? 0, 8),
101
+ hover_alpha_stroke_thicker: near(hoverAlpha?.strokeWidth ?? 0, 2),
102
+ hover_beta_stays_inactive: hoverBeta?.active === 'false',
103
+ hover_beta_r_unchanged: near(hoverBeta?.r ?? 0, 7),
104
+ hover_beta_stroke_unchanged: near(hoverBeta?.strokeWidth ?? 0, 1.5),
105
+
106
+ settled_alpha_inactive: settledAlpha?.active === 'false',
107
+ settled_alpha_r_back: near(settledAlpha?.r ?? 0, 7),
108
+ };
109
+ const ok = Object.values(results).every(Boolean);
110
+ console.log(`${ok ? '✅' : '❌'} runtime badge grow:`, JSON.stringify(results),
111
+ `\n idle:`, idle,
112
+ `\n hovered:`, hovered,
113
+ `\n settled:`, settled);
114
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,93 @@
1
+ /* Round 280 verification: backdrop spokes retired (R93 + R240 family)
2
+ * — 减法 cut #6.
3
+ *
4
+ * Pre-R280 the canvas rendered 6 <line> elements at angles 0, 30, 60,
5
+ * 90, 120, 150° through hub center — 12 rays of a "radar" star
6
+ * pattern, stroke opacity 0.18 (cyber) / 0.35 (light), tint shift to
7
+ * pal.legendAccent when any pin is active (R240).
8
+ *
9
+ * The radial-gradient backdrop (`topo-radar`) already provides soft
10
+ * hub-centered glow; the explicit line spokes were decorative density
11
+ * without structural signal — the topology IS hub-and-spoke, the
12
+ * actual hub-to-node connecting strokes already render the structure.
13
+ *
14
+ * Post-R280: spokes block gated with `false &&`. The 6 lines no
15
+ * longer render; radial-gradient backdrop remains.
16
+ *
17
+ * Test scope:
18
+ * 1. No element matches `[data-topo-spoke-angle]`.
19
+ * 2. R278 working halo <animate> count still 0 (regression).
20
+ * 3. R279 ping/pulse still absent (regression).
21
+ * 4. R277 legend height 88 (regression).
22
+ * 5. R275 freshness chip absent (regression).
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
+ 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
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
35
+ await ctx.route('**/api/hub/status*', async (route) => {
36
+ const r = await route.fetch();
37
+ const b = await r.json();
38
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
39
+ const mk = (alias) => ({
40
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
41
+ network_id: nid, project_dir: null,
42
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
43
+ });
44
+ await route.fulfill({ response: r, json: { ...b, sessions: [
45
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
46
+ ] } });
47
+ });
48
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
49
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
50
+
51
+ const page = await ctx.newPage();
52
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
53
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
54
+ await page.waitForSelector('[data-topo-panel="legend"]', { timeout: 10000 });
55
+ await page.waitForTimeout(300);
56
+
57
+ const probe = await page.evaluate(() => {
58
+ const spokes = document.querySelectorAll('[data-topo-spoke-angle]');
59
+ const ping = document.querySelectorAll('[data-arrival-ping]');
60
+ const pulse = document.querySelectorAll('[data-dispatch-pulse]');
61
+ const freshnessChip = document.querySelector('[data-freshness-chip]');
62
+ const legendG = document.querySelector('[data-topo-panel="legend"]');
63
+ const legendH = legendG?.querySelector('rect')?.getAttribute('height') ?? null;
64
+ const halos = document.querySelectorAll('[data-node-halo-breath="on"]');
65
+ let haloAnimateCount = 0;
66
+ halos.forEach((h) => { haloAnimateCount += h.querySelectorAll('animate').length; });
67
+ return {
68
+ spokeCount: spokes.length,
69
+ pingCount: ping.length,
70
+ pulseCount: pulse.length,
71
+ freshnessPresent: freshnessChip !== null,
72
+ legendHeight: legendH,
73
+ haloAnimateCount,
74
+ };
75
+ });
76
+ await browser.close();
77
+
78
+ const results = {
79
+ spokes_absent: probe.spokeCount === 0,
80
+ r279_ping_absent: probe.pingCount === 0,
81
+ r279_pulse_absent: probe.pulseCount === 0,
82
+ r275_freshness_absent: probe.freshnessPresent === false,
83
+ r277_legend_height_88: probe.legendHeight === '88',
84
+ r278_halo_animate_count_zero: probe.haloAnimateCount === 0,
85
+ };
86
+ const ok = Object.values(results).every(Boolean);
87
+ console.log(`${ok ? '✅' : '❌'} backdrop spokes retired:`, JSON.stringify(results),
88
+ '\n spoke count (expect 0):', probe.spokeCount,
89
+ '\n ping/pulse:', probe.pingCount, probe.pulseCount,
90
+ '\n freshness absent:', !probe.freshnessPresent,
91
+ '\n legend height:', probe.legendHeight,
92
+ '\n halo animate count (expect 0):', probe.haloAnimateCount);
93
+ process.exit(ok ? 0 : 1);