@sleep2agi/agent-network-dashboard 0.4.6-preview.26 → 0.4.6-preview.261

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 (459) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +41 -41
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/routes-manifest.json +6 -0
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +2 -2
  17. package/.next/server/app/_not-found.rsc +12 -12
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  19. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  20. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  24. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  25. package/.next/server/app/admin.html +2 -2
  26. package/.next/server/app/admin.rsc +14 -14
  27. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  28. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  29. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  30. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  31. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  32. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  33. package/.next/server/app/api/hub/servers/route/app-paths-manifest.json +3 -0
  34. package/.next/server/app/api/hub/servers/route/build-manifest.json +9 -0
  35. package/.next/server/app/api/hub/servers/route/server-reference-manifest.json +4 -0
  36. package/.next/server/app/api/hub/servers/route.js +7 -0
  37. package/.next/server/app/api/hub/servers/route.js.map +5 -0
  38. package/.next/server/app/api/hub/servers/route.js.nft.json +1 -0
  39. package/.next/server/app/api/hub/servers/route_client-reference-manifest.js +3 -0
  40. package/.next/server/app/index.html +2 -2
  41. package/.next/server/app/index.rsc +14 -14
  42. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  43. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  44. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  45. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  46. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  47. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/login.html +2 -2
  49. package/.next/server/app/login.rsc +14 -14
  50. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  55. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  56. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/logs.html +2 -2
  58. package/.next/server/app/logs.rsc +14 -14
  59. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  60. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  61. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  62. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  64. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  65. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app/messages.html +2 -2
  67. package/.next/server/app/messages.rsc +14 -14
  68. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  69. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  70. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  71. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  73. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  74. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/node.html +2 -2
  76. package/.next/server/app/node.rsc +14 -14
  77. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  78. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  79. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  80. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  82. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  83. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/nodes.html +2 -2
  85. package/.next/server/app/nodes.rsc +14 -14
  86. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  87. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  88. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  89. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  90. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  91. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  92. package/.next/server/app/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/server-logs.html +2 -2
  95. package/.next/server/app/server-logs.rsc +14 -14
  96. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  97. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  98. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  99. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  100. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  101. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  102. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  103. package/.next/server/app/settings/networks.html +2 -2
  104. package/.next/server/app/settings/networks.rsc +14 -14
  105. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  106. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  107. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  108. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  109. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  110. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  111. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  112. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  113. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  114. package/.next/server/app/settings/tokens.html +2 -2
  115. package/.next/server/app/settings/tokens.rsc +14 -14
  116. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  117. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  118. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  119. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  120. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  121. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  122. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  123. package/.next/server/app/settings.html +2 -2
  124. package/.next/server/app/settings.rsc +14 -14
  125. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  126. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  127. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  128. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  130. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  131. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  132. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  133. package/.next/server/app/tasks.html +2 -2
  134. package/.next/server/app/tasks.rsc +14 -14
  135. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  136. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  137. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  138. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  139. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  140. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  141. package/.next/server/app-paths-manifest.json +1 -0
  142. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js +3 -0
  143. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js.map +1 -0
  144. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +3 -0
  145. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -0
  146. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
  147. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
  148. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  149. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  150. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
  151. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
  152. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -1
  153. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  154. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  155. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  156. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +1 -1
  157. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -1
  158. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  159. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  160. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
  161. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
  162. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
  163. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
  164. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
  165. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
  166. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
  167. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
  168. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
  169. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
  170. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
  171. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
  172. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  173. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  174. package/.next/server/middleware-build-manifest.js +3 -3
  175. package/.next/server/pages/404.html +2 -2
  176. package/.next/server/pages/500.html +1 -1
  177. package/.next/server/server-reference-manifest.js +1 -1
  178. package/.next/server/server-reference-manifest.json +1 -1
  179. package/.next/static/chunks/0-mpa_947ipeq.js +1 -0
  180. package/.next/static/chunks/00.pi0.850kxg.js +1 -0
  181. package/.next/static/chunks/01lmbsd37fybu.js +1 -0
  182. package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
  183. package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
  184. package/.next/static/chunks/05uk96gc~9mni.js +1 -0
  185. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  186. package/.next/static/chunks/08y27uzl7w6bx.css +2 -0
  187. package/.next/static/chunks/0ggj5ummw2a84.js +7 -0
  188. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  189. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  190. package/.next/static/chunks/0s64vw8kd5rn7.js +1 -0
  191. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  192. package/.next/static/chunks/11drzen-wfaop.js +4 -0
  193. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  194. package/.next/trace +2 -2
  195. package/.next/trace-build +1 -1
  196. package/.next/types/routes.d.ts +2 -1
  197. package/.next/types/validator.ts +9 -0
  198. package/app/api/hub/servers/route.ts +39 -0
  199. package/app/components/ChatPopover.tsx +38 -2
  200. package/app/components/CommandPalette.tsx +107 -0
  201. package/app/components/HelpOverlay.tsx +126 -0
  202. package/app/components/ServersDrawer.tsx +64 -27
  203. package/app/components/TopoGraph.tsx +5291 -237
  204. package/app/components/utils.ts +8 -7
  205. package/app/globals.css +110 -5
  206. package/app/lib/time.ts +36 -0
  207. package/package.json +2 -2
  208. package/scripts/help-overlay-topo-test.mjs +44 -0
  209. package/scripts/servers-drawer-live-test.mjs +96 -0
  210. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  211. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  212. package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
  213. package/scripts/topo-active-links-chip-self-hover-test.mjs +171 -0
  214. package/scripts/topo-active-links-chip-test.mjs +73 -0
  215. package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
  216. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  217. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  218. package/scripts/topo-active-pulse-ease-test.mjs +120 -0
  219. package/scripts/topo-arrival-ping-test.mjs +87 -0
  220. package/scripts/topo-auto-fit-test.mjs +73 -0
  221. package/scripts/topo-canvas-aria-test.mjs +108 -0
  222. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  223. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  224. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  225. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  226. package/scripts/topo-chip-focus-test.mjs +95 -0
  227. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  228. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  229. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  230. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  231. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  232. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  233. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  234. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  235. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  236. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  237. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  238. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  239. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  240. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  241. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  242. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  243. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  244. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  245. package/scripts/topo-dispatch-pulse-test.mjs +83 -0
  246. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  247. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  248. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  249. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  250. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  251. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  252. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  253. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  254. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  255. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  256. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  257. package/scripts/topo-edge-fadein-test.mjs +106 -0
  258. package/scripts/topo-edge-focus-test.mjs +113 -0
  259. package/scripts/topo-edge-hover-test.mjs +93 -0
  260. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  261. package/scripts/topo-edge-pill-test.mjs +105 -0
  262. package/scripts/topo-edge-smil-sync-test.mjs +148 -0
  263. package/scripts/topo-edge-smil-theme-ease-test.mjs +97 -0
  264. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  265. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  266. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  267. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  268. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  269. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  270. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  271. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  272. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  273. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  274. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  275. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  276. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  277. package/scripts/topo-fit-key-test.mjs +106 -0
  278. package/scripts/topo-freshness-chip-test.mjs +74 -0
  279. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  280. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  281. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  282. package/scripts/topo-group-count-test.mjs +68 -0
  283. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  284. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  285. package/scripts/topo-group-label-hover-test.mjs +119 -0
  286. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  287. package/scripts/topo-group-label-tint-test.mjs +99 -0
  288. package/scripts/topo-group-label-title-test.mjs +83 -0
  289. package/scripts/topo-group-pin-test.mjs +119 -0
  290. package/scripts/topo-group-pip-fadein-test.mjs +100 -0
  291. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  292. package/scripts/topo-group-status-pips-test.mjs +103 -0
  293. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  294. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  295. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  296. package/scripts/topo-groupbox-live-test.mjs +100 -0
  297. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  298. package/scripts/topo-groupbox-test.mjs +4 -2
  299. package/scripts/topo-halo-breath-ease-test.mjs +92 -0
  300. package/scripts/topo-halo-breath-stagger-test.mjs +89 -0
  301. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  302. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  303. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  304. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  305. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  306. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  307. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  308. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  309. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  310. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  311. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  312. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  313. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  314. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  315. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  316. package/scripts/topo-hub-a11y-test.mjs +112 -0
  317. package/scripts/topo-hub-busyness-test.mjs +100 -0
  318. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  319. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  320. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  321. package/scripts/topo-hub-fadein-test.mjs +88 -0
  322. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  323. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  324. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  325. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  326. package/scripts/topo-hub-title-test.mjs +93 -0
  327. package/scripts/topo-hub-working-count-test.mjs +99 -0
  328. package/scripts/topo-ingroup-edge-test.mjs +103 -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-key-test.mjs +91 -0
  333. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  334. package/scripts/topo-layout-toggle-test.mjs +131 -0
  335. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  336. package/scripts/topo-legend-count-test.mjs +88 -0
  337. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  338. package/scripts/topo-legend-hover-test.mjs +113 -0
  339. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  340. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  341. package/scripts/topo-legend-pin-test.mjs +128 -0
  342. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  343. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  344. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  345. package/scripts/topo-legend-row-title-test.mjs +102 -0
  346. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  347. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  348. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  349. package/scripts/topo-minimap-test.mjs +68 -0
  350. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  351. package/scripts/topo-mobile-chips-test.mjs +103 -0
  352. package/scripts/topo-node-a11y-test.mjs +107 -0
  353. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  354. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  355. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  356. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  357. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  358. package/scripts/topo-node-title-peers-test.mjs +107 -0
  359. package/scripts/topo-node-title-test.mjs +101 -0
  360. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  361. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  362. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  363. package/scripts/topo-orbit-busyness-test.mjs +96 -0
  364. package/scripts/topo-orbit-opacity-bucket-test.mjs +124 -0
  365. package/scripts/topo-overlap-test.mjs +32 -5
  366. package/scripts/topo-pan-cursor-test.mjs +62 -0
  367. package/scripts/topo-panel-elevation-test.mjs +76 -0
  368. package/scripts/topo-panel-fadein-test.mjs +102 -0
  369. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  370. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  371. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  372. package/scripts/topo-particle-stagger-test.mjs +82 -0
  373. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  374. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  375. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  376. package/scripts/topo-pin-intersection-test.mjs +121 -0
  377. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  378. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  379. package/scripts/topo-pin-persistence-test.mjs +105 -0
  380. package/scripts/topo-ping-pulse-ease-test.mjs +96 -0
  381. package/scripts/topo-popover-meta-test.mjs +78 -0
  382. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  383. package/scripts/topo-pressure-bar-test.mjs +69 -0
  384. package/scripts/topo-pressure-hover-test.mjs +123 -0
  385. package/scripts/topo-pressure-pin-test.mjs +119 -0
  386. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  387. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  388. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  389. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  390. package/scripts/topo-pulse-rate-test.mjs +71 -0
  391. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  392. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  393. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  394. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  395. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  396. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  397. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  398. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  399. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  400. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  401. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  402. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  403. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  404. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  405. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  406. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  407. package/scripts/topo-recent-row-title-test.mjs +112 -0
  408. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  409. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  410. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  411. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  412. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  413. package/scripts/topo-reduced-motion-test.mjs +77 -0
  414. package/scripts/topo-reset-spin-test.mjs +120 -0
  415. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  416. package/scripts/topo-spoke-busyness-test.mjs +110 -0
  417. package/scripts/topo-spoke-tint-test.mjs +96 -0
  418. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  419. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  420. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  421. package/scripts/topo-svg-focus-test.mjs +112 -0
  422. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  423. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  424. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  425. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  426. package/scripts/topo-tier-ring-test.mjs +69 -0
  427. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  428. package/scripts/topo-tier-stagger-test.mjs +89 -0
  429. package/scripts/topo-tz-ghost-test.mjs +69 -0
  430. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  431. package/scripts/topo-vendor-chip-self-hover-test.mjs +130 -0
  432. package/scripts/topo-vendor-chip-tabular-test.mjs +89 -0
  433. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  434. package/scripts/topo-vendor-pill-test.mjs +125 -0
  435. package/scripts/topo-vendor-pin-test.mjs +125 -0
  436. package/scripts/topo-view-smooth-test.mjs +131 -0
  437. package/scripts/topo-wheel-zoom-test.mjs +79 -0
  438. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  439. package/scripts/topo-working-halo-breath-test.mjs +86 -0
  440. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  441. package/scripts/topo-zoom-keys-test.mjs +89 -0
  442. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  443. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  444. package/.next/static/chunks/09nd.t_hd.y6t.css +0 -2
  445. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  446. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  447. package/.next/static/chunks/0fqy_0wj_1xa7.js +0 -7
  448. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  449. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  450. package/.next/static/chunks/0jzqhqbydbrm~.js +0 -1
  451. package/.next/static/chunks/0lvk7f2_5y8b3.js +0 -1
  452. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  453. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  454. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  455. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  456. package/.next/static/chunks/12maqvd_4t3sh.js +0 -1
  457. /package/.next/static/{iC9JNLo0IdNSaeynEneGT → -JO9tjmCkno3inrj-XBxj}/_buildManifest.js +0 -0
  458. /package/.next/static/{iC9JNLo0IdNSaeynEneGT → -JO9tjmCkno3inrj-XBxj}/_clientMiddlewareManifest.js +0 -0
  459. /package/.next/static/{iC9JNLo0IdNSaeynEneGT → -JO9tjmCkno3inrj-XBxj}/_ssgManifest.js +0 -0
@@ -0,0 +1,98 @@
1
+ /* Round 249 verification: chrome-pop click animation extends from
2
+ * the existing zoom-in/zoom-out pair to ring/grid layout toggle +
3
+ * fullscreen buttons. Whole chrome strip now speaks one click
4
+ * vocabulary instead of zoom-only-pops + silent-everything-else.
5
+ *
6
+ * Per button, on click:
7
+ * data-topo-chrome-{key}-popping flips 'false' → 'true' for ~240ms
8
+ * className gains 'anet-chrome-pop' for the same window
9
+ *
10
+ * Test scope:
11
+ * - Ring layout button: click → data-topo-chrome-layout-ring-popping='true' (within 100ms)
12
+ * - Grid layout button: click → data-topo-chrome-layout-grid-popping='true'
13
+ * - Fullscreen button: click → data-topo-chrome-fullscreen-popping='true'
14
+ *
15
+ * Hard-to-test trap: chromePopping is transient (240ms TTL). Probe
16
+ * immediately after Playwright click resolves. Wait full 240ms after
17
+ * for the second probe — must be 'false' again.
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) => ({
35
+ alias, status: 'working', model: 'claude-opus-4', 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'), mk('beta'), mk('gamma'), mk('delta'),
41
+ ] } });
42
+ });
43
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
44
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
45
+
46
+ const page = await ctx.newPage();
47
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
48
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
49
+ await page.waitForSelector('[data-topo-chrome-layout="ring"]', { timeout: 10000 });
50
+ await page.waitForSelector('[data-topo-chrome-fullscreen]', { timeout: 10000 });
51
+ await page.waitForTimeout(300);
52
+
53
+ const testButton = async (clickSelector, poppingAttr, label) => {
54
+ // Click + immediately read state. The chromePopping setState is
55
+ // synchronous within the click handler, so the DOM should reflect
56
+ // 'true' on the next React commit (very soon after click resolves).
57
+ await page.locator(clickSelector).click();
58
+ // Probe quickly — within the 240ms window
59
+ await page.waitForTimeout(50);
60
+ const duringPop = await page.locator(clickSelector).getAttribute(poppingAttr);
61
+ const classDuring = await page.locator(clickSelector).getAttribute('class');
62
+ // Wait for the pop to clear
63
+ await page.waitForTimeout(300);
64
+ const afterPop = await page.locator(clickSelector).getAttribute(poppingAttr);
65
+ const classAfter = await page.locator(clickSelector).getAttribute('class');
66
+ return {
67
+ label,
68
+ duringAttr: duringPop,
69
+ duringClass: /anet-chrome-pop/.test(classDuring || ''),
70
+ afterAttr: afterPop,
71
+ afterClass: /anet-chrome-pop/.test(classAfter || ''),
72
+ };
73
+ };
74
+
75
+ const ring = await testButton('[data-topo-chrome-layout="ring"]', 'data-topo-chrome-layout-ring-popping', 'ring');
76
+ const grid = await testButton('[data-topo-chrome-layout="grid"]', 'data-topo-chrome-layout-grid-popping', 'grid');
77
+ // For fullscreen, browser context may not honour requestFullscreen in
78
+ // headless mode — wrap in try/catch on the page side. Test only the
79
+ // data attr + className.
80
+ const fullscreen = await testButton('[data-topo-chrome-fullscreen]', 'data-topo-chrome-fullscreen-popping', 'fullscreen');
81
+
82
+ await browser.close();
83
+
84
+ const ok_each = (b) =>
85
+ b.duringAttr === 'true' && b.duringClass === true &&
86
+ b.afterAttr === 'false' && b.afterClass === false;
87
+
88
+ const results = {
89
+ ring_pops_on_click: ok_each(ring),
90
+ grid_pops_on_click: ok_each(grid),
91
+ fullscreen_pops_on_click: ok_each(fullscreen),
92
+ };
93
+ const ok = Object.values(results).every(Boolean);
94
+ console.log(`${ok ? '✅' : '❌'} chrome-pop extension:`, JSON.stringify(results),
95
+ '\n ring: ', ring,
96
+ '\n grid: ', grid,
97
+ '\n fullscreen:', fullscreen);
98
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,88 @@
1
+ /* Round 250 verification: nodeSize S/M/L buttons close the chrome-
2
+ * pop click-feedback family. Every clickable chrome button now fires
3
+ * the R186 .anet-chrome-pop 220ms scale-pulse on release.
4
+ *
5
+ * R171 nodeSizeSwitching canvas crossfade still masks the radius
6
+ * change at the global scope; R250 chrome-pop adds the LOCAL
7
+ * button-level click confirmation.
8
+ *
9
+ * Test scope: click each of S/M/L → within 50ms data-topo-chrome-
10
+ * nodesize-popping='true' + className contains 'anet-chrome-pop'.
11
+ * After 300ms both clear.
12
+ */
13
+ import { chromium } from 'playwright';
14
+ import { readFileSync } from 'node:fs';
15
+
16
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
17
+ const browser = await chromium.launch({ headless: true });
18
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
22
+ });
23
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
24
+ await ctx.route('**/api/hub/status*', async (route) => {
25
+ const r = await route.fetch();
26
+ const b = await r.json();
27
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
28
+ const mk = (alias) => ({
29
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
30
+ network_id: nid, project_dir: null,
31
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
32
+ });
33
+ await route.fulfill({ response: r, json: { ...b, sessions: [
34
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
35
+ ] } });
36
+ });
37
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
38
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
39
+
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 === 4, { timeout: 30000 });
43
+ await page.waitForSelector('[data-topo-chrome-nodesize="S"]', { timeout: 10000 });
44
+ await page.waitForTimeout(300);
45
+
46
+ const testButton = async (size) => {
47
+ const sel = `[data-topo-chrome-nodesize="${size}"]`;
48
+ await page.locator(sel).click();
49
+ await page.waitForTimeout(50);
50
+ const duringAttr = await page.locator(sel).getAttribute('data-topo-chrome-nodesize-popping');
51
+ const duringClass = await page.locator(sel).getAttribute('class');
52
+ // Wait past the 240ms TTL
53
+ await page.waitForTimeout(300);
54
+ const afterAttr = await page.locator(sel).getAttribute('data-topo-chrome-nodesize-popping');
55
+ const afterClass = await page.locator(sel).getAttribute('class');
56
+ return {
57
+ size,
58
+ duringAttr,
59
+ duringClassHasPop: /anet-chrome-pop/.test(duringClass || ''),
60
+ afterAttr,
61
+ afterClassHasPop: /anet-chrome-pop/.test(afterClass || ''),
62
+ };
63
+ };
64
+
65
+ // Click each in order S → L → M to mix up state
66
+ const small = await testButton('S');
67
+ const large = await testButton('L');
68
+ const medium = await testButton('M');
69
+
70
+ await browser.close();
71
+
72
+ const ok_each = (b) =>
73
+ b.duringAttr === 'true' &&
74
+ b.duringClassHasPop === true &&
75
+ b.afterAttr === 'false' &&
76
+ b.afterClassHasPop === false;
77
+
78
+ const results = {
79
+ small_pops: ok_each(small),
80
+ medium_pops: ok_each(medium),
81
+ large_pops: ok_each(large),
82
+ };
83
+ const ok = Object.values(results).every(Boolean);
84
+ console.log(`${ok ? '✅' : '❌'} chrome-pop nodesize:`, JSON.stringify(results),
85
+ '\n small: ', small,
86
+ '\n medium:', medium,
87
+ '\n large: ', large);
88
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,135 @@
1
+ /* Round 196 verification: every chrome button picks up an active:
2
+ * (pressed) state for tactile click feedback. Bridges the gap between
3
+ * mouse-down and the existing release-pop animations:
4
+ * R184 reset-spin (0.45s rotation on release)
5
+ * R186 +/− icon pop (0.22s scale on release)
6
+ * R192 readout pop (same 0.22s on release)
7
+ * Pre-R196 mouse-down → release was instant; nothing acknowledged the
8
+ * held-down phase. R196 lays a darker bg under the cursor while held.
9
+ *
10
+ * Buttons covered:
11
+ * layout Ring / Grid (active variant: cyan-500/25, base: cyan-500/15)
12
+ * nodeSize S / M / L (active: cyan-500/25, base: white/10)
13
+ * zoom-out / zoom-in (white/10)
14
+ * reset (white/10)
15
+ * fullscreen (active: cyan-500/25, base: white/10)
16
+ *
17
+ * Test:
18
+ * 1. Static markup check — each button's class string contains the
19
+ * 'active:bg-...' modifier(s).
20
+ * 2. Runtime check on zoom-out: mouse.down on the button, getComputed-
21
+ * Style snapshot — backgroundColor differs from the idle/hover bg.
22
+ * Mouse.up + move off — back to idle bg.
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
+ // Collect all chrome button className strings
60
+ const markup = await page.evaluate(() => ({
61
+ layoutRing: document.querySelector('[data-topo-chrome-layout="ring"]')?.getAttribute('class') || '',
62
+ layoutGrid: document.querySelector('[data-topo-chrome-layout="grid"]')?.getAttribute('class') || '',
63
+ nodeSizeS: document.querySelector('[data-topo-chrome-nodesize="S"]')?.getAttribute('class') || '',
64
+ nodeSizeM: document.querySelector('[data-topo-chrome-nodesize="M"]')?.getAttribute('class') || '',
65
+ nodeSizeL: document.querySelector('[data-topo-chrome-nodesize="L"]')?.getAttribute('class') || '',
66
+ zoomOut: document.querySelector('[data-topo-chrome-zoom-out]')?.getAttribute('class') || '',
67
+ zoomIn: document.querySelector('[data-topo-chrome-zoom-in]')?.getAttribute('class') || '',
68
+ reset: document.querySelector('[data-topo-chrome-reset]')?.getAttribute('class') || '',
69
+ fullscreen: document.querySelector('[data-topo-chrome-fullscreen]')?.getAttribute('class') || '',
70
+ }));
71
+
72
+ // Static markup assertions
73
+ const has = (s, modifier) => s.includes(modifier);
74
+ const staticChecks = {
75
+ layoutRing_press_active: has(markup.layoutRing, 'active:bg-cyan-500/15') || has(markup.layoutRing, 'active:bg-cyan-500/25'),
76
+ layoutGrid_press_active: has(markup.layoutGrid, 'active:bg-cyan-500/15') || has(markup.layoutGrid, 'active:bg-cyan-500/25'),
77
+ // nodeSize S/M/L: idle nodeScale=L default, so S/M are non-active
78
+ // variant (active:bg-white/10); L is the active cyan variant
79
+ // (active:bg-cyan-500/25). Check at least one of each pattern present.
80
+ nodeSizeS_press: has(markup.nodeSizeS, 'active:bg-white/10') || has(markup.nodeSizeS, 'active:bg-cyan-500/25'),
81
+ nodeSizeM_press: has(markup.nodeSizeM, 'active:bg-white/10') || has(markup.nodeSizeM, 'active:bg-cyan-500/25'),
82
+ nodeSizeL_press: has(markup.nodeSizeL, 'active:bg-white/10') || has(markup.nodeSizeL, 'active:bg-cyan-500/25'),
83
+ zoomOut_press_white: has(markup.zoomOut, 'active:bg-white/10'),
84
+ zoomIn_press_white: has(markup.zoomIn, 'active:bg-white/10'),
85
+ reset_press_white: has(markup.reset, 'active:bg-white/10'),
86
+ fullscreen_press: has(markup.fullscreen, 'active:bg-white/10') || has(markup.fullscreen, 'active:bg-cyan-500/25'),
87
+ };
88
+
89
+ // Stylesheet-presence check: walk document.styleSheets recursively
90
+ // and confirm Tailwind compiled the press-state rules into the page's
91
+ // CSS. Tailwind 4 nests utilities inside @layer utilities { ... } as
92
+ // a single grouping rule, so cssRules iteration must recurse.
93
+ // Synthetic Playwright pointer events don't reliably trigger
94
+ // :hover/:active in headless Chromium, so runtime computed-style
95
+ // snapshots are unreliable. Checking that the rule exists in the
96
+ // page's stylesheets is the closest static guarantee that a real
97
+ // user's click will deepen the bg.
98
+ const cssChecks = await page.evaluate(() => {
99
+ const activeRules = [];
100
+ function walk(list) {
101
+ for (const r of list || []) {
102
+ if (r.selectorText && r.selectorText.includes(':active')) {
103
+ activeRules.push({ selector: r.selectorText, text: r.cssText });
104
+ }
105
+ if (r.cssRules) walk(r.cssRules);
106
+ }
107
+ }
108
+ for (const sheet of document.styleSheets) {
109
+ let list;
110
+ try { list = sheet.cssRules; } catch { continue; }
111
+ walk(list);
112
+ }
113
+ // For each modifier the markup uses, verify at least one matching
114
+ // CSSStyleRule exists. Tailwind 4 emits each rule twice (an RGBA
115
+ // fallback and a color-mix() form), so .some() across either is fine.
116
+ const has = (selectorSubstr) =>
117
+ activeRules.some(r => r.selector.includes(selectorSubstr));
118
+ return {
119
+ css_active_white_10: has('active\\:bg-white\\/10'),
120
+ css_active_cyan_25: has('active\\:bg-cyan-500\\/25'),
121
+ css_active_cyan_15: has('active\\:bg-cyan-500\\/15'),
122
+ sample_active_rules: activeRules.length,
123
+ };
124
+ });
125
+
126
+ await browser.close();
127
+
128
+ const results = { ...staticChecks, ...cssChecks };
129
+ // Boolean fields must be true; numeric sample_active_rules just needs > 0.
130
+ const ok = Object.entries(results).every(([k, v]) =>
131
+ k.startsWith('sample_') ? (typeof v === 'number' && v > 0) : v === true);
132
+ console.log(`${ok ? '✅' : '❌'} chrome press-state:`, JSON.stringify(results),
133
+ `\n markup snippets:`,
134
+ Object.fromEntries(Object.entries(markup).map(([k, v]) => [k, v.match(/active:bg-[^\s]+/g)?.join(' ') || '(no active: classes)'])));
135
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,122 @@
1
+ /* Round 192 verification: the zoom-level readout span (the "%" text
2
+ * between + and − buttons) participates in the R186 click-feel pop
3
+ * alongside the icons.
4
+ *
5
+ * Pre-R192 only the + / − icons popped on click; the readout text
6
+ * itself snapped from one value to another with no acknowledgement.
7
+ * R192 splices the existing .anet-chrome-pop class onto the readout
8
+ * span while chromePopping is 'zoom-in' or 'zoom-out' (set by the
9
+ * shared popChrome flag the icons already use).
10
+ *
11
+ * Test:
12
+ * 1. Default state — readout has data-topo-chrome-zoom-level-popping
13
+ * = 'false', no 'anet-chrome-pop' in className.
14
+ * 2. Click zoom-in: capture the readout className within ~120ms.
15
+ * Should have 'anet-chrome-pop' + data-popping='true'.
16
+ * 3. Wait past the 240ms class clear: readout className back to
17
+ * base, data-popping='false'.
18
+ * 4. Click zoom-out: same pop activation flips on then back off.
19
+ * 5. Display rule: span must NOT be 'inline' so the transform:
20
+ * scale() from the keyframe actually renders. (Inline style
21
+ * sets display:'inline-block' for that reason — but the parent
22
+ * <div class="flex"> blockifies it to 'block' in computed
23
+ * style, which is fine for transforms either way.)
24
+ */
25
+ import { chromium } from 'playwright';
26
+ import { readFileSync } from 'node:fs';
27
+
28
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
29
+ const browser = await chromium.launch({ headless: true });
30
+
31
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
32
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
33
+ await ctx.addInitScript(() => {
34
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
35
+ });
36
+
37
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
38
+ await ctx.route('**/api/hub/status*', async (route) => {
39
+ const r = await route.fetch();
40
+ const b = await r.json();
41
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
42
+ const mk = (alias) => ({
43
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
44
+ network_id: nid, project_dir: null,
45
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
46
+ });
47
+ await route.fulfill({ response: r, json: { ...b, sessions: [
48
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
49
+ ] } });
50
+ });
51
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
52
+ await ctx.route('**/api/hub/tasks*', (route) => route.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-chrome-zoom-level]', { timeout: 10000 });
58
+ await page.waitForTimeout(300);
59
+
60
+ const readSpan = async () => page.evaluate(() => {
61
+ const el = document.querySelector('[data-topo-chrome-zoom-level]');
62
+ if (!el) return null;
63
+ return {
64
+ className: el.getAttribute('class') || '',
65
+ popping: el.getAttribute('data-topo-chrome-zoom-level-popping'),
66
+ display: getComputedStyle(el).display,
67
+ text: el.textContent?.trim() || '',
68
+ };
69
+ });
70
+
71
+ const idle = await readSpan();
72
+
73
+ // Click zoom-in and snapshot quickly
74
+ await page.click('[data-topo-chrome-zoom-in]');
75
+ await page.waitForTimeout(80); // well inside the 240ms class window
76
+ const popped = await readSpan();
77
+
78
+ // Wait past the popChrome clear (~260ms total)
79
+ await page.waitForTimeout(280);
80
+ const settled = await readSpan();
81
+
82
+ // Click zoom-out and snapshot quickly
83
+ await page.click('[data-topo-chrome-zoom-out]');
84
+ await page.waitForTimeout(80);
85
+ const poppedOut = await readSpan();
86
+
87
+ await page.waitForTimeout(280);
88
+ const settled2 = await readSpan();
89
+
90
+ await browser.close();
91
+
92
+ const has = (s, c) => (s?.className || '').split(/\s+/).includes(c);
93
+
94
+ const results = {
95
+ idle_no_pop_class: !has(idle, 'anet-chrome-pop'),
96
+ idle_popping_false: idle?.popping === 'false',
97
+ idle_has_base_classes: /tabular-nums/.test(idle?.className || '') && /border-x/.test(idle?.className || ''),
98
+ display_not_inline: idle?.display !== 'inline',
99
+
100
+ popped_has_pop_class: has(popped, 'anet-chrome-pop'),
101
+ popped_popping_true: popped?.popping === 'true',
102
+ popped_keeps_base: /tabular-nums/.test(popped?.className || ''),
103
+
104
+ settled_no_pop_class: !has(settled, 'anet-chrome-pop'),
105
+ settled_popping_false: settled?.popping === 'false',
106
+
107
+ poppedOut_has_pop_class: has(poppedOut, 'anet-chrome-pop'),
108
+ poppedOut_popping_true: poppedOut?.popping === 'true',
109
+
110
+ settled2_no_pop_class: !has(settled2, 'anet-chrome-pop'),
111
+ settled2_popping_false: settled2?.popping === 'false',
112
+
113
+ text_shows_percent: /^\d+%$/.test(idle?.text || ''),
114
+ };
115
+ const ok = Object.values(results).every(Boolean);
116
+ console.log(`${ok ? '✅' : '❌'} chrome zoom-level pop:`, JSON.stringify(results),
117
+ `\n idle:`, idle,
118
+ `\n popped:`, popped,
119
+ `\n settled:`, settled,
120
+ `\n poppedOut:`, poppedOut,
121
+ `\n settled2:`, settled2);
122
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,130 @@
1
+ /* Round 186 verification: chrome zoom-in / zoom-out buttons get
2
+ * a one-shot icon "pop" on click — same click-feel idiom R184
3
+ * added for the reset spin, applied to the two zoom buttons.
4
+ *
5
+ * Test:
6
+ * 1. Idle: neither popping
7
+ * 2. Click chrome zoom-in → zoom-in icon gets 'anet-chrome-pop',
8
+ * attr 'true' within 50ms
9
+ * 3. Wait 280ms → class + attr cleared
10
+ * 4. Click chrome zoom-out → zoom-out icon gets the class, attr
11
+ * 'true'; zoom-in stays cleared
12
+ * 5. CSS keyframe 'anet-chrome-pop' exists in stylesheet and
13
+ * includes scale(1.18)
14
+ */
15
+ import { chromium } from 'playwright';
16
+ import { readFileSync } from 'node:fs';
17
+
18
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
19
+ const browser = await chromium.launch({ headless: true });
20
+
21
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
22
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
23
+ await ctx.addInitScript(() => {
24
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
25
+ });
26
+
27
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
28
+ await ctx.route('**/api/hub/status*', async (route) => {
29
+ const r = await route.fetch();
30
+ const b = await r.json();
31
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
32
+ const mk = (alias) => ({
33
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
34
+ network_id: nid, project_dir: null,
35
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
36
+ });
37
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta'), mk('gamma')] } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
40
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
41
+
42
+ const page = await ctx.newPage();
43
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
44
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
45
+ await page.waitForSelector('[data-topo-chrome-zoom-in]', { timeout: 10000 });
46
+ await page.waitForTimeout(400);
47
+
48
+ const probe = () => page.evaluate(() => {
49
+ const inBtn = document.querySelector('[data-topo-chrome-zoom-in]');
50
+ const outBtn = document.querySelector('[data-topo-chrome-zoom-out]');
51
+ const inIcon = document.querySelector('[data-topo-chrome-zoom-in-icon]');
52
+ const outIcon = document.querySelector('[data-topo-chrome-zoom-out-icon]');
53
+ return {
54
+ inAttr: inBtn?.getAttribute('data-topo-chrome-zoom-in-popping'),
55
+ outAttr: outBtn?.getAttribute('data-topo-chrome-zoom-out-popping'),
56
+ inClass: inIcon?.getAttribute('class') || '',
57
+ outClass: outIcon?.getAttribute('class') || '',
58
+ };
59
+ });
60
+
61
+ const idle = await probe();
62
+
63
+ await page.locator('[data-topo-chrome-zoom-in]').click();
64
+ await page.waitForTimeout(50);
65
+ const duringIn = await probe();
66
+
67
+ await page.waitForTimeout(280);
68
+ const afterIn = await probe();
69
+
70
+ await page.locator('[data-topo-chrome-zoom-out]').click();
71
+ await page.waitForTimeout(50);
72
+ const duringOut = await probe();
73
+
74
+ await page.waitForTimeout(280);
75
+ const afterOut = await probe();
76
+
77
+ const cssCheck = await page.evaluate(() => {
78
+ for (const sheet of document.styleSheets) {
79
+ let rules;
80
+ try { rules = sheet.cssRules; } catch { continue; }
81
+ if (!rules) continue;
82
+ for (const rule of rules) {
83
+ if (rule.type === 7 && rule.name === 'anet-chrome-pop') {
84
+ return { found: true, cssText: rule.cssText };
85
+ }
86
+ }
87
+ }
88
+ return { found: false, cssText: '' };
89
+ });
90
+
91
+ await browser.close();
92
+
93
+ const results = {
94
+ idle_in_attr_false: idle.inAttr === 'false',
95
+ idle_out_attr_false: idle.outAttr === 'false',
96
+ idle_no_in_pop: !idle.inClass.includes('anet-chrome-pop'),
97
+ idle_no_out_pop: !idle.outClass.includes('anet-chrome-pop'),
98
+
99
+ // After zoom-in click: in popping, out static
100
+ in_attr_true: duringIn.inAttr === 'true',
101
+ in_has_pop_class: duringIn.inClass.includes('anet-chrome-pop'),
102
+ out_stays_false: duringIn.outAttr === 'false',
103
+ out_no_pop_class: !duringIn.outClass.includes('anet-chrome-pop'),
104
+
105
+ // After 280ms: in cleared
106
+ after_in_attr_false: afterIn.inAttr === 'false',
107
+ after_in_no_pop: !afterIn.inClass.includes('anet-chrome-pop'),
108
+
109
+ // After zoom-out click: out popping
110
+ out_attr_true: duringOut.outAttr === 'true',
111
+ out_has_pop_class: duringOut.outClass.includes('anet-chrome-pop'),
112
+ in_stays_false: duringOut.inAttr === 'false',
113
+ in_no_pop_after: !duringOut.inClass.includes('anet-chrome-pop'),
114
+
115
+ // After 280ms: out cleared
116
+ after_out_attr_false: afterOut.outAttr === 'false',
117
+ after_out_no_pop: !afterOut.outClass.includes('anet-chrome-pop'),
118
+
119
+ keyframe_found: cssCheck.found,
120
+ keyframe_has_scale_1p18: cssCheck.cssText.includes('scale(1.18)'),
121
+ };
122
+ const ok = Object.values(results).every(Boolean);
123
+ console.log(`${ok ? '✅' : '❌'} chrome zoom pop:`, JSON.stringify(results),
124
+ `\n idle:`, idle,
125
+ `\n duringIn:`, duringIn,
126
+ `\n afterIn:`, afterIn,
127
+ `\n duringOut:`, duringOut,
128
+ `\n afterOut:`, afterOut,
129
+ `\n keyframe found:`, cssCheck.found);
130
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,94 @@
1
+ /* Round 227 verification: click-ripple SMIL <animate> gets ease-out
2
+ * curve via calcMode/keySplines instead of default linear
3
+ * interpolation. Click triggers ripple state (R14 node click); test
4
+ * probes the resulting <circle data-click-ripple> element's two
5
+ * <animate> children for the spline attributes.
6
+ *
7
+ * Test scenario:
8
+ * - Load TopoGraph with 4 nodes
9
+ * - Click the first node (R14 click-feel state machine)
10
+ * - Within 500ms (before ripple expires), probe [data-click-ripple]
11
+ * - Verify both <animate> children have:
12
+ * calcMode="spline"
13
+ * keyTimes="0;1"
14
+ * keySplines="0.25 0.1 0.25 1"
15
+ * dur="0.5s"
16
+ * fill="freeze"
17
+ */
18
+ import { chromium } from 'playwright';
19
+ import { readFileSync } from 'node:fs';
20
+
21
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
22
+ const browser = await chromium.launch({ headless: true });
23
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
24
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
25
+ await ctx.addInitScript(() => {
26
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
27
+ });
28
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
29
+ await ctx.route('**/api/hub/status*', async (route) => {
30
+ const r = await route.fetch();
31
+ const b = await r.json();
32
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
33
+ const mk = (alias) => ({
34
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
35
+ network_id: nid, project_dir: null,
36
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
37
+ });
38
+ await route.fulfill({ response: r, json: { ...b, sessions: [
39
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
40
+ ] } });
41
+ });
42
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
44
+
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
48
+ await page.waitForTimeout(400);
49
+
50
+ // Click the first node body (its <circle data-node-status-ring> sits
51
+ // inside the node <g>, which has the onClick handler that fires
52
+ // setClickRipple). Programmatic dispatch — Playwright .click() can
53
+ // be flaky on SVG <g>, so we synthesize the event directly.
54
+ await page.evaluate(() => {
55
+ const node = document.querySelector('g[data-node]');
56
+ if (!node) throw new Error('no node found');
57
+ node.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
58
+ });
59
+ // Ripple element mounts on next React commit — wait for it.
60
+ await page.waitForSelector('[data-click-ripple]', { timeout: 3000, state: 'attached' });
61
+
62
+ const probe = await page.evaluate(() => {
63
+ const ripple = document.querySelector('[data-click-ripple]');
64
+ if (!ripple) return null;
65
+ const animates = ripple.querySelectorAll('animate');
66
+ return Array.from(animates).map((a) => ({
67
+ attr: a.getAttribute('attributeName'),
68
+ calcMode: a.getAttribute('calcMode'),
69
+ keyTimes: a.getAttribute('keyTimes'),
70
+ keySplines: a.getAttribute('keySplines'),
71
+ dur: a.getAttribute('dur'),
72
+ fill: a.getAttribute('fill'),
73
+ }));
74
+ });
75
+ await browser.close();
76
+
77
+ const easeSpline = '0.25 0.1 0.25 1';
78
+ const expected = ['r', 'opacity'];
79
+
80
+ const results = {
81
+ ripple_mounted: probe !== null,
82
+ two_animates: probe?.length === 2,
83
+ has_r_animate: probe?.some(a => a.attr === 'r') ?? false,
84
+ has_opacity_animate: probe?.some(a => a.attr === 'opacity') ?? false,
85
+ both_spline_calcmode: probe?.every(a => a.calcMode === 'spline') ?? false,
86
+ both_keytimes_0_1: probe?.every(a => a.keyTimes === '0;1') ?? false,
87
+ both_ease_keysplines: probe?.every(a => a.keySplines === easeSpline) ?? false,
88
+ both_dur_0_5s: probe?.every(a => a.dur === '0.5s') ?? false,
89
+ both_fill_freeze: probe?.every(a => a.fill === 'freeze') ?? false,
90
+ attrs_match_expected: probe?.map(a => a.attr).sort().join(',') === expected.sort().join(','),
91
+ };
92
+ const ok = Object.values(results).every(Boolean);
93
+ console.log(`${ok ? '✅' : '❌'} click-ripple ease-out:`, JSON.stringify(results), '\n probe:', probe);
94
+ process.exit(ok ? 0 : 1);