@sleep2agi/agent-network-dashboard 0.4.6-preview.36 → 0.4.6-preview.362

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 (533) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +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 +16 -0
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +2 -2
  17. package/.next/server/app/_not-found.rsc +12 -12
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  19. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  20. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  24. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  25. package/.next/server/app/admin.html +2 -2
  26. package/.next/server/app/admin.rsc +14 -14
  27. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  28. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  29. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  30. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  31. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  32. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  33. package/.next/server/app/api/hub/server/[hostname]/agents/route/app-paths-manifest.json +3 -0
  34. package/.next/server/app/api/hub/server/[hostname]/agents/route/build-manifest.json +9 -0
  35. package/.next/server/app/api/hub/server/[hostname]/agents/route/server-reference-manifest.json +4 -0
  36. package/.next/server/app/api/hub/server/[hostname]/agents/route.js +7 -0
  37. package/.next/server/app/api/hub/server/[hostname]/agents/route.js.map +5 -0
  38. package/.next/server/app/api/hub/server/[hostname]/agents/route.js.nft.json +1 -0
  39. package/.next/server/app/api/hub/server/[hostname]/agents/route_client-reference-manifest.js +3 -0
  40. package/.next/server/app/api/hub/server/[hostname]/health/route/app-paths-manifest.json +3 -0
  41. package/.next/server/app/api/hub/server/[hostname]/health/route/build-manifest.json +9 -0
  42. package/.next/server/app/api/hub/server/[hostname]/health/route/server-reference-manifest.json +4 -0
  43. package/.next/server/app/api/hub/server/[hostname]/health/route.js +7 -0
  44. package/.next/server/app/api/hub/server/[hostname]/health/route.js.map +5 -0
  45. package/.next/server/app/api/hub/server/[hostname]/health/route.js.nft.json +1 -0
  46. package/.next/server/app/api/hub/server/[hostname]/health/route_client-reference-manifest.js +3 -0
  47. package/.next/server/app/index.html +2 -2
  48. package/.next/server/app/index.rsc +14 -14
  49. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  50. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  55. package/.next/server/app/login.html +2 -2
  56. package/.next/server/app/login.rsc +14 -14
  57. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  58. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  59. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  60. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  61. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  62. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  63. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  64. package/.next/server/app/logs.html +2 -2
  65. package/.next/server/app/logs.rsc +14 -14
  66. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  67. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  68. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  69. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  70. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  71. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  72. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  73. package/.next/server/app/messages.html +2 -2
  74. package/.next/server/app/messages.rsc +14 -14
  75. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  76. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  77. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  78. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  79. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  80. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  81. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app/node.html +2 -2
  83. package/.next/server/app/node.rsc +14 -14
  84. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  85. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  86. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  87. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  88. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  89. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  90. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  91. package/.next/server/app/nodes.html +2 -2
  92. package/.next/server/app/nodes.rsc +14 -14
  93. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  94. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  95. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  96. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  97. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  98. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  99. package/.next/server/app/page_client-reference-manifest.js +1 -1
  100. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  101. package/.next/server/app/server-logs.html +2 -2
  102. package/.next/server/app/server-logs.rsc +14 -14
  103. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  104. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  105. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  106. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  107. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  108. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  109. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  110. package/.next/server/app/settings/networks.html +2 -2
  111. package/.next/server/app/settings/networks.rsc +14 -14
  112. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  113. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  114. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  115. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  116. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  117. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  118. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  119. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  120. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  121. package/.next/server/app/settings/tokens.html +2 -2
  122. package/.next/server/app/settings/tokens.rsc +14 -14
  123. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  124. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  125. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  126. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  127. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  128. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  129. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  130. package/.next/server/app/settings.html +2 -2
  131. package/.next/server/app/settings.rsc +14 -14
  132. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  133. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  134. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  135. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  136. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  137. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  138. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  139. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  140. package/.next/server/app/tasks.html +2 -2
  141. package/.next/server/app/tasks.rsc +14 -14
  142. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  143. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  144. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  145. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  146. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  147. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  148. package/.next/server/app-paths-manifest.json +2 -0
  149. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js +3 -0
  150. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js.map +1 -0
  151. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js +3 -0
  152. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js.map +1 -0
  153. package/.next/server/chunks/[root-of-the-server]__05nyivw._.js +3 -0
  154. package/.next/server/chunks/[root-of-the-server]__05nyivw._.js.map +1 -0
  155. package/.next/server/chunks/[root-of-the-server]__0floe3h._.js +3 -0
  156. package/.next/server/chunks/[root-of-the-server]__0floe3h._.js.map +1 -0
  157. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
  158. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
  159. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  160. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  161. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
  162. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
  163. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -1
  164. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  165. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  166. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  167. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +1 -1
  168. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -1
  169. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  170. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  171. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
  172. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
  173. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
  174. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
  175. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
  176. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
  177. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
  178. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
  179. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
  180. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
  181. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
  182. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
  183. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  184. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  185. package/.next/server/middleware-build-manifest.js +3 -3
  186. package/.next/server/pages/404.html +2 -2
  187. package/.next/server/pages/500.html +1 -1
  188. package/.next/server/server-reference-manifest.js +1 -1
  189. package/.next/server/server-reference-manifest.json +1 -1
  190. package/.next/static/chunks/0-mpa_947ipeq.js +1 -0
  191. package/.next/static/chunks/01lmbsd37fybu.js +1 -0
  192. package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
  193. package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
  194. package/.next/static/chunks/05uk96gc~9mni.js +1 -0
  195. package/.next/static/chunks/07lsi~s-c0wvh.css +2 -0
  196. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  197. package/.next/static/chunks/0b5odyhyr1fnp.js +1 -0
  198. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  199. package/.next/static/chunks/0l_~q07bhpkcx.js +7 -0
  200. package/.next/static/chunks/0mmvfihvlb_w4.js +4 -0
  201. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  202. package/.next/static/chunks/0utwz8~_w7ae2.js +1 -0
  203. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  204. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  205. package/.next/trace +2 -2
  206. package/.next/trace-build +1 -1
  207. package/.next/types/routes.d.ts +3 -1
  208. package/.next/types/validator.ts +18 -0
  209. package/app/api/hub/server/[hostname]/agents/route.ts +62 -0
  210. package/app/api/hub/server/[hostname]/health/route.ts +58 -0
  211. package/app/components/ChatPopover.tsx +38 -2
  212. package/app/components/CommandPalette.tsx +107 -0
  213. package/app/components/HelpOverlay.tsx +110 -1
  214. package/app/components/ServersDrawer.tsx +181 -5
  215. package/app/components/TopoGraph.tsx +6228 -254
  216. package/app/components/utils.ts +8 -7
  217. package/app/globals.css +110 -5
  218. package/app/lib/time.ts +36 -0
  219. package/app/lib/vendorIdentity.ts +14 -3
  220. package/docs/rfc/v0.10.0-hero3-dashboard-upgrade.md +230 -0
  221. package/package.json +2 -2
  222. package/public/sleep2agi-logo.svg +11 -0
  223. package/public/vendors/claude.svg +11 -0
  224. package/public/vendors/minimax.svg +11 -0
  225. package/public/vendors/openai.svg +12 -0
  226. package/screenshots/r293-p0/after-full.png +0 -0
  227. package/screenshots/r293-p0/after-topo.png +0 -0
  228. package/screenshots/r293-p0/baseline-full.png +0 -0
  229. package/screenshots/r293-p0/baseline-topo.png +0 -0
  230. package/screenshots/r293-p0/logo-integrated-full.png +0 -0
  231. package/screenshots/r293-p0/logo-integrated-topo.png +0 -0
  232. package/screenshots/r293-p0/servers-drawer-v0.10.0-full.png +0 -0
  233. package/screenshots/r293-p0/servers-drawer-v0.10.0.png +0 -0
  234. package/screenshots/r293-p0/wave1-after-full.png +0 -0
  235. package/screenshots/r293-p0/wave1-after-topo.png +0 -0
  236. package/scripts/p0-servers-drawer-screenshot.mjs +99 -0
  237. package/scripts/p0-topo-screenshot.mjs +82 -0
  238. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  239. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  240. package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
  241. package/scripts/topo-active-links-chip-self-hover-test.mjs +171 -0
  242. package/scripts/topo-active-links-chip-test.mjs +73 -0
  243. package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
  244. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  245. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  246. package/scripts/topo-active-pulse-ease-test.mjs +120 -0
  247. package/scripts/topo-brand-logo-transition-test.mjs +94 -0
  248. package/scripts/topo-brand-watermark-test.mjs +97 -0
  249. package/scripts/topo-canvas-aria-test.mjs +108 -0
  250. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  251. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  252. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  253. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  254. package/scripts/topo-chip-focus-test.mjs +95 -0
  255. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  256. package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
  257. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  258. package/scripts/topo-chiprow-data-weight-test.mjs +94 -0
  259. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  260. package/scripts/topo-chrome-active-weight-test.mjs +104 -0
  261. package/scripts/topo-chrome-focus-ring-unify-test.mjs +100 -0
  262. package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
  263. package/scripts/topo-chrome-inactive-color-test.mjs +90 -0
  264. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  265. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  266. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  267. package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
  268. package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
  269. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  270. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  271. package/scripts/topo-chrome-zoom-readout-weight-test.mjs +81 -0
  272. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  273. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  274. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  275. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  276. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  277. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  278. package/scripts/topo-corner-radius-unify-test.mjs +103 -0
  279. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  280. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  281. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  282. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  283. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  284. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  285. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  286. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  287. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  288. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  289. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  290. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  291. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  292. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  293. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  294. package/scripts/topo-edge-fadein-test.mjs +106 -0
  295. package/scripts/topo-edge-focus-test.mjs +113 -0
  296. package/scripts/topo-edge-hover-test.mjs +93 -0
  297. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  298. package/scripts/topo-edge-pill-test.mjs +105 -0
  299. package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
  300. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  301. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  302. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  303. package/scripts/topo-empty-hint-tracking-test.mjs +84 -0
  304. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  305. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  306. package/scripts/topo-empty-state-recenter-test.mjs +107 -0
  307. package/scripts/topo-empty-subhint-tracking-test.mjs +87 -0
  308. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  309. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  310. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  311. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  312. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  313. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  314. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  315. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  316. package/scripts/topo-freshness-chip-test.mjs +74 -0
  317. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  318. package/scripts/topo-freshness-chip-weight-test.mjs +90 -0
  319. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  320. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  321. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  322. package/scripts/topo-group-count-test.mjs +6 -1
  323. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  324. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  325. package/scripts/topo-group-label-hover-test.mjs +119 -0
  326. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  327. package/scripts/topo-group-label-tint-test.mjs +99 -0
  328. package/scripts/topo-group-label-title-test.mjs +83 -0
  329. package/scripts/topo-group-pin-test.mjs +119 -0
  330. package/scripts/topo-group-pip-fadein-test.mjs +104 -0
  331. package/scripts/topo-group-pip-singletier-drop-test.mjs +131 -0
  332. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  333. package/scripts/topo-group-status-pips-test.mjs +103 -0
  334. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  335. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  336. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  337. package/scripts/topo-groupbox-live-test.mjs +100 -0
  338. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  339. package/scripts/topo-groupbox-test.mjs +4 -2
  340. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  341. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  342. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  343. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  344. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  345. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  346. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  347. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  348. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  349. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  350. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  351. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  352. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  353. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  354. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  355. package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
  356. package/scripts/topo-hub-a11y-test.mjs +112 -0
  357. package/scripts/topo-hub-busyness-test.mjs +100 -0
  358. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  359. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  360. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  361. package/scripts/topo-hub-fadein-test.mjs +88 -0
  362. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  363. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  364. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  365. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  366. package/scripts/topo-hub-title-test.mjs +93 -0
  367. package/scripts/topo-hub-working-count-test.mjs +99 -0
  368. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  369. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  370. package/scripts/topo-kicker-color-test.mjs +93 -0
  371. package/scripts/topo-kicker-tracking-test.mjs +101 -0
  372. package/scripts/topo-kicker-weight-test.mjs +98 -0
  373. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  374. package/scripts/topo-label-lift-test.mjs +5 -1
  375. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  376. package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
  377. package/scripts/topo-layout-key-test.mjs +91 -0
  378. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  379. package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
  380. package/scripts/topo-layout-toggle-test.mjs +131 -0
  381. package/scripts/topo-legend-compress-test.mjs +107 -0
  382. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  383. package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
  384. package/scripts/topo-legend-count-tabular-test.mjs +87 -0
  385. package/scripts/topo-legend-count-test.mjs +88 -0
  386. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  387. package/scripts/topo-legend-count-weight-test.mjs +86 -0
  388. package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
  389. package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
  390. package/scripts/topo-legend-hover-test.mjs +113 -0
  391. package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
  392. package/scripts/topo-legend-offline-label-simplify-test.mjs +86 -0
  393. package/scripts/topo-legend-panel-count-weight-test.mjs +89 -0
  394. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  395. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  396. package/scripts/topo-legend-pin-test.mjs +128 -0
  397. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  398. package/scripts/topo-legend-row-labels-simplify-test.mjs +80 -0
  399. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  400. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  401. package/scripts/topo-legend-row-title-test.mjs +102 -0
  402. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  403. package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
  404. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  405. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  406. package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
  407. package/scripts/topo-minimap-test.mjs +68 -0
  408. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  409. package/scripts/topo-mobile-chips-test.mjs +103 -0
  410. package/scripts/topo-monogram-font-test.mjs +112 -0
  411. package/scripts/topo-monogram-stroke-test.mjs +101 -0
  412. package/scripts/topo-node-a11y-test.mjs +107 -0
  413. package/scripts/topo-node-alias-chat-pin-test.mjs +95 -0
  414. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  415. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  416. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  417. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  418. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  419. package/scripts/topo-node-title-peers-test.mjs +107 -0
  420. package/scripts/topo-node-title-test.mjs +101 -0
  421. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  422. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  423. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  424. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  425. package/scripts/topo-panel-elevation-test.mjs +76 -0
  426. package/scripts/topo-panel-fadein-test.mjs +102 -0
  427. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  428. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  429. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  430. package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
  431. package/scripts/topo-panel-title-tracking-test.mjs +78 -0
  432. package/scripts/topo-particle-stagger-test.mjs +82 -0
  433. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  434. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  435. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  436. package/scripts/topo-pin-intersection-test.mjs +121 -0
  437. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  438. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  439. package/scripts/topo-pin-persistence-test.mjs +105 -0
  440. package/scripts/topo-popover-meta-test.mjs +78 -0
  441. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  442. package/scripts/topo-pressure-bar-test.mjs +69 -0
  443. package/scripts/topo-pressure-hover-test.mjs +123 -0
  444. package/scripts/topo-pressure-pin-test.mjs +119 -0
  445. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  446. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  447. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  448. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  449. package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
  450. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  451. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  452. package/scripts/topo-recent-panel-count-weight-test.mjs +91 -0
  453. package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
  454. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  455. package/scripts/topo-recent-panel-hot-tabular-test.mjs +113 -0
  456. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  457. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  458. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  459. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  460. package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
  461. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  462. package/scripts/topo-recent-row-count-weight-test.mjs +112 -0
  463. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  464. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  465. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  466. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  467. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  468. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  469. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  470. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  471. package/scripts/topo-recent-row-title-test.mjs +112 -0
  472. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  473. package/scripts/topo-recent-row-ts-tabular-test.mjs +108 -0
  474. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  475. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  476. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  477. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  478. package/scripts/topo-reduced-motion-test.mjs +77 -0
  479. package/scripts/topo-reset-spin-test.mjs +120 -0
  480. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  481. package/scripts/topo-spokes-retired-test.mjs +93 -0
  482. package/scripts/topo-starfield-density-test.mjs +89 -0
  483. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  484. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  485. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  486. package/scripts/topo-svg-focus-test.mjs +112 -0
  487. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  488. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  489. package/scripts/topo-tier-guide-dashes-test.mjs +86 -0
  490. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  491. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  492. package/scripts/topo-tier-ring-test.mjs +69 -0
  493. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  494. package/scripts/topo-tier-stagger-test.mjs +89 -0
  495. package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
  496. package/scripts/topo-title-tracking-test.mjs +98 -0
  497. package/scripts/topo-titleblock-gap-test.mjs +86 -0
  498. package/scripts/topo-titleblock-margin-test.mjs +83 -0
  499. package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
  500. package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
  501. package/scripts/topo-tz-ghost-test.mjs +69 -0
  502. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  503. package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
  504. package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
  505. package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
  506. package/scripts/topo-vendor-chip-weight-test.mjs +87 -0
  507. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  508. package/scripts/topo-vendor-pill-test.mjs +125 -0
  509. package/scripts/topo-vendor-pin-test.mjs +125 -0
  510. package/scripts/topo-view-smooth-test.mjs +131 -0
  511. package/scripts/topo-watermark-tracking-test.mjs +99 -0
  512. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  513. package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
  514. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  515. package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
  516. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  517. package/.next/static/chunks/00_p_cprq~0bi.js +0 -1
  518. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  519. package/.next/static/chunks/082rjyyytvq6v.js +0 -1
  520. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  521. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  522. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  523. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  524. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  525. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  526. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  527. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  528. package/.next/static/chunks/13.zti9ix.1eh.css +0 -2
  529. package/.next/static/chunks/16.fwlwq9fvk_.js +0 -1
  530. package/.next/static/chunks/169rm7pr0yr3h.js +0 -7
  531. /package/.next/static/{2VmVsLw44dMGZA7M-1NKi → 5LdAxDjnLcwVdWMm8Id_r}/_buildManifest.js +0 -0
  532. /package/.next/static/{2VmVsLw44dMGZA7M-1NKi → 5LdAxDjnLcwVdWMm8Id_r}/_clientMiddlewareManifest.js +0 -0
  533. /package/.next/static/{2VmVsLw44dMGZA7M-1NKi → 5LdAxDjnLcwVdWMm8Id_r}/_ssgManifest.js +0 -0
@@ -0,0 +1,106 @@
1
+ /* Round 128 verification: "+N more flows" overflow hint in the
2
+ * recent-signal panel.
3
+ *
4
+ * The panel renders top 3 flowLinks via .slice(0, 3). When the
5
+ * fleet has > 3 active flows, the rest silently disappear — the
6
+ * R96 "X flows" header reports the total but doesn't make
7
+ * truncation explicit. R128 adds a muted italic footer
8
+ * "+ N more flow(s)"
9
+ * inside the existing panel (y=82, between row-3 baseline and the
10
+ * panel bottom at y=84), only when flowLinks.length > 3.
11
+ *
12
+ * Three states tested:
13
+ * - 5 distinct flow pairs → footer shows "+ 2 more flows"
14
+ * - exactly 3 flow pairs → footer absent
15
+ * - 0 flows (empty panel) → footer absent (R45 placeholder wins)
16
+ *
17
+ * Note: the test seeds (count) messages per pair so each unique
18
+ * (from→to) pair becomes ONE flowLink (deduped). Pairs > 3 force
19
+ * the hint; pairs ≤ 3 don't.
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
+
26
+ async function probe(pairCount) {
27
+ const browser = await chromium.launch({ headless: true });
28
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
32
+ });
33
+
34
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
35
+ const aliases = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta'];
36
+ await ctx.route('**/api/hub/status*', async (route) => {
37
+ const r = await route.fetch();
38
+ const b = await r.json();
39
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
40
+ const mk = (alias) => ({
41
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
42
+ network_id: nid, project_dir: null,
43
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
44
+ });
45
+ await route.fulfill({ response: r, json: { ...b, sessions: aliases.map(mk) } });
46
+ });
47
+
48
+ const now = Date.now();
49
+ const msgs = [];
50
+ // Make pairCount distinct (from→to) pairs, 1 msg each, ascending age
51
+ for (let i = 0; i < pairCount; i++) {
52
+ msgs.push({
53
+ id: `m${i}`,
54
+ from_alias: aliases[i],
55
+ to_alias: aliases[(i + 1) % aliases.length],
56
+ content: 'hi',
57
+ network_id: 'default',
58
+ created_at: new Date(now - (20000 + i * 1000)).toISOString(),
59
+ });
60
+ }
61
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
62
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
63
+
64
+ const page = await ctx.newPage();
65
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
66
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 6, { timeout: 30000 });
67
+ await page.waitForTimeout(500);
68
+
69
+ const out = await page.evaluate(() => {
70
+ const el = document.querySelector('[data-recent-panel-more]');
71
+ const rows = document.querySelectorAll('[data-recent-row]').length;
72
+ const headerCount = document.querySelector('[data-recent-panel-count]')?.textContent;
73
+ return {
74
+ footerPresent: !!el,
75
+ footerText: el?.textContent?.trim(),
76
+ footerAttr: el?.getAttribute('data-recent-panel-more'),
77
+ rows,
78
+ headerCount,
79
+ };
80
+ });
81
+
82
+ await browser.close();
83
+ return out;
84
+ }
85
+
86
+ const five = await probe(5);
87
+ const three = await probe(3);
88
+ const zero = await probe(0);
89
+
90
+ const results = {
91
+ five_footerPresent: five.footerPresent,
92
+ five_footerText: five.footerText === '+ 2 more flows',
93
+ five_footerAttr: five.footerAttr === '2',
94
+ five_rowsCapped3: five.rows === 3,
95
+ five_headerSaysFive: five.headerCount?.startsWith('5'),
96
+
97
+ three_footerAbsent: !three.footerPresent,
98
+ three_rowsExactly3: three.rows === 3,
99
+
100
+ zero_footerAbsent: !zero.footerPresent,
101
+ zero_rowsZero: zero.rows === 0,
102
+ };
103
+ const ok = Object.values(results).every(Boolean);
104
+ console.log(`${ok ? '✅' : '❌'} recent-panel more:`, JSON.stringify(results),
105
+ `\n five=`, five, `\n three=`, three, `\n zero=`, zero);
106
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,151 @@
1
+ /* Round 259 verification: recent-signal panel's two italic instructional
2
+ * texts bump fontSize 8 → 9 for readability.
3
+ *
4
+ * Pre-R259 size hierarchy in the recent-signal panel:
5
+ * "no flow yet" main empty-state fontSize 10 italic (action prompt)
6
+ * row content "alpha→beta · 3" fontSize 9 regular (data)
7
+ * hint "send a message…" fontSize 8 italic (instruction)
8
+ * "+N more flows" footer link fontSize 8 italic (nav affordance)
9
+ * row timestamp "5s" fontSize 8 regular (recency tag)
10
+ *
11
+ * Pre-R259 the hint + footer rendered at the smallest readable size on
12
+ * canvas, italic + low opacity layering legibility cost on top of the
13
+ * small type. Both are READ-TO-ACT text (instructional / clickable).
14
+ *
15
+ * Post-R259:
16
+ * hint fontSize 8 → 9
17
+ * footer link fontSize 8 → 9
18
+ *
19
+ * The per-row timestamp STAYS at 8 — it's an at-a-glance recency tag
20
+ * tightly co-located with row text, not read-to-act instruction. Hint +
21
+ * footer at 9pt italic still subordinate to row content (9pt regular)
22
+ * via italic + lower opacity discrimination.
23
+ *
24
+ * Test scope:
25
+ * 1. Empty state hint fontSize === 9 (was 8). Tested in empty scenario.
26
+ * 2. "+N more flows" footer fontSize === 9. Tested in 5-msg scenario.
27
+ * 3. Per-row timestamp UNCHANGED at fontSize === 8 (sibling regression).
28
+ * 4. R256 footer-breath invariant preserved — hover footer client
29
+ * bottom inside panel client bottom (footer with fontSize 9 still
30
+ * tucks inside the panel).
31
+ */
32
+ import { chromium } from 'playwright';
33
+ import { readFileSync } from 'node:fs';
34
+
35
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
36
+ const browser = await chromium.launch({ headless: true });
37
+
38
+ async function probeEmpty() {
39
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
40
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
41
+ await ctx.addInitScript(() => {
42
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
43
+ });
44
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
45
+ await ctx.route('**/api/hub/status*', async (route) => {
46
+ const r = await route.fetch();
47
+ const b = await r.json();
48
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
49
+ const mk = (alias) => ({
50
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
51
+ network_id: nid, project_dir: null,
52
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
53
+ });
54
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta')] } });
55
+ });
56
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
57
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
58
+ const page = await ctx.newPage();
59
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
60
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
61
+ await page.waitForSelector('[data-recent-signal-empty-hint]', { timeout: 10000 });
62
+ await page.waitForTimeout(300);
63
+ const result = await page.evaluate(() => {
64
+ const hint = document.querySelector('[data-recent-signal-empty-hint]');
65
+ return { hintFontSize: hint?.getAttribute('font-size') };
66
+ });
67
+ await ctx.close();
68
+ return result;
69
+ }
70
+
71
+ async function probePopulated() {
72
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
73
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
74
+ await ctx.addInitScript(() => {
75
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
76
+ });
77
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
78
+ await ctx.route('**/api/hub/status*', async (route) => {
79
+ const r = await route.fetch();
80
+ const b = await r.json();
81
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
82
+ const mk = (alias) => ({
83
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
84
+ network_id: nid, project_dir: null,
85
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
86
+ });
87
+ await route.fulfill({ response: r, json: { ...b, sessions: [
88
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
89
+ ] } });
90
+ });
91
+ // 5 distinct flow pairs → flowLinks.length === 5 → footer visible
92
+ const now = Date.now();
93
+ const msgs = [
94
+ { id: 'm0', from_alias: 'alpha', to_alias: 'beta', content: 'hi', network_id: 'default', created_at: new Date(now - 1000).toISOString() },
95
+ { id: 'm1', from_alias: 'beta', to_alias: 'gamma', content: 'hi', network_id: 'default', created_at: new Date(now - 1500).toISOString() },
96
+ { id: 'm2', from_alias: 'gamma', to_alias: 'delta', content: 'hi', network_id: 'default', created_at: new Date(now - 2000).toISOString() },
97
+ { id: 'm3', from_alias: 'delta', to_alias: 'alpha', content: 'hi', network_id: 'default', created_at: new Date(now - 2500).toISOString() },
98
+ { id: 'm4', from_alias: 'alpha', to_alias: 'gamma', content: 'hi', network_id: 'default', created_at: new Date(now - 3000).toISOString() },
99
+ ];
100
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: msgs } }));
101
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
102
+ const page = await ctx.newPage();
103
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
104
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
105
+ await page.waitForSelector('[data-recent-panel-more-nav]', { timeout: 10000 });
106
+ await page.waitForTimeout(500);
107
+
108
+ const rest = await page.evaluate(() => {
109
+ const footer = document.querySelector('[data-recent-panel-more]');
110
+ const ts = document.querySelector('[data-recent-row-ts]');
111
+ return {
112
+ footerFontSize: footer?.getAttribute('font-size'),
113
+ tsFontSize: ts?.getAttribute('font-size'),
114
+ };
115
+ });
116
+
117
+ // R256 invariant regression — hover the footer + check it tucks inside panel
118
+ await page.locator('[data-recent-panel-more-nav]').hover();
119
+ await page.waitForTimeout(250);
120
+ const hoverProbe = await page.evaluate(() => {
121
+ const panelG = document.querySelector('[data-topo-panel="recent"]');
122
+ const rect = panelG?.querySelector('rect');
123
+ const footer = document.querySelector('[data-recent-panel-more]');
124
+ return {
125
+ rectBottom: rect?.getBoundingClientRect().bottom ?? null,
126
+ footerBottom: footer?.getBoundingClientRect().bottom ?? null,
127
+ };
128
+ });
129
+ await ctx.close();
130
+
131
+ const hoverClearance = (hoverProbe.rectBottom != null && hoverProbe.footerBottom != null)
132
+ ? (hoverProbe.rectBottom - hoverProbe.footerBottom) : null;
133
+
134
+ return { ...rest, hoverClearance };
135
+ }
136
+
137
+ const empty = await probeEmpty();
138
+ const pop = await probePopulated();
139
+ await browser.close();
140
+
141
+ const results = {
142
+ hint_fontsize_bumped_to_9: empty.hintFontSize === '9',
143
+ footer_fontsize_bumped_to_9: pop.footerFontSize === '9',
144
+ per_row_timestamp_unchanged_at_8: pop.tsFontSize === '8',
145
+ r256_footer_inside_panel_on_hover: pop.hoverClearance != null && pop.hoverClearance >= -1,
146
+ };
147
+ const ok = Object.values(results).every(Boolean);
148
+ console.log(`${ok ? '✅' : '❌'} recent panel readability bump:`, JSON.stringify(results),
149
+ '\n empty:', empty,
150
+ '\n pop:', pop);
151
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,129 @@
1
+ /* Round 189 verification: recent-signal row count tspan unified
2
+ * into one always-mounted element with fill/fontWeight gated by
3
+ * isHot. transition: fill 300ms ease-out makes the R127 hot
4
+ * threshold crossing (count >= 10) ease smoothly through the
5
+ * colour shift instead of snapping.
6
+ *
7
+ * Pre-R189 two tspans branched on isHot — different
8
+ * data-recent-row-count and data-recent-row-count-hot
9
+ * attributes; mount/unmount made fill+fontWeight snap.
10
+ *
11
+ * Test:
12
+ * Mock 3 flows side by side:
13
+ * alpha→beta: count=4 (cold) → fill unset, weight unset,
14
+ * no hot attr
15
+ * alpha→gamma: count=12 (hot) → fill=amber, weight=700,
16
+ * hot attr 'true'
17
+ * alpha→delta: count=20 (hot) → fill=amber, weight=700
18
+ * All three tspans:
19
+ * - have data-recent-row-count
20
+ * - have style.transition includes 'fill 300ms'
21
+ */
22
+ import { chromium } from 'playwright';
23
+ import { readFileSync } from 'node:fs';
24
+
25
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
26
+ const browser = await chromium.launch({ headless: true });
27
+
28
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
32
+ });
33
+
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: 'cli-claude-code',
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
+
49
+ const now = Date.now();
50
+ const msgs = [];
51
+ // alpha→beta: 4 (cold)
52
+ for (let i = 0; i < 4; i++) {
53
+ msgs.push({ id: `cold${i}`, from_alias: 'alpha', to_alias: 'beta', content: 'hi',
54
+ network_id: 'default', created_at: new Date(now - (5000 + i * 50)).toISOString() });
55
+ }
56
+ // alpha→gamma: 12 (hot)
57
+ for (let i = 0; i < 12; i++) {
58
+ msgs.push({ id: `h1${i}`, from_alias: 'alpha', to_alias: 'gamma', content: 'hi',
59
+ network_id: 'default', created_at: new Date(now - (10000 + i * 50)).toISOString() });
60
+ }
61
+ // alpha→delta: 20 (hot)
62
+ for (let i = 0; i < 20; i++) {
63
+ msgs.push({ id: `h2${i}`, from_alias: 'alpha', to_alias: 'delta', content: 'hi',
64
+ network_id: 'default', created_at: new Date(now - (15000 + i * 50)).toISOString() });
65
+ }
66
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
67
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
68
+
69
+ const page = await ctx.newPage();
70
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
71
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
72
+ await page.waitForSelector('[data-recent-row-count]', { timeout: 10000 });
73
+ await page.waitForTimeout(400);
74
+
75
+ const probe = await page.evaluate(() => {
76
+ const tspans = [...document.querySelectorAll('[data-recent-row-count]')];
77
+ return tspans.map(t => {
78
+ const row = t.closest('[data-recent-row]');
79
+ return {
80
+ rowKey: row?.getAttribute('data-recent-row'),
81
+ text: t.textContent,
82
+ fill: t.getAttribute('fill'),
83
+ fontWeight: t.getAttribute('font-weight'),
84
+ hotAttr: t.getAttribute('data-recent-row-count-hot'),
85
+ transition: t.style.transition || getComputedStyle(t).transition,
86
+ };
87
+ });
88
+ });
89
+
90
+ await browser.close();
91
+
92
+ const findByKey = (key) => probe.find(p => p.rowKey === key);
93
+ const cold = findByKey('alpha->beta');
94
+ const hot1 = findByKey('alpha->gamma');
95
+ const hot2 = findByKey('alpha->delta');
96
+
97
+ const hasFillTransition = (s) =>
98
+ (s?.transition || '').includes('fill 300ms') ||
99
+ /fill\s+0\.3s|fill\s+300ms/.test(s?.transition || '');
100
+
101
+ const results = {
102
+ three_tspans_present: probe.length === 3,
103
+ cold_found: cold !== undefined,
104
+ hot1_found: hot1 !== undefined,
105
+ hot2_found: hot2 !== undefined,
106
+
107
+ cold_no_fill: cold?.fill === null || cold?.fill === undefined,
108
+ cold_no_fontWeight: cold?.fontWeight === null || cold?.fontWeight === undefined,
109
+ cold_no_hot_attr: cold?.hotAttr === null,
110
+ cold_text_4: cold?.text === '4',
111
+
112
+ hot1_fill_amber: hot1?.fill === '#fbbf24',
113
+ hot1_fontWeight_700: hot1?.fontWeight === '700',
114
+ hot1_hot_attr: hot1?.hotAttr === 'true',
115
+ hot1_text_12: hot1?.text === '12',
116
+
117
+ hot2_fill_amber: hot2?.fill === '#fbbf24',
118
+ hot2_fontWeight_700: hot2?.fontWeight === '700',
119
+ hot2_hot_attr: hot2?.hotAttr === 'true',
120
+ hot2_text_20: hot2?.text === '20',
121
+
122
+ all_have_fill_transition: probe.every(p => hasFillTransition(p)),
123
+ };
124
+ const ok = Object.values(results).every(Boolean);
125
+ console.log(`${ok ? '✅' : '❌'} recent-row count fill:`, JSON.stringify(results),
126
+ `\n cold:`, cold,
127
+ `\n hot1:`, hot1,
128
+ `\n hot2:`, hot2);
129
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,112 @@
1
+ /* Round 320 verification: recent-signal panel per-row count tspan
2
+ * cold-state fontWeight 400 (parent-inherited) → 600 (explicit).
3
+ *
4
+ * The 5-tier SVG data-weight hierarchy was completed in pieces:
5
+ * - R309: legend per-row count fw=600
6
+ * - R310: legend panel-header count fw=600
7
+ * - R311: recent-signal panel-header flow count fw=600
8
+ * - R320: recent-signal PER-ROW count fw=600 (this round)
9
+ *
10
+ * Pre-R320 a cold row's "· 12" digit painted at parent default (400),
11
+ * indistinguishable from the surrounding alias text. The count IS
12
+ * data — it should sit in the fw=600 tier alongside its siblings.
13
+ *
14
+ * Hot state (count ≥ 10) stays at 700 (R127). The cold→hot crossing
15
+ * now gradates 600 → 700 in weight and amber-fill in colour — the
16
+ * fill flip carries the dramatic visual cue while the weight tier
17
+ * stays consistent across rows.
18
+ *
19
+ * Contract:
20
+ * - [data-recent-row-count] computed font-weight === '600' for
21
+ * COLD rows (count < 10)
22
+ * - Same selector with [data-recent-row-count-hot='true'] computed
23
+ * font-weight === '700' for HOT rows
24
+ * - R319 single-tier-drop + R317 inactive gray-400 + R318 active
25
+ * font-medium + R294 pulse absent all preserved.
26
+ */
27
+ import { chromium } from 'playwright';
28
+ import { readFileSync } from 'node:fs';
29
+
30
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
31
+ const browser = await chromium.launch({ headless: true });
32
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
33
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
34
+ await ctx.addInitScript(() => {
35
+ try {
36
+ localStorage.setItem('anet-theme', 'cyber');
37
+ sessionStorage.setItem('anet_v3_auth', '1');
38
+ } catch {}
39
+ });
40
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
41
+ await ctx.route('**/api/hub/status*', async (route) => {
42
+ const r = await route.fetch();
43
+ const b = await r.json();
44
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
45
+ const mk = (alias) => ({
46
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
47
+ network_id: nid, project_dir: null,
48
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
49
+ });
50
+ await route.fulfill({ response: r, json: { ...b, sessions: [
51
+ mk('alpha'), mk('beta'), mk('gamma'),
52
+ ] } });
53
+ });
54
+ // Mock messages — the recent-signal panel counts INDIVIDUAL records
55
+ // locally (the API's `count` field on aggregated records is not
56
+ // what drives the per-row tally). Inject 4 individual cold-flow
57
+ // records + 12 hot-flow records so the panel aggregates to count=4
58
+ // and count=12 respectively.
59
+ const nowIso = new Date().toISOString();
60
+ const mkMsg = (i, from_alias, to_alias) => ({
61
+ from_alias, to_alias, content: `m${i}`, last_at: nowIso, count: 1,
62
+ });
63
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
64
+ ...Array.from({ length: 4 }, (_, i) => mkMsg(`a${i}`, 'alpha', 'beta')),
65
+ ...Array.from({ length: 12 }, (_, i) => mkMsg(`b${i}`, 'beta', 'gamma')),
66
+ ] } }));
67
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
68
+
69
+ const page = await ctx.newPage();
70
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
71
+ await page.waitForSelector('[data-recent-row-count]', { timeout: 15000 });
72
+ await page.waitForTimeout(400);
73
+
74
+ const probe = await page.evaluate(() => {
75
+ const counts = Array.from(document.querySelectorAll('[data-recent-row-count]'));
76
+ return {
77
+ rows: counts.map(c => ({
78
+ text: c.textContent,
79
+ isHot: c.getAttribute('data-recent-row-count-hot') === 'true',
80
+ fw: getComputedStyle(c).fontWeight,
81
+ })),
82
+ r317InactiveLayoutCls:
83
+ document.querySelector('[data-topo-chrome-layout="grid"]')?.className ?? '',
84
+ r317ActiveLayoutCls:
85
+ document.querySelector('[data-topo-chrome-layout="ring"]')?.className ?? '',
86
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
87
+ };
88
+ });
89
+ await browser.close();
90
+
91
+ const cold = probe.rows.filter(r => !r.isHot);
92
+ const hot = probe.rows.filter(r => r.isHot);
93
+
94
+ const results = {
95
+ at_least_one_cold_row: cold.length >= 1,
96
+ at_least_one_hot_row: hot.length >= 1,
97
+ all_cold_fw_600: cold.every(r => String(r.fw) === '600'),
98
+ all_hot_fw_700: hot.every(r => String(r.fw) === '700'),
99
+ // R319 regression: single-tier behaviour on group pips. Not directly
100
+ // probed here; topo-group-pip-singletier-drop-test.mjs covers that.
101
+ // R317 / R318 regression — layout toggle styling. Default layout
102
+ // is 'ring' (active). Grid is inactive and carries gray-400 (R317).
103
+ // Active ring carries cyan-300 + font-medium (R318).
104
+ r317_inactive_gray_400: probe.r317InactiveLayoutCls.includes('text-gray-400'),
105
+ r318_active_font_medium: probe.r317ActiveLayoutCls.includes('font-medium'),
106
+ r294_pulse_absent: probe.pulseCount === 0,
107
+ };
108
+ const ok = Object.values(results).every(Boolean);
109
+ console.log(`${ok ? '✅' : '❌'} recent-row count weight:`, JSON.stringify(results),
110
+ '\n cold rows:', cold,
111
+ '\n hot rows:', hot);
112
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,107 @@
1
+ /* Round 203 verification: recent-signal panel rows fade in on mount,
2
+ * not just when the panel itself first appears (R175). Pre-R203 a new
3
+ * flow rising into the top-3 list snap-popped in; R203 attaches
4
+ * anet-fade-in per-row so the entrance eases.
5
+ *
6
+ * Key={link.key} preserves stable identity across re-renders — the
7
+ * animation plays once per mount, not on every count update.
8
+ *
9
+ * Test (cyber dark, 3 flows already present at load):
10
+ * 1. Three recent-signal rows render with data-recent-row.
11
+ * 2. Each row's className includes 'anet-fade-in' AND
12
+ * 'anet-topo-svg-focus' (existing focus class kept).
13
+ * 3. CSS animationName resolves to a non-empty value matching the
14
+ * anet-fade-in keyframe (we read it via getComputedStyle — the
15
+ * browser's compiled CSS-rule selector binds the class to the
16
+ * animation).
17
+ * 4. Existing R143 row-lift inline transition is intact
18
+ * (transform 150ms cubic-bezier).
19
+ * 5. Existing R160 pip and R191 ts elements still render inside
20
+ * each row — no markup regression from the className change.
21
+ */
22
+ import { chromium } from 'playwright';
23
+ import { readFileSync } from 'node:fs';
24
+
25
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
26
+ const browser = await chromium.launch({ headless: true });
27
+
28
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
32
+ });
33
+
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: 'cli-claude-code',
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
+
49
+ const now = Date.now();
50
+ const msgs = [
51
+ { id: 'a', from_alias: 'alpha', to_alias: 'beta', content: 'hi',
52
+ network_id: 'default', created_at: new Date(now - 3000).toISOString() },
53
+ { id: 'b', from_alias: 'beta', to_alias: 'gamma', content: 'hi',
54
+ network_id: 'default', created_at: new Date(now - 6000).toISOString() },
55
+ { id: 'c', from_alias: 'gamma', to_alias: 'delta', content: 'hi',
56
+ network_id: 'default', created_at: new Date(now - 9000).toISOString() },
57
+ ];
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 === 4, { timeout: 30000 });
64
+ await page.waitForSelector('[data-recent-row]', { timeout: 10000 });
65
+
66
+ // Wait past panel R175 700ms delay + 150ms row animation so the rows
67
+ // have finished their entrance and computed style shows the resting
68
+ // state with the animation declared in CSS but completed.
69
+ await page.waitForTimeout(1100);
70
+
71
+ const rows = await page.evaluate(() => {
72
+ return [...document.querySelectorAll('[data-recent-row]')].map(el => {
73
+ const cs = getComputedStyle(el);
74
+ const pip = el.querySelector('[data-recent-row-freshness]');
75
+ const ts = el.querySelector('[data-recent-row-ts]');
76
+ return {
77
+ key: el.getAttribute('data-recent-row'),
78
+ className: el.getAttribute('class') || '',
79
+ animationName: cs.animationName,
80
+ animationDuration: cs.animationDuration,
81
+ inlineTransition: el.style.transition,
82
+ hasFreshnessPip: !!pip,
83
+ hasTimestamp: !!ts,
84
+ };
85
+ });
86
+ });
87
+
88
+ await browser.close();
89
+
90
+ const has = (s, c) => (s || '').split(/\s+/).includes(c);
91
+
92
+ const results = {
93
+ three_rows_present: rows.length === 3,
94
+ all_have_fadein_class: rows.every(r => has(r.className, 'anet-fade-in')),
95
+ all_keep_focus_class: rows.every(r => has(r.className, 'anet-topo-svg-focus')),
96
+ all_have_animation_bound: rows.every(r => /anet-fade-in|fadein/i.test(r.animationName)
97
+ || r.animationName.includes('anet-fade-in')),
98
+ // R143 row-lift transition still in inline style
99
+ all_keep_transform_transition: rows.every(r => /transform\s+(?:150ms|0\.15s)/.test(r.inlineTransition || '')),
100
+ // R160 R191 markup contracts unbroken
101
+ all_have_pip: rows.every(r => r.hasFreshnessPip === true),
102
+ all_have_ts: rows.every(r => r.hasTimestamp === true),
103
+ };
104
+ const ok = Object.values(results).every(Boolean);
105
+ console.log(`${ok ? '✅' : '❌'} recent-row fade-in:`, JSON.stringify(results),
106
+ `\n rows:`, rows);
107
+ process.exit(ok ? 0 : 1);