@sleep2agi/agent-network-dashboard 0.4.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +31 -31
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/routes-manifest.json +16 -0
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +2 -2
  17. package/.next/server/app/_not-found.rsc +12 -12
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  19. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  20. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  24. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  25. package/.next/server/app/admin.html +2 -2
  26. package/.next/server/app/admin.rsc +14 -14
  27. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  28. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  29. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  30. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  31. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  32. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  33. package/.next/server/app/api/hub/server/[hostname]/agents/route/app-paths-manifest.json +3 -0
  34. package/.next/server/app/api/hub/server/[hostname]/agents/route/build-manifest.json +9 -0
  35. package/.next/server/app/api/hub/server/[hostname]/agents/route/server-reference-manifest.json +4 -0
  36. package/.next/server/app/api/hub/server/[hostname]/agents/route.js +7 -0
  37. package/.next/server/app/api/hub/server/[hostname]/agents/route.js.map +5 -0
  38. package/.next/server/app/api/hub/server/[hostname]/agents/route.js.nft.json +1 -0
  39. package/.next/server/app/api/hub/server/[hostname]/agents/route_client-reference-manifest.js +3 -0
  40. package/.next/server/app/api/hub/server/[hostname]/health/route/app-paths-manifest.json +3 -0
  41. package/.next/server/app/api/hub/server/[hostname]/health/route/build-manifest.json +9 -0
  42. package/.next/server/app/api/hub/server/[hostname]/health/route/server-reference-manifest.json +4 -0
  43. package/.next/server/app/api/hub/server/[hostname]/health/route.js +7 -0
  44. package/.next/server/app/api/hub/server/[hostname]/health/route.js.map +5 -0
  45. package/.next/server/app/api/hub/server/[hostname]/health/route.js.nft.json +1 -0
  46. package/.next/server/app/api/hub/server/[hostname]/health/route_client-reference-manifest.js +3 -0
  47. package/.next/server/app/index.html +2 -2
  48. package/.next/server/app/index.rsc +14 -14
  49. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  50. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  55. package/.next/server/app/login.html +2 -2
  56. package/.next/server/app/login.rsc +14 -14
  57. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  58. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  59. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  60. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  61. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  62. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  63. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  64. package/.next/server/app/logs.html +2 -2
  65. package/.next/server/app/logs.rsc +14 -14
  66. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  67. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  68. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  69. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  70. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  71. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  72. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  73. package/.next/server/app/messages.html +2 -2
  74. package/.next/server/app/messages.rsc +14 -14
  75. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  76. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  77. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  78. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  79. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  80. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  81. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app/node.html +2 -2
  83. package/.next/server/app/node.rsc +14 -14
  84. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  85. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  86. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  87. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  88. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  89. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  90. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  91. package/.next/server/app/nodes.html +2 -2
  92. package/.next/server/app/nodes.rsc +14 -14
  93. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  94. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  95. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  96. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  97. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  98. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  99. package/.next/server/app/page_client-reference-manifest.js +1 -1
  100. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  101. package/.next/server/app/server-logs.html +2 -2
  102. package/.next/server/app/server-logs.rsc +14 -14
  103. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  104. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  105. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  106. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  107. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  108. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  109. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  110. package/.next/server/app/settings/networks.html +2 -2
  111. package/.next/server/app/settings/networks.rsc +14 -14
  112. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  113. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  114. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  115. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  116. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  117. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  118. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  119. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  120. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  121. package/.next/server/app/settings/tokens.html +2 -2
  122. package/.next/server/app/settings/tokens.rsc +14 -14
  123. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  124. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  125. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  126. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  127. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  128. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  129. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  130. package/.next/server/app/settings.html +2 -2
  131. package/.next/server/app/settings.rsc +14 -14
  132. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  133. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  134. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  135. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  136. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  137. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  138. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  139. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  140. package/.next/server/app/tasks.html +2 -2
  141. package/.next/server/app/tasks.rsc +14 -14
  142. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  143. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  144. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  145. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  146. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  147. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  148. package/.next/server/app-paths-manifest.json +2 -0
  149. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js +3 -0
  150. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js.map +1 -0
  151. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js +3 -0
  152. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js.map +1 -0
  153. package/.next/server/chunks/[root-of-the-server]__05nyivw._.js +3 -0
  154. package/.next/server/chunks/[root-of-the-server]__05nyivw._.js.map +1 -0
  155. package/.next/server/chunks/[root-of-the-server]__0floe3h._.js +3 -0
  156. package/.next/server/chunks/[root-of-the-server]__0floe3h._.js.map +1 -0
  157. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +1 -1
  158. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -1
  159. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -2
  160. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  161. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  162. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  163. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  164. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  165. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  166. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  167. package/.next/server/middleware-build-manifest.js +3 -3
  168. package/.next/server/pages/404.html +2 -2
  169. package/.next/server/pages/500.html +1 -1
  170. package/.next/server/server-reference-manifest.js +1 -1
  171. package/.next/server/server-reference-manifest.json +1 -1
  172. package/.next/static/chunks/0aauz~36q5n2a.css +2 -0
  173. package/.next/static/chunks/0f~0kgy2pycyc.js +1 -0
  174. package/.next/static/chunks/0l_~q07bhpkcx.js +7 -0
  175. package/.next/static/chunks/1175-dp18a1e_.js +1 -0
  176. package/.next/static/chunks/1552j0415oeal.js +4 -0
  177. package/.next/trace +2 -2
  178. package/.next/trace-build +1 -1
  179. package/.next/types/routes.d.ts +3 -1
  180. package/.next/types/validator.ts +18 -0
  181. package/app/api/hub/server/[hostname]/agents/route.ts +62 -0
  182. package/app/api/hub/server/[hostname]/health/route.ts +58 -0
  183. package/app/api/hub/servers/route.ts +88 -1
  184. package/app/components/CommandPalette.tsx +107 -0
  185. package/app/components/HelpOverlay.tsx +109 -1
  186. package/app/components/ServersDrawer.tsx +181 -5
  187. package/app/components/TopoGraph.tsx +6409 -286
  188. package/app/globals.css +110 -5
  189. package/app/lib/vendorIdentity.ts +14 -3
  190. package/docs/rfc/v0.10.0-hero3-dashboard-upgrade.md +230 -0
  191. package/package.json +2 -2
  192. package/public/sleep2agi-logo.svg +11 -0
  193. package/public/vendors/claude.svg +11 -0
  194. package/public/vendors/minimax.svg +11 -0
  195. package/public/vendors/openai.svg +12 -0
  196. package/screenshots/r293-p0/after-full.png +0 -0
  197. package/screenshots/r293-p0/after-topo.png +0 -0
  198. package/screenshots/r293-p0/baseline-full.png +0 -0
  199. package/screenshots/r293-p0/baseline-topo.png +0 -0
  200. package/screenshots/r293-p0/logo-integrated-full.png +0 -0
  201. package/screenshots/r293-p0/logo-integrated-topo.png +0 -0
  202. package/screenshots/r293-p0/servers-drawer-v0.10.0-full.png +0 -0
  203. package/screenshots/r293-p0/servers-drawer-v0.10.0.png +0 -0
  204. package/screenshots/r293-p0/wave1-after-full.png +0 -0
  205. package/screenshots/r293-p0/wave1-after-topo.png +0 -0
  206. package/scripts/p0-servers-drawer-screenshot.mjs +99 -0
  207. package/scripts/p0-topo-screenshot.mjs +82 -0
  208. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  209. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  210. package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
  211. package/scripts/topo-active-links-chip-self-hover-test.mjs +171 -0
  212. package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
  213. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  214. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  215. package/scripts/topo-active-pulse-ease-test.mjs +120 -0
  216. package/scripts/topo-brand-logo-transition-test.mjs +94 -0
  217. package/scripts/topo-brand-watermark-test.mjs +97 -0
  218. package/scripts/topo-canvas-aria-test.mjs +108 -0
  219. package/scripts/topo-canvas-brand-fade-test.mjs +87 -0
  220. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  221. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  222. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  223. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  224. package/scripts/topo-chip-focus-test.mjs +95 -0
  225. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  226. package/scripts/topo-chip-row-gap-test.mjs +90 -0
  227. package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
  228. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  229. package/scripts/topo-chip-unit-demotion-test.mjs +93 -0
  230. package/scripts/topo-chiprow-data-weight-test.mjs +94 -0
  231. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  232. package/scripts/topo-chrome-active-weight-test.mjs +104 -0
  233. package/scripts/topo-chrome-focus-ring-unify-test.mjs +100 -0
  234. package/scripts/topo-chrome-gap-test.mjs +73 -0
  235. package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
  236. package/scripts/topo-chrome-inactive-color-test.mjs +90 -0
  237. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  238. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  239. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  240. package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
  241. package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
  242. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  243. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  244. package/scripts/topo-chrome-zoom-readout-weight-test.mjs +81 -0
  245. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  246. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  247. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  248. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  249. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  250. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  251. package/scripts/topo-corner-radius-unify-test.mjs +103 -0
  252. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  253. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  254. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  255. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  256. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  257. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  258. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  259. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  260. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  261. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  262. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  263. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  264. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  265. package/scripts/topo-edge-fadein-test.mjs +106 -0
  266. package/scripts/topo-edge-focus-test.mjs +113 -0
  267. package/scripts/topo-edge-hover-test.mjs +13 -5
  268. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  269. package/scripts/topo-edge-pill-test.mjs +105 -0
  270. package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
  271. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  272. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  273. package/scripts/topo-empty-hint-tracking-test.mjs +84 -0
  274. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  275. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  276. package/scripts/topo-empty-state-recenter-test.mjs +107 -0
  277. package/scripts/topo-empty-subhint-tracking-test.mjs +87 -0
  278. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  279. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  280. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  281. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  282. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  283. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  284. package/scripts/topo-filter-pill-count-tabular-test.mjs +135 -0
  285. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  286. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  287. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  288. package/scripts/topo-freshness-chip-weight-test.mjs +90 -0
  289. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  290. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  291. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  292. package/scripts/topo-group-count-test.mjs +6 -1
  293. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  294. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  295. package/scripts/topo-group-label-hover-test.mjs +119 -0
  296. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  297. package/scripts/topo-group-label-tint-test.mjs +99 -0
  298. package/scripts/topo-group-label-title-test.mjs +83 -0
  299. package/scripts/topo-group-pin-test.mjs +119 -0
  300. package/scripts/topo-group-pip-fadein-test.mjs +104 -0
  301. package/scripts/topo-group-pip-singletier-drop-test.mjs +131 -0
  302. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  303. package/scripts/topo-group-status-pips-test.mjs +103 -0
  304. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  305. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  306. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  307. package/scripts/topo-groupbox-live-test.mjs +100 -0
  308. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  309. package/scripts/topo-groupbox-test.mjs +4 -2
  310. package/scripts/topo-header-gap-test.mjs +94 -0
  311. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  312. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  313. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  314. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  315. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  316. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  317. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  318. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  319. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  320. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  321. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  322. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  323. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  324. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  325. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  326. package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
  327. package/scripts/topo-hub-a11y-test.mjs +112 -0
  328. package/scripts/topo-hub-busyness-test.mjs +100 -0
  329. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  330. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  331. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  332. package/scripts/topo-hub-fadein-test.mjs +88 -0
  333. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  334. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  335. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  336. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  337. package/scripts/topo-hub-title-test.mjs +7 -2
  338. package/scripts/topo-hub-working-count-test.mjs +99 -0
  339. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  340. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  341. package/scripts/topo-kicker-color-test.mjs +93 -0
  342. package/scripts/topo-kicker-tracking-test.mjs +101 -0
  343. package/scripts/topo-kicker-weight-test.mjs +98 -0
  344. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  345. package/scripts/topo-label-lift-test.mjs +5 -1
  346. package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
  347. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  348. package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
  349. package/scripts/topo-layout-toggle-test.mjs +131 -0
  350. package/scripts/topo-layout-trailer-mr-test.mjs +82 -0
  351. package/scripts/topo-legend-compress-test.mjs +107 -0
  352. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  353. package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
  354. package/scripts/topo-legend-count-tabular-test.mjs +87 -0
  355. package/scripts/topo-legend-count-test.mjs +88 -0
  356. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  357. package/scripts/topo-legend-count-weight-test.mjs +86 -0
  358. package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
  359. package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
  360. package/scripts/topo-legend-hover-test.mjs +113 -0
  361. package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
  362. package/scripts/topo-legend-offline-label-simplify-test.mjs +86 -0
  363. package/scripts/topo-legend-panel-count-weight-test.mjs +89 -0
  364. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  365. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  366. package/scripts/topo-legend-pin-test.mjs +128 -0
  367. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  368. package/scripts/topo-legend-row-labels-simplify-test.mjs +80 -0
  369. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  370. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  371. package/scripts/topo-legend-row-title-test.mjs +102 -0
  372. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  373. package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
  374. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  375. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  376. package/scripts/topo-minimap-radius-test.mjs +88 -0
  377. package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
  378. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  379. package/scripts/topo-monogram-font-test.mjs +112 -0
  380. package/scripts/topo-monogram-stroke-test.mjs +101 -0
  381. package/scripts/topo-more-flows-letterspacing-test.mjs +111 -0
  382. package/scripts/topo-node-a11y-test.mjs +107 -0
  383. package/scripts/topo-node-alias-chat-pin-test.mjs +95 -0
  384. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  385. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  386. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  387. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  388. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  389. package/scripts/topo-node-title-peers-test.mjs +107 -0
  390. package/scripts/topo-node-title-test.mjs +101 -0
  391. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  392. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  393. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  394. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  395. package/scripts/topo-panel-count-unit-test.mjs +112 -0
  396. package/scripts/topo-panel-elevation-test.mjs +76 -0
  397. package/scripts/topo-panel-fadein-test.mjs +102 -0
  398. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  399. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  400. package/scripts/topo-panel-rx-test.mjs +90 -0
  401. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  402. package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
  403. package/scripts/topo-panel-title-tracking-test.mjs +78 -0
  404. package/scripts/topo-particle-stagger-test.mjs +82 -0
  405. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  406. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  407. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  408. package/scripts/topo-pin-intersection-test.mjs +121 -0
  409. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  410. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  411. package/scripts/topo-pin-persistence-test.mjs +105 -0
  412. package/scripts/topo-prefix-opacity-test.mjs +107 -0
  413. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  414. package/scripts/topo-pressure-hover-test.mjs +123 -0
  415. package/scripts/topo-pressure-pin-test.mjs +119 -0
  416. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  417. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  418. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  419. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  420. package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
  421. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  422. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  423. package/scripts/topo-recent-panel-count-weight-test.mjs +91 -0
  424. package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
  425. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  426. package/scripts/topo-recent-panel-hot-tabular-test.mjs +113 -0
  427. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  428. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  429. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  430. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  431. package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
  432. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  433. package/scripts/topo-recent-row-count-weight-test.mjs +112 -0
  434. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  435. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  436. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  437. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  438. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  439. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  440. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  441. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  442. package/scripts/topo-recent-row-title-test.mjs +112 -0
  443. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  444. package/scripts/topo-recent-row-ts-tabular-test.mjs +108 -0
  445. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  446. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  447. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  448. package/scripts/topo-reset-spin-test.mjs +120 -0
  449. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  450. package/scripts/topo-spokes-retired-test.mjs +93 -0
  451. package/scripts/topo-starfield-density-test.mjs +89 -0
  452. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  453. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  454. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  455. package/scripts/topo-svg-focus-test.mjs +112 -0
  456. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  457. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  458. package/scripts/topo-tier-guide-dashes-test.mjs +86 -0
  459. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  460. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  461. package/scripts/topo-tier-ring-test.mjs +69 -0
  462. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  463. package/scripts/topo-tier-stagger-test.mjs +89 -0
  464. package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
  465. package/scripts/topo-title-tracking-test.mjs +98 -0
  466. package/scripts/topo-titleblock-gap-test.mjs +86 -0
  467. package/scripts/topo-titleblock-margin-test.mjs +83 -0
  468. package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
  469. package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
  470. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  471. package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
  472. package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
  473. package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
  474. package/scripts/topo-vendor-chip-weight-test.mjs +87 -0
  475. package/scripts/topo-vendor-count-suffix-test.mjs +95 -0
  476. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  477. package/scripts/topo-vendor-pill-test.mjs +125 -0
  478. package/scripts/topo-vendor-pin-test.mjs +125 -0
  479. package/scripts/topo-view-smooth-test.mjs +131 -0
  480. package/scripts/topo-watermark-tracking-test.mjs +99 -0
  481. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  482. package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
  483. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  484. package/scripts/topo-wrapper-radius-test.mjs +73 -0
  485. package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
  486. package/.next/static/chunks/088ouqy7tekty.js +0 -2
  487. package/.next/static/chunks/0hyl7mdapsz38.js +0 -1
  488. package/.next/static/chunks/0mqwn-l7wtcdo.js +0 -7
  489. package/.next/static/chunks/127kw7~wsnyu7.css +0 -2
  490. package/.next/static/chunks/16yfxp9ak2gdm.js +0 -1
  491. /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_buildManifest.js +0 -0
  492. /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_clientMiddlewareManifest.js +0 -0
  493. /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_ssgManifest.js +0 -0
@@ -0,0 +1,112 @@
1
+ /* Round 148 verification: recent-signal row gains a <title> tooltip
2
+ * exposing full message context — the row text truncates aliases to
3
+ * 6 chars (R127) and content to 8, useful for scan-density but
4
+ * obscures detail. Native SVG <title> reveals the full alias /
5
+ * content / timestamp on hover. Pinned vs unpinned switches the
6
+ * click-hint so the user knows the next gesture's effect.
7
+ *
8
+ * Mirrors R98's enriched node tooltip at the per-row scope.
9
+ *
10
+ * Fleet: alpha sends 12 msgs to beta-with-long-name (HOT), and
11
+ * beta-with-long-name sends 4 msgs to gamma (warm). Content is
12
+ * a long string to verify untruncated display.
13
+ *
14
+ * States tested:
15
+ * - hot row: title contains full from / to / msg count + "hot lane"
16
+ * marker + full content + ISO timestamp + pin hint
17
+ * - warm row: same shape minus the "hot lane" marker
18
+ * - pinned: click-hint flips to "click to release pin"
19
+ */
20
+ import { chromium } from 'playwright';
21
+ import { readFileSync } from 'node:fs';
22
+
23
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
24
+ const browser = await chromium.launch({ headless: true });
25
+
26
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
27
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
28
+ await ctx.addInitScript(() => {
29
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
30
+ });
31
+
32
+ // Aliases longer than the row truncation cap (6) so the title's
33
+ // untruncated form is meaningfully different from what's painted.
34
+ const aliasA = 'alpha-runner';
35
+ const aliasB = 'beta-with-long-name';
36
+ const aliasC = 'gamma';
37
+ const longContent = 'this is the full long message body that the row truncates';
38
+
39
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
40
+ await ctx.route('**/api/hub/status*', async (route) => {
41
+ const r = await route.fetch();
42
+ const b = await r.json();
43
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
44
+ const mk = (alias, status) => ({
45
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
46
+ network_id: nid, project_dir: null,
47
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
48
+ });
49
+ await route.fulfill({ response: r, json: { ...b, sessions: [
50
+ mk(aliasA, 'working'), mk(aliasB, 'working'), mk(aliasC, 'idle'),
51
+ ] } });
52
+ });
53
+
54
+ const now = Date.now();
55
+ const mkMsg = (id, from, to, ageMs) => ({
56
+ id, from_alias: from, to_alias: to, content: longContent,
57
+ network_id: 'default', created_at: new Date(now - ageMs).toISOString(),
58
+ });
59
+ const msgs = [];
60
+ // 12 alpha→beta = HOT
61
+ for (let i = 0; i < 12; i++) msgs.push(mkMsg(`a${i}`, aliasA, aliasB, 20000 + i * 500));
62
+ // 4 beta→gamma = warm
63
+ for (let i = 0; i < 4; i++) msgs.push(mkMsg(`b${i}`, aliasB, aliasC, 30000 + i * 500));
64
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
65
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
66
+
67
+ const page = await ctx.newPage();
68
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
69
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
70
+ await page.waitForSelector(`[data-recent-row="${aliasA}->${aliasB}"]`, { timeout: 10000 });
71
+ await page.waitForTimeout(400);
72
+
73
+ const readTitle = (key) => page.evaluate((k) => {
74
+ const row = document.querySelector(`[data-recent-row="${k}"]`);
75
+ return row?.querySelector('title')?.textContent || '';
76
+ }, key);
77
+
78
+ const hotTitle = await readTitle(`${aliasA}->${aliasB}`);
79
+ const warmTitle = await readTitle(`${aliasB}->${aliasC}`);
80
+
81
+ // Pin the hot row → title's click-hint flips
82
+ await page.locator(`[data-recent-row="${aliasA}->${aliasB}"]`).click();
83
+ await page.waitForTimeout(200);
84
+ const pinnedTitle = await readTitle(`${aliasA}->${aliasB}`);
85
+
86
+ await browser.close();
87
+
88
+ const results = {
89
+ // Hot row
90
+ hot_hasFullFrom: hotTitle.includes(aliasA),
91
+ hot_hasFullTo: hotTitle.includes(aliasB),
92
+ hot_hasCount12: hotTitle.includes('12 msgs'),
93
+ hot_hasHotMarker: hotTitle.includes('hot lane'),
94
+ hot_hasFullContent: hotTitle.includes(longContent),
95
+ hot_hasTimestamp: /last: \d/.test(hotTitle),
96
+ hot_pinHint: hotTitle.includes('click to pin'),
97
+
98
+ // Warm row
99
+ warm_hasCount4: warmTitle.includes('4 msgs'),
100
+ warm_noHotMarker: !warmTitle.includes('hot lane'),
101
+ warm_pinHint: warmTitle.includes('click to pin'),
102
+
103
+ // Pinned row's title flips hint
104
+ pinned_releaseHint: pinnedTitle.includes('click to release pin'),
105
+ pinned_noPinHint: !pinnedTitle.includes('click to pin · hover to preview'),
106
+ };
107
+ const ok = Object.values(results).every(Boolean);
108
+ console.log(`${ok ? '✅' : '❌'} recent-row title:`, JSON.stringify(results),
109
+ `\n hotTitle:\n${hotTitle}`,
110
+ `\n warmTitle:\n${warmTitle}`,
111
+ `\n pinnedTitle:\n${pinnedTitle}`);
112
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,100 @@
1
+ /* Round 191 verification: recent-signal row timestamp text
2
+ * (right edge) picks up the freshness ramp.
3
+ *
4
+ * Pre-R191 the timestamp was static opacity=0.55 regardless of
5
+ * recency. R191 mirrors R160's left-edge pip with its own
6
+ * alpha range:
7
+ * ≤30s → 0.85
8
+ * 30-300s → 0.85 → 0.30 smooth decay
9
+ * >300s → 0.30 (stale floor)
10
+ *
11
+ * Test:
12
+ * 3 flows at 5s / 90s / 360s ago. Probe their timestamp text
13
+ * on the right of each row.
14
+ * - Fresh: opacity ≈ 0.85
15
+ * - Mid: opacity in (0.55, 0.80)
16
+ * - Stale: opacity ≈ 0.30 (floor)
17
+ * - all have data-recent-row-ts-alpha attr matching
18
+ * - all have transition 'opacity 200ms ease-out'
19
+ */
20
+ import { chromium } from 'playwright';
21
+ import { readFileSync } from 'node:fs';
22
+
23
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
24
+ const browser = await chromium.launch({ headless: true });
25
+
26
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
27
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
28
+ await ctx.addInitScript(() => {
29
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
30
+ });
31
+
32
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
33
+ await ctx.route('**/api/hub/status*', async (route) => {
34
+ const r = await route.fetch();
35
+ const b = await r.json();
36
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
37
+ const mk = (alias) => ({
38
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
39
+ network_id: nid, project_dir: null,
40
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
41
+ });
42
+ await route.fulfill({ response: r, json: { ...b, sessions: [
43
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
44
+ ] } });
45
+ });
46
+
47
+ const now = Date.now();
48
+ const msgs = [
49
+ { id: 'f', from_alias: 'alpha', to_alias: 'beta', content: 'hi',
50
+ network_id: 'default', created_at: new Date(now - 5000).toISOString() },
51
+ { id: 'm', from_alias: 'beta', to_alias: 'gamma', content: 'hi',
52
+ network_id: 'default', created_at: new Date(now - 90000).toISOString() },
53
+ { id: 's', from_alias: 'gamma', to_alias: 'delta', content: 'hi',
54
+ network_id: 'default', created_at: new Date(now - 360000).toISOString() },
55
+ ];
56
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
57
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
58
+
59
+ const page = await ctx.newPage();
60
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
61
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
62
+ await page.waitForSelector('[data-recent-row-ts]', { timeout: 10000 });
63
+ await page.waitForTimeout(400);
64
+
65
+ const probe = await page.evaluate(() => {
66
+ const tss = [...document.querySelectorAll('[data-recent-row-ts]')];
67
+ return tss.map(t => ({
68
+ key: t.getAttribute('data-recent-row-ts'),
69
+ opacity: parseFloat(t.getAttribute('opacity') || ''),
70
+ alphaAttr: parseFloat(t.getAttribute('data-recent-row-ts-alpha') || ''),
71
+ transition: t.style.transition || getComputedStyle(t).transition,
72
+ text: t.textContent,
73
+ }));
74
+ });
75
+
76
+ await browser.close();
77
+
78
+ // Rows are sorted by recency (newest first)
79
+ const [pFresh, pMid, pStale] = probe;
80
+ const hasOpacityTransition = (s) =>
81
+ (s.transition || '').includes('opacity 200ms') ||
82
+ /opacity\s+0\.2s|opacity\s+200ms/.test(s.transition || '');
83
+
84
+ const results = {
85
+ three_timestamps: probe.length === 3,
86
+ fresh_opacity_0p85: Math.abs(pFresh?.opacity - 0.85) < 0.05,
87
+ mid_opacity_window: pMid && pMid.opacity > 0.55 && pMid.opacity < 0.80,
88
+ stale_opacity_0p30: Math.abs(pStale?.opacity - 0.30) < 0.05,
89
+ fresh_alpha_attr_matches: Math.abs(pFresh?.alphaAttr - pFresh?.opacity) < 0.01,
90
+ mid_alpha_attr_matches: Math.abs(pMid?.alphaAttr - pMid?.opacity) < 0.01,
91
+ stale_alpha_attr_matches: Math.abs(pStale?.alphaAttr - pStale?.opacity) < 0.01,
92
+ ladder_descends: pFresh && pMid && pStale && pFresh.opacity > pMid.opacity && pMid.opacity > pStale.opacity,
93
+ all_have_transition: probe.every(p => hasOpacityTransition(p)),
94
+ fresh_text_5s: /^5s$/.test(pFresh?.text || ''),
95
+ mid_text_1m_or_2m: /^[12]m$/.test(pMid?.text || ''),
96
+ };
97
+ const ok = Object.values(results).every(Boolean);
98
+ console.log(`${ok ? '✅' : '❌'} recent-row ts freshness:`, JSON.stringify(results),
99
+ `\n probes=`, probe);
100
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,108 @@
1
+ /* Round 321 verification: recent-signal row freshness timestamp
2
+ * (lastAt) picks up fontVariantNumeric: 'tabular-nums'.
3
+ *
4
+ * The right-aligned (textAnchor="end") timestamp ticks through
5
+ * 1s..59s / 1m..59m / 1h..24h every panel tick. Pre-R321 a 9s→10s
6
+ * or 59s→1m crossing slid the chip ~3px in monospace because the
7
+ * digit '1' has a narrower natural width than '0' / '5' / etc even
8
+ * in mono fonts. Tabular-nums locks the slot.
9
+ *
10
+ * 7th surface in the info-density tabular-nums sweep:
11
+ * R224 edge badge / R225 hub digit / R225 panel header /
12
+ * R225 recent-row count / R229 group-label count /
13
+ * R230 group-label status pips / R321 recent-row timestamp.
14
+ *
15
+ * Contract:
16
+ * - [data-recent-row-ts] computed style fontVariantNumeric
17
+ * contains 'tabular-nums'.
18
+ * - R320 cold-row count still fw=600 + hot-row count fw=700
19
+ * (regression).
20
+ * - R319 single-tier-drop, R317 inactive gray-400, R318 active
21
+ * font-medium, R294 pulse absent all preserved.
22
+ */
23
+ import { chromium } from 'playwright';
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
27
+ const browser = await chromium.launch({ headless: true });
28
+ 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 {
32
+ localStorage.setItem('anet-theme', 'cyber');
33
+ sessionStorage.setItem('anet_v3_auth', '1');
34
+ } catch {}
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: 'claude-code-cli',
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: [ mk('alpha'), mk('beta'), mk('gamma') ] } });
47
+ });
48
+ // Match the shape topo-recent-row-ts-alpha-test uses — individual
49
+ // message records with created_at (the panel derives link.last_at
50
+ // from these). Network_id default matches the sessions' fixture.
51
+ const now = Date.now();
52
+ const mkMsg = (i, from_alias, to_alias, secAgo) => ({
53
+ id: `${from_alias}-${to_alias}-${i}`,
54
+ from_alias, to_alias, content: `m${i}`,
55
+ network_id: 'default',
56
+ created_at: new Date(now - secAgo * 1000).toISOString(),
57
+ });
58
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
59
+ ...Array.from({ length: 4 }, (_, i) => mkMsg(`a${i}`, 'alpha', 'beta', 5 + i)),
60
+ ...Array.from({ length: 12 }, (_, i) => mkMsg(`b${i}`, 'beta', 'gamma', 5 + i)),
61
+ ] } }));
62
+ await ctx.route('**/api/hub/tasks*', (r) => r.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.waitForSelector('[data-recent-row-ts]', { timeout: 15000 });
67
+ await page.waitForTimeout(400);
68
+
69
+ const probe = await page.evaluate(() => {
70
+ const tsels = Array.from(document.querySelectorAll('[data-recent-row-ts]'));
71
+ const counts = Array.from(document.querySelectorAll('[data-recent-row-count]'));
72
+ return {
73
+ tsTabular: tsels.map(t => getComputedStyle(t).fontVariantNumeric),
74
+ tsTexts: tsels.map(t => t.textContent),
75
+ countRows: counts.map(c => ({
76
+ text: c.textContent,
77
+ isHot: c.getAttribute('data-recent-row-count-hot') === 'true',
78
+ fw: getComputedStyle(c).fontWeight,
79
+ })),
80
+ layoutInactiveCls: document.querySelector('[data-topo-chrome-layout="grid"]')?.className ?? '',
81
+ layoutActiveCls: document.querySelector('[data-topo-chrome-layout="ring"]')?.className ?? '',
82
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
83
+ };
84
+ });
85
+ await browser.close();
86
+
87
+ const cold = probe.countRows.filter(r => !r.isHot);
88
+ const hot = probe.countRows.filter(r => r.isHot);
89
+ const hasTab = (s) => /tabular-nums/.test(s || '');
90
+
91
+ const results = {
92
+ ts_at_least_one: probe.tsTabular.length >= 1,
93
+ ts_all_tabular_nums: probe.tsTabular.every(hasTab),
94
+ // R320 regression — count weight tier.
95
+ r320_at_least_one_cold: cold.length >= 1,
96
+ r320_at_least_one_hot: hot.length >= 1,
97
+ r320_cold_fw_600: cold.every(r => String(r.fw) === '600'),
98
+ r320_hot_fw_700: hot.every(r => String(r.fw) === '700'),
99
+ // R317/R318 chrome regression.
100
+ r317_inactive_gray_400: probe.layoutInactiveCls.includes('text-gray-400'),
101
+ r318_active_font_medium: probe.layoutActiveCls.includes('font-medium'),
102
+ r294_pulse_absent: probe.pulseCount === 0,
103
+ };
104
+ const ok = Object.values(results).every(Boolean);
105
+ console.log(`${ok ? '✅' : '❌'} recent-row ts tabular:`, JSON.stringify(results),
106
+ '\n ts texts:', probe.tsTexts,
107
+ '\n ts tabular:', probe.tsTabular);
108
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,95 @@
1
+ /* Round 94 verification: each recent-signal panel row now carries a
2
+ * right-aligned relative timestamp (e.g. `2s` / `1m`). Composes with
3
+ * the R56 hover affordance and R57 panel elevation without changing
4
+ * geometry. Mirror what the R42 active-links chip already shows for
5
+ * the most-recent flow, but per-row so users can see which flows
6
+ * are current vs stale.
7
+ *
8
+ * Drive the dashboard with messages at known ages — 30s, 5m, 2h —
9
+ * and assert each row's timestamp matches the expected glyph. Also
10
+ * verify the timestamp <text> is non-interactive (pointerEvents:none)
11
+ * so it doesn't steal hover from the row hitbox.
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
+
19
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
20
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
21
+ await ctx.addInitScript(() => {
22
+ try {
23
+ localStorage.setItem('anet-theme', 'cyber');
24
+ localStorage.setItem('anet-topo-layout', 'ring');
25
+ sessionStorage.setItem('anet_v3_auth', '1');
26
+ } catch {}
27
+ });
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: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
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: [mk('alpha'), mk('beta'), mk('gamma'), mk('delta')] } });
40
+ });
41
+
42
+ // Mock messages so flowLinks aggregates them. Three flows at increasing
43
+ // ages — the panel sorts most-recent first (per the recent-signal
44
+ // aggregator), so row 0 is the freshest.
45
+ const now = Date.now();
46
+ const mkMsg = (id, from_alias, to_alias, content, ageMs) => ({
47
+ id, from_alias, to_alias, content, network_id: 'default',
48
+ created_at: new Date(now - ageMs).toISOString(),
49
+ });
50
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
51
+ mkMsg('m1', 'alpha', 'beta', 'fix-the-bug', 30 * 1000), // 30s ago → "30s"
52
+ mkMsg('m2', 'gamma', 'delta', 'review-the-pr', 5 * 60 * 1000), // 5m ago → "5m"
53
+ mkMsg('m3', 'alpha', 'gamma', 'deploy-stage', 2 * 60 * 60 * 1000), // 2h ago → "2h"
54
+ ] } }));
55
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
56
+ const page = await ctx.newPage();
57
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
58
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
59
+ // Wait for at least one recent-signal timestamp to render.
60
+ await page.waitForSelector('[data-recent-row-ts]', { timeout: 10000 });
61
+ await page.waitForTimeout(400);
62
+
63
+ const rows = await page.evaluate(() => {
64
+ return [...document.querySelectorAll('[data-recent-row]')].map(g => {
65
+ const rowKey = g.getAttribute('data-recent-row');
66
+ const tsEl = g.querySelector('[data-recent-row-ts]');
67
+ const rowText = g.querySelector('text:not([data-recent-row-ts])')?.textContent || '';
68
+ const tsText = tsEl?.textContent || null;
69
+ const tsPointerEvents = tsEl ? getComputedStyle(tsEl).pointerEvents : null;
70
+ return { rowKey, rowText, tsText, tsPointerEvents };
71
+ });
72
+ });
73
+
74
+ await browser.close();
75
+
76
+ const results = {
77
+ threeRows: rows.length === 3,
78
+ // Row 0 (freshest) should be alpha→beta around "30s" or "29s" etc.
79
+ row0_hasFreshTs: rows[0] && /^[2-9]\d?s$/.test(rows[0].tsText || ''),
80
+ // Row 1 should read 5m (minutes precision).
81
+ row1_minuteTs: rows[1] && /^[4-6]m$/.test(rows[1].tsText || ''),
82
+ // Row 2 should read ~2h.
83
+ row2_hourTs: rows[2] && /^[12]h$/.test(rows[2].tsText || ''),
84
+ // ts <text> must be non-interactive so it doesn't intercept the row hover.
85
+ ts_nonInteractive: rows.every(r => r.tsPointerEvents === 'none'),
86
+ // Row content shows from/to/count/content via the existing layout.
87
+ // R138: arrow notation unified to unicode `→` (was ASCII `->`),
88
+ // matching filter pills / tooltips / edge-badge titles. Assertion
89
+ // flipped to the new typography contract.
90
+ row0_hasArrow: rows[0]?.rowText.includes('→'),
91
+ };
92
+ const ok = Object.values(results).every(Boolean);
93
+ console.log(`${ok ? '✅' : '❌'} recent-row ts:`, JSON.stringify(results),
94
+ `\n rows=`, rows);
95
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,119 @@
1
+ /* Round 138 verification: recent-signal row typography unifies
2
+ * with the rest of the topology UI.
3
+ *
4
+ * Pre-R138 row text: "alpha -> beta / 12 / hi"
5
+ * Post-R138 row text: "alpha → beta · 12 · hi"
6
+ *
7
+ * Why: filter pills (R119), node tooltips (R98), active-links
8
+ * chip tooltip (R114), edge-badge titles all use the unicode
9
+ * arrow `→` and the data-delimiter ` · `. The recent-signal row
10
+ * was the lone holdout using ASCII `->` and ` / `. R138 brings
11
+ * it in line so the row reads like every other surface.
12
+ *
13
+ * Checks:
14
+ * - Row text contains "→" (unicode arrow)
15
+ * - Row text does NOT contain "->" (ASCII)
16
+ * - Row text uses " · " (middle dot) as separator
17
+ * - Row text does NOT contain " / " (forward slash)
18
+ * - Hot count tspan still works (R127 contract preserved)
19
+ * - Row pin still works (R116 contract preserved)
20
+ */
21
+ import { chromium } from 'playwright';
22
+ import { readFileSync } from 'node:fs';
23
+
24
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
25
+ const browser = await chromium.launch({ headless: true });
26
+
27
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
28
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
29
+ await ctx.addInitScript(() => {
30
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
31
+ });
32
+
33
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
34
+ await ctx.route('**/api/hub/status*', async (route) => {
35
+ const r = await route.fetch();
36
+ const b = await r.json();
37
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
38
+ const mk = (alias, status) => ({
39
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
40
+ network_id: nid, project_dir: null,
41
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
42
+ });
43
+ await route.fulfill({ response: r, json: { ...b, sessions: [
44
+ mk('alpha', 'working'), mk('beta', 'working'), mk('gamma', 'idle'),
45
+ ] } });
46
+ });
47
+
48
+ const now = Date.now();
49
+ const mkMsg = (id, from, to, ageMs) => ({
50
+ id, from_alias: from, to_alias: to, content: 'hello',
51
+ network_id: 'default', created_at: new Date(now - ageMs).toISOString(),
52
+ });
53
+ const msgs = [];
54
+ // alpha→beta: 12 msgs (hot)
55
+ for (let i = 0; i < 12; i++) msgs.push(mkMsg(`a${i}`, 'alpha', 'beta', 20000 + i * 500));
56
+ // beta→gamma: 4 msgs (warm)
57
+ for (let i = 0; i < 4; i++) msgs.push(mkMsg(`b${i}`, 'beta', 'gamma', 30000 + i * 500));
58
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
59
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
60
+
61
+ const page = await ctx.newPage();
62
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
63
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
64
+ await page.waitForSelector('[data-recent-row="alpha->beta"]', { timeout: 10000 });
65
+ await page.waitForTimeout(400);
66
+
67
+ const inspect = (key) => page.evaluate((k) => {
68
+ const row = document.querySelector(`[data-recent-row="${k}"]`);
69
+ const text = row?.querySelector('text');
70
+ return {
71
+ fullText: text?.textContent,
72
+ pinned: row?.getAttribute('data-recent-row-pinned'),
73
+ hot: row?.getAttribute('data-recent-row-hot'),
74
+ };
75
+ }, key);
76
+
77
+ const a2b = await inspect('alpha->beta');
78
+ const b2g = await inspect('beta->gamma');
79
+
80
+ // Verify pin still works (R116 invariant)
81
+ await page.locator('[data-recent-row="alpha->beta"]').click();
82
+ await page.waitForTimeout(200);
83
+ const a2bPinned = await inspect('alpha->beta');
84
+ await page.keyboard.press('Escape');
85
+ await page.waitForTimeout(200);
86
+
87
+ await browser.close();
88
+
89
+ // Note: data-recent-row attribute itself still uses "alpha->beta" as
90
+ // the link.key (computed from from + '->' + to upstream). That's
91
+ // internal data, not user-visible text — R138 only touches the
92
+ // rendered <text> content.
93
+ const hasUnicodeArrow = (s) => !!s && s.includes('→');
94
+ const lacksAsciiArrow = (s) => !!s && !s.includes('->');
95
+ const usesMiddleDot = (s) => !!s && s.includes('·');
96
+ const lacksSlash = (s) => !!s && !/\s\/\s/.test(s);
97
+
98
+ const results = {
99
+ a2b_hasUnicodeArrow: hasUnicodeArrow(a2b.fullText),
100
+ a2b_lacksAsciiArrow: lacksAsciiArrow(a2b.fullText),
101
+ a2b_usesMiddleDot: usesMiddleDot(a2b.fullText),
102
+ a2b_lacksSlash: lacksSlash(a2b.fullText),
103
+
104
+ b2g_hasUnicodeArrow: hasUnicodeArrow(b2g.fullText),
105
+ b2g_usesMiddleDot: usesMiddleDot(b2g.fullText),
106
+
107
+ // R127 hot preserved
108
+ a2b_hotAttr: a2b.hot === 'true',
109
+ b2g_notHot: b2g.hot === 'false',
110
+
111
+ // R116 pin preserved
112
+ pin_landed: a2bPinned.pinned === 'true',
113
+ };
114
+ const ok = Object.values(results).every(Boolean);
115
+ console.log(`${ok ? '✅' : '❌'} recent-row typography:`, JSON.stringify(results),
116
+ `\n a2b=`, a2b,
117
+ `\n b2g=`, b2g,
118
+ `\n a2bPinned=`, a2bPinned);
119
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,73 @@
1
+ /* Round 110 verification: recent-signal empty placeholder gains a
2
+ * sub-text hint so the empty state reads as intentional invitation
3
+ * rather than a stuck-loading state. Primary line "no flow yet"
4
+ * stays at the same position semantics (now y=54 instead of y=60
5
+ * to balance the two-line stack within the panel); sub-line at
6
+ * y=68 says "send a message between agents".
7
+ *
8
+ * Verifies:
9
+ * - both lines render when flowLinks is empty
10
+ * - both lines disappear when flowLinks has content
11
+ * - data-recent-signal-empty + data-recent-signal-empty-hint
12
+ * attributes are stable test hooks
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const browser = await chromium.launch({ headless: true });
19
+ const fresh = new Date(Date.now() - 30 * 1000).toISOString();
20
+
21
+ const probe = async (withFlow) => {
22
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1200 } });
23
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
24
+ await ctx.addInitScript(() => {
25
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
26
+ });
27
+ await ctx.route('**/api/hub/status*', async (route) => {
28
+ const r = await route.fetch();
29
+ const b = await r.json();
30
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
31
+ const mk = (alias) => ({
32
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
33
+ network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ });
36
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta')] } });
37
+ });
38
+ const now = Date.now();
39
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: withFlow ? [
40
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'hi',
41
+ network_id: 'default', created_at: new Date(now - 10000).toISOString() },
42
+ ] : [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
46
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
47
+ await page.waitForTimeout(600);
48
+ const data = await page.evaluate(() => ({
49
+ empty: document.querySelector('[data-recent-signal-empty]')?.textContent || null,
50
+ emptyHint: document.querySelector('[data-recent-signal-empty-hint]')?.textContent || null,
51
+ rowCount: document.querySelectorAll('[data-recent-row]').length,
52
+ }));
53
+ await ctx.close();
54
+ return data;
55
+ };
56
+
57
+ const empty = await probe(false);
58
+ const full = await probe(true);
59
+ await browser.close();
60
+
61
+ const results = {
62
+ empty_primaryLineShown: empty.empty === 'no flow yet',
63
+ empty_hintShown: /^send a message between agents$/.test(empty.emptyHint || ''),
64
+ empty_noRows: empty.rowCount === 0,
65
+ full_primaryGone: full.empty === null,
66
+ full_hintGone: full.emptyHint === null,
67
+ full_rowsRendered: full.rowCount >= 1,
68
+ };
69
+ const ok = Object.values(results).every(Boolean);
70
+ console.log(`${ok ? '✅' : '❌'} recent-signal empty hint:`, JSON.stringify(results),
71
+ `\n empty=`, empty,
72
+ `\n full=`, full);
73
+ process.exit(ok ? 0 : 1);