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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (477) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/diagnostics/route-bundle-stats.json +41 -41
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/server/app/_global-error.html +1 -1
  7. package/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/_not-found.html +2 -2
  15. package/.next/server/app/_not-found.rsc +12 -12
  16. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  17. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  18. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  21. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/admin.html +2 -2
  24. package/.next/server/app/admin.rsc +14 -14
  25. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  26. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  27. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  28. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  29. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  30. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  31. package/.next/server/app/index.html +2 -2
  32. package/.next/server/app/index.rsc +14 -14
  33. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  34. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  35. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  36. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  37. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  38. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  39. package/.next/server/app/login.html +2 -2
  40. package/.next/server/app/login.rsc +14 -14
  41. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  42. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  43. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  44. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  45. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  46. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  47. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/logs.html +2 -2
  49. package/.next/server/app/logs.rsc +14 -14
  50. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  55. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  56. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/messages.html +2 -2
  58. package/.next/server/app/messages.rsc +14 -14
  59. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  60. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  61. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  62. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  64. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  65. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app/node.html +2 -2
  67. package/.next/server/app/node.rsc +14 -14
  68. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  69. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  70. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  71. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  73. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  74. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/nodes.html +2 -2
  76. package/.next/server/app/nodes.rsc +14 -14
  77. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  78. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  79. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  80. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  82. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  83. package/.next/server/app/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  85. package/.next/server/app/server-logs.html +2 -2
  86. package/.next/server/app/server-logs.rsc +14 -14
  87. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  88. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  89. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  90. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  91. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  92. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  93. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/settings/networks.html +2 -2
  95. package/.next/server/app/settings/networks.rsc +14 -14
  96. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  97. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  98. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  99. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  100. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  101. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  102. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  103. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  104. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  105. package/.next/server/app/settings/tokens.html +2 -2
  106. package/.next/server/app/settings/tokens.rsc +14 -14
  107. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  108. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  109. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  110. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  111. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  112. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  113. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  114. package/.next/server/app/settings.html +2 -2
  115. package/.next/server/app/settings.rsc +14 -14
  116. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  117. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  118. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  119. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  120. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  121. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  122. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  123. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  124. package/.next/server/app/tasks.html +2 -2
  125. package/.next/server/app/tasks.rsc +14 -14
  126. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  127. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  128. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  129. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  130. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  131. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  132. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
  133. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
  134. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  135. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  136. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
  137. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
  138. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -1
  139. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  140. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  141. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  142. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +1 -1
  143. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -1
  144. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  145. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  146. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
  147. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
  148. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
  149. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
  150. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
  151. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
  152. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
  153. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
  154. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
  155. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
  156. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
  157. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
  158. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  159. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  160. package/.next/server/middleware-build-manifest.js +3 -3
  161. package/.next/server/pages/404.html +2 -2
  162. package/.next/server/pages/500.html +1 -1
  163. package/.next/server/server-reference-manifest.js +1 -1
  164. package/.next/server/server-reference-manifest.json +1 -1
  165. package/.next/static/chunks/0-mpa_947ipeq.js +1 -0
  166. package/.next/static/chunks/01lmbsd37fybu.js +1 -0
  167. package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
  168. package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
  169. package/.next/static/chunks/05uk96gc~9mni.js +1 -0
  170. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  171. package/.next/static/chunks/0ggj5ummw2a84.js +7 -0
  172. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  173. package/.next/static/chunks/0o40uf-rn28q6.js +1 -0
  174. package/.next/static/chunks/0q3hr1~viwzkt.js +4 -0
  175. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  176. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  177. package/.next/static/chunks/0~ks9b48lw8hx.css +2 -0
  178. package/.next/static/chunks/13lvf-h-j8bmc.js +1 -0
  179. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  180. package/.next/trace +2 -2
  181. package/.next/trace-build +1 -1
  182. package/app/components/ChatPopover.tsx +38 -2
  183. package/app/components/CommandPalette.tsx +107 -0
  184. package/app/components/HelpOverlay.tsx +126 -0
  185. package/app/components/TopoGraph.tsx +5927 -258
  186. package/app/components/utils.ts +8 -7
  187. package/app/globals.css +110 -5
  188. package/app/lib/time.ts +36 -0
  189. package/app/lib/vendorIdentity.ts +14 -3
  190. package/package.json +2 -2
  191. package/public/sleep2agi-logo.svg +11 -0
  192. package/public/vendors/claude.svg +11 -0
  193. package/public/vendors/minimax.svg +11 -0
  194. package/public/vendors/openai.svg +12 -0
  195. package/screenshots/r293-p0/after-full.png +0 -0
  196. package/screenshots/r293-p0/after-topo.png +0 -0
  197. package/screenshots/r293-p0/baseline-full.png +0 -0
  198. package/screenshots/r293-p0/baseline-topo.png +0 -0
  199. package/screenshots/r293-p0/logo-integrated-full.png +0 -0
  200. package/screenshots/r293-p0/logo-integrated-topo.png +0 -0
  201. package/scripts/help-overlay-topo-test.mjs +44 -0
  202. package/scripts/p0-topo-screenshot.mjs +82 -0
  203. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  204. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  205. package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
  206. package/scripts/topo-active-links-chip-self-hover-test.mjs +171 -0
  207. package/scripts/topo-active-links-chip-test.mjs +73 -0
  208. package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
  209. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  210. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  211. package/scripts/topo-active-pulse-ease-test.mjs +120 -0
  212. package/scripts/topo-auto-fit-test.mjs +73 -0
  213. package/scripts/topo-brand-watermark-test.mjs +97 -0
  214. package/scripts/topo-canvas-aria-test.mjs +108 -0
  215. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  216. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  217. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  218. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  219. package/scripts/topo-chip-focus-test.mjs +95 -0
  220. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  221. package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
  222. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  223. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  224. package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
  225. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  226. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  227. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  228. package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
  229. package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
  230. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  231. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  232. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  233. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  234. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  235. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  236. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  237. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  238. package/scripts/topo-corner-radius-unify-test.mjs +103 -0
  239. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  240. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  241. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  242. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  243. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  244. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  245. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  246. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  247. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  248. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  249. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  250. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  251. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  252. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  253. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  254. package/scripts/topo-edge-fadein-test.mjs +106 -0
  255. package/scripts/topo-edge-focus-test.mjs +113 -0
  256. package/scripts/topo-edge-hover-test.mjs +93 -0
  257. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  258. package/scripts/topo-edge-pill-test.mjs +105 -0
  259. package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
  260. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  261. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  262. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  263. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  264. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  265. package/scripts/topo-empty-state-recenter-test.mjs +107 -0
  266. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  267. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  268. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  269. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  270. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  271. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  272. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  273. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  274. package/scripts/topo-fit-key-test.mjs +106 -0
  275. package/scripts/topo-freshness-chip-test.mjs +74 -0
  276. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  277. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  278. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  279. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  280. package/scripts/topo-group-count-test.mjs +6 -1
  281. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  282. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  283. package/scripts/topo-group-label-hover-test.mjs +119 -0
  284. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  285. package/scripts/topo-group-label-tint-test.mjs +99 -0
  286. package/scripts/topo-group-label-title-test.mjs +83 -0
  287. package/scripts/topo-group-pin-test.mjs +119 -0
  288. package/scripts/topo-group-pip-fadein-test.mjs +100 -0
  289. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  290. package/scripts/topo-group-status-pips-test.mjs +103 -0
  291. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  292. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  293. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  294. package/scripts/topo-groupbox-live-test.mjs +100 -0
  295. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  296. package/scripts/topo-groupbox-test.mjs +4 -2
  297. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  298. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  299. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  300. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  301. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  302. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  303. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  304. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  305. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  306. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  307. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  308. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  309. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  310. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  311. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  312. package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
  313. package/scripts/topo-hub-a11y-test.mjs +112 -0
  314. package/scripts/topo-hub-busyness-test.mjs +100 -0
  315. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  316. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  317. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  318. package/scripts/topo-hub-fadein-test.mjs +88 -0
  319. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  320. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  321. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  322. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  323. package/scripts/topo-hub-title-test.mjs +93 -0
  324. package/scripts/topo-hub-working-count-test.mjs +99 -0
  325. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  326. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  327. package/scripts/topo-kicker-color-test.mjs +93 -0
  328. package/scripts/topo-kicker-tracking-test.mjs +101 -0
  329. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  330. package/scripts/topo-label-lift-test.mjs +119 -0
  331. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  332. package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
  333. package/scripts/topo-layout-key-test.mjs +91 -0
  334. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  335. package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
  336. package/scripts/topo-layout-toggle-test.mjs +131 -0
  337. package/scripts/topo-legend-compress-test.mjs +107 -0
  338. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  339. package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
  340. package/scripts/topo-legend-count-tabular-test.mjs +87 -0
  341. package/scripts/topo-legend-count-test.mjs +88 -0
  342. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  343. package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
  344. package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
  345. package/scripts/topo-legend-hover-test.mjs +113 -0
  346. package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
  347. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  348. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  349. package/scripts/topo-legend-pin-test.mjs +128 -0
  350. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  351. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  352. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  353. package/scripts/topo-legend-row-title-test.mjs +102 -0
  354. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  355. package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
  356. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  357. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  358. package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
  359. package/scripts/topo-minimap-test.mjs +68 -0
  360. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  361. package/scripts/topo-mobile-chips-test.mjs +103 -0
  362. package/scripts/topo-monogram-font-test.mjs +112 -0
  363. package/scripts/topo-monogram-stroke-test.mjs +101 -0
  364. package/scripts/topo-node-a11y-test.mjs +107 -0
  365. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  366. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  367. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  368. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  369. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  370. package/scripts/topo-node-title-peers-test.mjs +107 -0
  371. package/scripts/topo-node-title-test.mjs +101 -0
  372. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  373. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  374. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  375. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  376. package/scripts/topo-overlap-test.mjs +32 -5
  377. package/scripts/topo-panel-elevation-test.mjs +76 -0
  378. package/scripts/topo-panel-fadein-test.mjs +102 -0
  379. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  380. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  381. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  382. package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
  383. package/scripts/topo-particle-stagger-test.mjs +82 -0
  384. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  385. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  386. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  387. package/scripts/topo-pin-intersection-test.mjs +121 -0
  388. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  389. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  390. package/scripts/topo-pin-persistence-test.mjs +105 -0
  391. package/scripts/topo-popover-meta-test.mjs +78 -0
  392. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  393. package/scripts/topo-pressure-bar-test.mjs +69 -0
  394. package/scripts/topo-pressure-hover-test.mjs +123 -0
  395. package/scripts/topo-pressure-pin-test.mjs +119 -0
  396. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  397. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  398. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  399. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  400. package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
  401. package/scripts/topo-pulse-rate-test.mjs +71 -0
  402. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  403. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  404. package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
  405. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  406. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  407. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  408. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  409. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  410. package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
  411. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  412. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  413. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  414. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  415. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  416. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  417. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  418. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  419. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  420. package/scripts/topo-recent-row-title-test.mjs +112 -0
  421. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  422. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  423. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  424. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  425. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  426. package/scripts/topo-reduced-motion-test.mjs +77 -0
  427. package/scripts/topo-reset-spin-test.mjs +120 -0
  428. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  429. package/scripts/topo-spokes-retired-test.mjs +93 -0
  430. package/scripts/topo-starfield-density-test.mjs +89 -0
  431. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  432. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  433. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  434. package/scripts/topo-svg-focus-test.mjs +112 -0
  435. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  436. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  437. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  438. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  439. package/scripts/topo-tier-ring-test.mjs +69 -0
  440. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  441. package/scripts/topo-tier-stagger-test.mjs +89 -0
  442. package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
  443. package/scripts/topo-title-tracking-test.mjs +98 -0
  444. package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
  445. package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
  446. package/scripts/topo-tz-ghost-test.mjs +69 -0
  447. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  448. package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
  449. package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
  450. package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
  451. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  452. package/scripts/topo-vendor-pill-test.mjs +125 -0
  453. package/scripts/topo-vendor-pin-test.mjs +125 -0
  454. package/scripts/topo-view-smooth-test.mjs +131 -0
  455. package/scripts/topo-watermark-tracking-test.mjs +99 -0
  456. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  457. package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
  458. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  459. package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
  460. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  461. package/.next/static/chunks/01u5zo8b7r_r3.js +0 -1
  462. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  463. package/.next/static/chunks/09nd.t_hd.y6t.css +0 -2
  464. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  465. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  466. package/.next/static/chunks/0fqit.b4j_6rz.js +0 -1
  467. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  468. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  469. package/.next/static/chunks/0pl7l_.2im.j0.js +0 -7
  470. package/.next/static/chunks/0qcfcgdthhos6.js +0 -1
  471. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  472. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  473. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  474. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  475. /package/.next/static/{mgs4E0Fu2JlZsA5DB0XUu → Kkkh4U0z1GCAKfQMsfmjt}/_buildManifest.js +0 -0
  476. /package/.next/static/{mgs4E0Fu2JlZsA5DB0XUu → Kkkh4U0z1GCAKfQMsfmjt}/_clientMiddlewareManifest.js +0 -0
  477. /package/.next/static/{mgs4E0Fu2JlZsA5DB0XUu → Kkkh4U0z1GCAKfQMsfmjt}/_ssgManifest.js +0 -0
@@ -0,0 +1,71 @@
1
+ /* Round 24 verification: working-node pulse duration tiers by sse:N.
2
+ * sse 0-1 → 1.2s (calm)
3
+ * sse 2-3 → 0.9s (active)
4
+ * sse ≥ 4 → 0.7s (busy)
5
+ * Inject sse counts via mocked /api/hub/health → sse_sessions map. */
6
+ import { chromium } from 'playwright';
7
+ import { readFileSync } from 'node:fs';
8
+
9
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
10
+ const browser = await chromium.launch({ headless: true });
11
+
12
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
13
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
14
+ await ctx.addInitScript(() => {
15
+ try {
16
+ localStorage.setItem('anet-theme', 'cyber');
17
+ localStorage.setItem('anet-topo-layout', 'grid');
18
+ sessionStorage.setItem('anet_v3_auth', '1');
19
+ } catch {}
20
+ });
21
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
22
+ // Three working sessions with different sse counts. Use a known network_id
23
+ // so the `${nid}:${alias}` sse-map key works (the page's grouped lookup).
24
+ const NID = 'net_test';
25
+ await ctx.route('**/api/hub/status*', async (route) => {
26
+ await route.fulfill({ json: {
27
+ ok: true,
28
+ sessions: ['calm', 'active', 'busy'].map(alias => ({
29
+ alias, status: 'working', network_id: NID, project_dir: null,
30
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
31
+ })),
32
+ } });
33
+ });
34
+ await ctx.route('**/api/hub/health*', async (route) => {
35
+ await route.fulfill({ json: {
36
+ ok: true,
37
+ version: '0.8.0-test',
38
+ sse_sessions: {
39
+ [`${NID}:calm`]: 1,
40
+ [`${NID}:active`]: 2,
41
+ [`${NID}:busy`]: 4,
42
+ },
43
+ } });
44
+ });
45
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
46
+ const page = await ctx.newPage();
47
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
48
+ await page.waitForFunction(() => {
49
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
50
+ return !!svg && svg.querySelectorAll('g[data-node]').length === 3;
51
+ }, { timeout: 30000 }).catch(() => {});
52
+ await page.waitForTimeout(600);
53
+
54
+ const durOf = alias => page.$eval(
55
+ `g[data-node="${alias}"] circle[data-pulse-dur]`,
56
+ el => el.getAttribute('data-pulse-dur'),
57
+ ).catch(() => null);
58
+
59
+ const calm = await durOf('calm');
60
+ const active = await durOf('active');
61
+ const busy = await durOf('busy');
62
+
63
+ await browser.close();
64
+ const results = {
65
+ calmIs1_2s: calm === '1.2s',
66
+ activeIs0_9s: active === '0.9s',
67
+ busyIs0_7s: busy === '0.7s',
68
+ };
69
+ const ok = Object.values(results).every(Boolean);
70
+ console.log(`${ok ? '✅' : '❌'} pulse rate:`, JSON.stringify(results), `calm=${calm} active=${active} busy=${busy}`);
71
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,109 @@
1
+ /* Round 162 verification: recent-signal panel header "N flows"
2
+ * tspan picks up the R160/R161 freshness vocabulary.
3
+ *
4
+ * R10 fades canvas edge by freshness (per-edge stroke opacity)
5
+ * R160 brought it to the recent-signal panel rows (per-flow pip)
6
+ * R161 brought it to the chip-row "active links" chip bullet
7
+ * (fleet aggregate in the chip-row above the SVG)
8
+ * R162 brings it to the recent-signal panel HEADER count
9
+ * (fleet aggregate inside the SVG corner)
10
+ *
11
+ * The chip-row "N active links · last 5s" and the panel-header
12
+ * "N flows" show the same metric at two scopes. R161 tinted the
13
+ * chip-row side; R162 tints the panel-side mirror so both
14
+ * scopes speak the same freshness vocabulary side-by-side.
15
+ *
16
+ * Same alpha ramp as R160/R161:
17
+ * ageSec ≤ 30 → 1.0 (fully fresh)
18
+ * 30-300s → smooth decay 1.0 → 0.25
19
+ * > 300s → 0.25 stale floor
20
+ *
21
+ * Hot tail (R129 amber " · N hot") is volume-tinted and stays
22
+ * unchanged — recency tints the head; volume colors the tail.
23
+ *
24
+ * Test: 3 page loads, 5s / 90s / 360s ago.
25
+ */
26
+ import { chromium } from 'playwright';
27
+ import { readFileSync } from 'node:fs';
28
+
29
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
30
+ const browser = await chromium.launch({ headless: true });
31
+
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 { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
36
+ });
37
+
38
+ async function probe(msgAgeSec) {
39
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
40
+ await ctx.unroute('**/api/hub/status*').catch(() => {});
41
+ await ctx.unroute('**/api/hub/messages*').catch(() => {});
42
+ await ctx.unroute('**/api/hub/tasks*').catch(() => {});
43
+ await ctx.route('**/api/hub/status*', async (route) => {
44
+ const r = await route.fetch();
45
+ const b = await r.json();
46
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
47
+ const mk = (alias) => ({
48
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
49
+ network_id: nid, project_dir: null,
50
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
51
+ });
52
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta')] } });
53
+ });
54
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
55
+ { id: 'x', from_alias: 'alpha', to_alias: 'beta', content: 'hi',
56
+ network_id: 'default', created_at: new Date(Date.now() - msgAgeSec * 1000).toISOString() },
57
+ ] } }));
58
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
59
+
60
+ const page = await ctx.newPage();
61
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
62
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
63
+ await page.waitForSelector('[data-recent-panel-count]', { timeout: 10000 });
64
+ await page.waitForTimeout(400);
65
+ const out = await page.evaluate(() => {
66
+ const el = document.querySelector('[data-recent-panel-count]');
67
+ if (!el) return null;
68
+ // SVG tspan fill attribute is the source of truth — getComputedStyle on
69
+ // tspan returns nothing useful on some browsers.
70
+ return {
71
+ alpha: parseFloat(el.getAttribute('data-recent-panel-count-freshness-alpha') || ''),
72
+ fill: el.getAttribute('fill') || '',
73
+ text: el.textContent,
74
+ };
75
+ });
76
+ await page.close();
77
+ return out;
78
+ }
79
+
80
+ const A = await probe(5); // fresh
81
+ const B = await probe(90); // mid
82
+ const C = await probe(360); // stale
83
+
84
+ await browser.close();
85
+
86
+ const cyanRgb = (str) => {
87
+ const m = /rgba?\((\d+),\s*(\d+),\s*(\d+)/.exec(str);
88
+ if (!m) return null;
89
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
90
+ };
91
+
92
+ const results = {
93
+ fresh_dot_found: A !== null,
94
+ mid_dot_found: B !== null,
95
+ stale_dot_found: C !== null,
96
+ fresh_alpha_1: A && Math.abs(A.alpha - 1.0) < 0.05,
97
+ mid_alpha_window: B && B.alpha > 0.7 && B.alpha < 0.95,
98
+ stale_alpha_floor: C && Math.abs(C.alpha - 0.25) < 0.05,
99
+ fresh_color_cyan: A && JSON.stringify(cyanRgb(A.fill)) === '[34,211,238]',
100
+ stale_alpha_in_fill: C && /0\.25/.test(C.fill),
101
+ ladder_descends: A && B && C && A.alpha > B.alpha && B.alpha > C.alpha,
102
+ text_says_1_flow: A && /1 flow/.test(A.text || ''),
103
+ };
104
+ const ok = Object.values(results).every(Boolean);
105
+ console.log(`${ok ? '✅' : '❌'} panel-header count freshness:`, JSON.stringify(results),
106
+ `\n fresh =`, A,
107
+ `\n mid =`, B,
108
+ `\n stale =`, C);
109
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,72 @@
1
+ /* Round 96 verification: the recent-signal panel header count now
2
+ * reads "X flows" off flowLinks.length (matching the deduped rows
3
+ * below), not "X msgs" off the raw messages.length. Pre-R96 a
4
+ * fleet with 10 messages aggregating to 3 unique pairs read
5
+ * "10 msgs" above only 3 rendered rows — read as broken.
6
+ *
7
+ * Mock 5 messages between 2 distinct pairs: alpha↔beta gets 4
8
+ * msgs (4 separate exchanges, dedupes to 1 flow), gamma→delta
9
+ * gets 1 msg (1 flow). Total = 5 msgs, 2 flows. Header should
10
+ * show "2 flows", panel renders 2 rows.
11
+ */
12
+ import { chromium } from 'playwright';
13
+ import { readFileSync } from 'node:fs';
14
+
15
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
16
+ const browser = await chromium.launch({ headless: true });
17
+
18
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
22
+ });
23
+
24
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
25
+ await ctx.route('**/api/hub/status*', async (route) => {
26
+ const r = await route.fetch();
27
+ const b = await r.json();
28
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
29
+ const mk = (alias) => ({
30
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
31
+ network_id: nid, project_dir: null,
32
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
33
+ });
34
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta'), mk('gamma'), mk('delta')] } });
35
+ });
36
+
37
+ const now = Date.now();
38
+ const mkMsg = (id, from_alias, to_alias, content, ageMs) => ({
39
+ id, from_alias, to_alias, content, network_id: 'default',
40
+ created_at: new Date(now - ageMs).toISOString(),
41
+ });
42
+ // 5 messages → 2 flow pairs (alpha→beta × 4, gamma→delta × 1).
43
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
44
+ mkMsg('m1', 'alpha', 'beta', 'ping-1', 60 * 1000),
45
+ mkMsg('m2', 'alpha', 'beta', 'ping-2', 50 * 1000),
46
+ mkMsg('m3', 'alpha', 'beta', 'ping-3', 40 * 1000),
47
+ mkMsg('m4', 'alpha', 'beta', 'ping-4', 30 * 1000),
48
+ mkMsg('m5', 'gamma', 'delta', 'hello', 20 * 1000),
49
+ ] } }));
50
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
51
+ const page = await ctx.newPage();
52
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
53
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
54
+ await page.waitForSelector('[data-recent-panel-count]', { timeout: 10000 });
55
+ await page.waitForTimeout(400);
56
+
57
+ const headerCount = await page.locator('[data-recent-panel-count]').textContent();
58
+ const renderedRows = await page.locator('[data-recent-row]').count();
59
+
60
+ await browser.close();
61
+
62
+ const results = {
63
+ header_saysFlowsNotMsgs: /\bflows\b/.test(headerCount || ''),
64
+ header_count2: /^2 flows/.test(headerCount || ''),
65
+ rows_match_header: renderedRows === 2,
66
+ no_msgs_word: !/\bmsgs?\b/.test(headerCount || ''),
67
+ };
68
+ const ok = Object.values(results).every(Boolean);
69
+ console.log(`${ok ? '✅' : '❌'} recent-panel count:`, JSON.stringify(results),
70
+ `\n header=`, JSON.stringify(headerCount),
71
+ `\n rows=`, renderedRows);
72
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,142 @@
1
+ /* Round 256 verification: recent-signal panel footer breathing room.
2
+ *
3
+ * Pre-R256 the panel was 230×84 with the footer "+ N more flows" text
4
+ * baseline at y=82, fontSize 8. CSS textDecoration:underline (on hover)
5
+ * renders ~3-4px below baseline → y ≈ 85-86, BELOW the panel rect
6
+ * bottom y=84. The hover affordance literally extended past the panel
7
+ * chrome.
8
+ *
9
+ * R256 grows the panel rect from height 84 → 88 (+4 px), keeping
10
+ * footer y=82 and every interior layout coordinate untouched. The
11
+ * underline now sits at y≈85-86 inside an 88-tall panel — 2-3 px of
12
+ * clear room below it, the textDecoration tucks INSIDE the panel
13
+ * border instead of clipping past it.
14
+ *
15
+ * Test scope:
16
+ * 1. Panel rect height === 88 (DOM-side ground truth).
17
+ * 2. Footer baseline y === 82 (didn't shift — only the panel grew).
18
+ * 3. Footer bbox bottom (baseline + descender + reserve for
19
+ * underline) < panel rect bottom y. Use getBBox on the text
20
+ * element and verify it sits inside the rect.
21
+ * 4. Fleet of >3 flows so footer mounts visible (R221 always-mount
22
+ * + opacity gate; gated visible at flowLinks.length > 3).
23
+ * 5. Underline on hover stays inside the panel — hover, wait for
24
+ * transition, measure underline location via the element's
25
+ * bounding rect vs the panel's bounding rect.
26
+ *
27
+ * Geometric check (sanity): panel corner (16+230, 16+88) = (246, 104).
28
+ * Distance to canvas center (500, 330) = √(254² + 226²) ≈ 339.99.
29
+ * Outermost ring radius is 325; corner clears the ring (> 325 → ok).
30
+ * Panel still tucked outside any node.
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
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
38
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
39
+ await ctx.addInitScript(() => {
40
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
41
+ });
42
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
43
+
44
+ // Mock sessions: 4 working agents (alpha/beta/gamma/delta)
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: [
55
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
56
+ ] } });
57
+ });
58
+
59
+ // 5 messages across 5 distinct flow pairs so flowLinks.length === 5
60
+ // (> 3 → footer mounts visible, the "+ 2 more flows" hint).
61
+ const now = Date.now();
62
+ const msgs = [
63
+ { id: 'm0', from_alias: 'alpha', to_alias: 'beta', content: 'hi 1', network_id: 'default', created_at: new Date(now - 1000).toISOString() },
64
+ { id: 'm1', from_alias: 'beta', to_alias: 'gamma', content: 'hi 2', network_id: 'default', created_at: new Date(now - 1500).toISOString() },
65
+ { id: 'm2', from_alias: 'gamma', to_alias: 'delta', content: 'hi 3', network_id: 'default', created_at: new Date(now - 2000).toISOString() },
66
+ { id: 'm3', from_alias: 'delta', to_alias: 'alpha', content: 'hi 4', network_id: 'default', created_at: new Date(now - 2500).toISOString() },
67
+ { id: 'm4', from_alias: 'alpha', to_alias: 'gamma', content: 'hi 5', network_id: 'default', created_at: new Date(now - 3000).toISOString() },
68
+ ];
69
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: msgs } }));
70
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
71
+
72
+ const page = await ctx.newPage();
73
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
74
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
75
+ await page.waitForSelector('[data-recent-panel-more-nav]', { timeout: 10000 });
76
+ await page.waitForTimeout(500);
77
+
78
+ const probe = await page.evaluate(() => {
79
+ const panelG = document.querySelector('[data-topo-panel="recent"]');
80
+ const rect = panelG?.querySelector('rect');
81
+ const footer = document.querySelector('[data-recent-panel-more]');
82
+ const tx = panelG?.getAttribute('transform').match(/translate\(([\d.]+),\s*([\d.]+)\)/);
83
+ const panelTopY = tx ? +tx[2] : null;
84
+ const rectH = rect ? +rect.getAttribute('height') : null;
85
+ const footerY = footer ? +footer.getAttribute('y') : null;
86
+ // Use getBoundingClientRect for both — getBBox on text inside a
87
+ // transformed <g> with opacity transition can return zero on some
88
+ // browser stacks (rev-checked against earlier R203/R215 idioms).
89
+ // Client-rect math gives consistent ground truth.
90
+ const rectCR = rect ? rect.getBoundingClientRect() : null;
91
+ const footerCR = footer ? footer.getBoundingClientRect() : null;
92
+ return {
93
+ panelTopY, rectH, footerY,
94
+ rectCR_bottom: rectCR ? rectCR.bottom : null,
95
+ footerCR_bottom: footerCR ? footerCR.bottom : null,
96
+ };
97
+ });
98
+
99
+ // Hover the footer + measure underline-region overflow vs panel
100
+ await page.locator('[data-recent-panel-more-nav]').hover();
101
+ await page.waitForTimeout(250);
102
+
103
+ const hoverProbe = await page.evaluate(() => {
104
+ const panelG = document.querySelector('[data-topo-panel="recent"]');
105
+ const rect = panelG?.querySelector('rect');
106
+ const footer = document.querySelector('[data-recent-panel-more]');
107
+ return {
108
+ hovered: footer?.getAttribute('data-recent-panel-more-hovered'),
109
+ textDecoration: footer ? window.getComputedStyle(footer).textDecorationLine : null,
110
+ rectClientBottom: rect?.getBoundingClientRect().bottom ?? null,
111
+ footerClientBottom: footer?.getBoundingClientRect().bottom ?? null,
112
+ };
113
+ });
114
+ await browser.close();
115
+
116
+ // At rest: footer's client-rect bottom should sit at or above (less than)
117
+ // the panel rect's client-rect bottom. Allow 1px sub-pixel slop.
118
+ const restClearance = (probe.rectCR_bottom != null && probe.footerCR_bottom != null)
119
+ ? (probe.rectCR_bottom - probe.footerCR_bottom) : null;
120
+
121
+ // Hover-state: same check, but the underline-on-hover would have shifted
122
+ // the visible bottom downward by the underline's offset. Verifies it
123
+ // still tucks INSIDE the panel chrome.
124
+ const hoverClearance = (hoverProbe.rectClientBottom != null && hoverProbe.footerClientBottom != null)
125
+ ? (hoverProbe.rectClientBottom - hoverProbe.footerClientBottom) : null;
126
+
127
+ const results = {
128
+ panel_height_88: probe.rectH === 88,
129
+ footer_y_unchanged_at_82: probe.footerY === 82,
130
+ rest_footer_inside_panel: restClearance != null && restClearance >= -1,
131
+ rest_footer_breathing_room: restClearance != null && restClearance >= 2, // at least 2px clear at rest
132
+ hover_state_fires: hoverProbe.hovered === 'true',
133
+ hover_underline_applied: hoverProbe.textDecoration?.includes('underline') === true,
134
+ hover_footer_inside_panel: hoverClearance != null && hoverClearance >= -1,
135
+ };
136
+ const ok = Object.values(results).every(Boolean);
137
+ console.log(`${ok ? '✅' : '❌'} recent panel footer breath:`, JSON.stringify(results),
138
+ '\n panelRectH:', probe.rectH, 'footerY:', probe.footerY,
139
+ '\n rest clearance:', restClearance, 'px (rect bottom', probe.rectCR_bottom, 'footer bottom', probe.footerCR_bottom, ')',
140
+ '\n hover clearance:', hoverClearance, 'px (rect bottom', hoverProbe.rectClientBottom, 'footer bottom', hoverProbe.footerClientBottom, ')',
141
+ '\n hover textDecoration:', hoverProbe.textDecoration);
142
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,132 @@
1
+ /* Round 129 verification: recent-signal panel header gains a
2
+ * " · N hot" amber tail when ≥ 1 flowLink has count ≥ 10.
3
+ *
4
+ * The hot-lane convention's surfaces:
5
+ * R126: canvas midpoint count badge → amber stroke
6
+ * R127: recent-signal row count digit → amber + bold
7
+ * R129: recent-signal panel HEADER → " · N hot" amber suffix
8
+ *
9
+ * Vertical scan path now reads: header → rows → canvas. Each layer
10
+ * answers a different scope: header = "how many hot?", rows =
11
+ * "which ones?", canvas = "where on the topology?".
12
+ *
13
+ * Three states:
14
+ * A. 3 pairs, all count < 10 → header "3 flows", no hot tail
15
+ * B. 5 pairs, 2 of them count ≥ 10 → header "5 flows · 2 hot", amber tail
16
+ * C. 5 pairs, 5 of them count ≥ 10 → header "5 flows · 5 hot"
17
+ */
18
+ import { chromium } from 'playwright';
19
+ import { readFileSync } from 'node:fs';
20
+
21
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
22
+
23
+ async function probe(seedPairs) {
24
+ // seedPairs is an array of [from, to, count] tuples — each pair gets
25
+ // `count` msgs to make link.count == count after dedup.
26
+ const browser = await chromium.launch({ headless: true });
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
+ const aliasSet = new Set();
35
+ for (const [a, b] of seedPairs) { aliasSet.add(a); aliasSet.add(b); }
36
+ const aliases = [...aliasSet];
37
+ await ctx.route('**/api/hub/status*', async (route) => {
38
+ const r = await route.fetch();
39
+ const b = await r.json();
40
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
41
+ const mk = (alias) => ({
42
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
43
+ network_id: nid, project_dir: null,
44
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
45
+ });
46
+ await route.fulfill({ response: r, json: { ...b, sessions: aliases.map(mk) } });
47
+ });
48
+
49
+ const now = Date.now();
50
+ const msgs = [];
51
+ let id = 0;
52
+ for (const [from, to, count] of seedPairs) {
53
+ for (let i = 0; i < count; i++) {
54
+ msgs.push({
55
+ id: `m${id++}`,
56
+ from_alias: from,
57
+ to_alias: to,
58
+ content: 'hi',
59
+ network_id: 'default',
60
+ created_at: new Date(now - (20000 + i * 500 + id * 13)).toISOString(),
61
+ });
62
+ }
63
+ }
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((n) => document.querySelectorAll('g[data-node]').length === n, aliases.length, { timeout: 30000 });
70
+ await page.waitForTimeout(500);
71
+
72
+ const out = await page.evaluate(() => {
73
+ const count = document.querySelector('[data-recent-panel-count]');
74
+ const hot = document.querySelector('[data-recent-panel-hot-count]');
75
+ return {
76
+ countText: count?.textContent,
77
+ hotText: hot?.textContent,
78
+ hotAttr: hot?.getAttribute('data-recent-panel-hot-count'),
79
+ hotFill: hot?.getAttribute('fill'),
80
+ hotWeight: hot?.getAttribute('font-weight'),
81
+ };
82
+ });
83
+
84
+ await browser.close();
85
+ return out;
86
+ }
87
+
88
+ // State A: all warm
89
+ const a = await probe([
90
+ ['alpha', 'beta', 4],
91
+ ['gamma', 'delta', 5],
92
+ ['epsilon', 'zeta', 3],
93
+ ]);
94
+
95
+ // State B: 5 pairs, 2 hot (≥ 10)
96
+ const b = await probe([
97
+ ['alpha', 'beta', 12], // hot
98
+ ['gamma', 'delta', 15], // hot
99
+ ['epsilon', 'zeta', 4],
100
+ ['eta', 'theta', 6],
101
+ ['iota', 'kappa', 8],
102
+ ]);
103
+
104
+ // State C: 5 pairs, all 5 hot
105
+ const c = await probe([
106
+ ['alpha', 'beta', 12],
107
+ ['gamma', 'delta', 11],
108
+ ['epsilon', 'zeta', 14],
109
+ ['eta', 'theta', 13],
110
+ ['iota', 'kappa', 25],
111
+ ]);
112
+
113
+ const amber = '#fbbf24';
114
+
115
+ const results = {
116
+ a_count_3flows: a.countText === '3 flows',
117
+ a_noHotTspan: a.hotText === undefined,
118
+
119
+ b_count_5flows: b.countText === '5 flows',
120
+ b_hot_text: b.hotText === ' · 2 hot',
121
+ b_hot_attr: b.hotAttr === '2',
122
+ b_hot_amberFill: (b.hotFill || '').toLowerCase() === amber,
123
+ b_hot_boldWeight: b.hotWeight === '700',
124
+
125
+ c_count_5flows: c.countText === '5 flows',
126
+ c_hot_text: c.hotText === ' · 5 hot',
127
+ c_hot_attr: c.hotAttr === '5',
128
+ };
129
+ const ok = Object.values(results).every(Boolean);
130
+ console.log(`${ok ? '✅' : '❌'} recent-panel hot header:`, JSON.stringify(results),
131
+ `\n A=`, a, `\n B=`, b, `\n C=`, c);
132
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,124 @@
1
+ /* Round 221 verification: recent-signal panel "+N more" footer
2
+ * always-mounts; visibility crossfades via wrapper <g> opacity
3
+ * instead of React conditional mount/unmount on flowLinks > 3.
4
+ *
5
+ * Pre-R221 a fleet's 4th flow appearing snap-popped the footer
6
+ * into the canvas; tapering back to 3 flows snap-removed it.
7
+ * Same threshold-crossing snap R215 closed for edge midpoint
8
+ * badges, now applied to the recent-panel footer surface.
9
+ *
10
+ * Test scenarios:
11
+ * A. 2 flows (below threshold) → footer in DOM, visible='false',
12
+ * opacity=0, pointerEvents='none',
13
+ * aria-hidden='true', role unset,
14
+ * tabIndex=-1
15
+ * B. 5 flows (visible threshold) → footer visible='true',
16
+ * opacity>0, role='link', tabIndex=0
17
+ * Both: inline transition includes 'opacity 300ms'.
18
+ * moreCount text content clamps at 0 / 2 respectively.
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
+ async function setup(n) {
27
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
28
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
29
+ await ctx.addInitScript(() => {
30
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
31
+ });
32
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
33
+ await ctx.route('**/api/hub/status*', async (route) => {
34
+ const r = await route.fetch();
35
+ const b = await r.json();
36
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
37
+ const mk = (alias) => ({
38
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
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
+ const now = Date.now();
47
+ // Need N distinct flow pairs → mock N messages with distinct alias pairs
48
+ const pairs = [
49
+ ['alpha', 'beta'], ['beta', 'gamma'], ['gamma', 'delta'],
50
+ ['delta', 'alpha'], ['alpha', 'gamma'], ['beta', 'delta'],
51
+ ];
52
+ const msgs = pairs.slice(0, n).map((p, i) => ({
53
+ id: `m${i}`, from_alias: p[0], to_alias: p[1], content: 'hi',
54
+ network_id: 'default', created_at: new Date(now - (1000 + i * 500)).toISOString(),
55
+ }));
56
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: msgs } }));
57
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.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 === 4, { timeout: 30000 });
61
+ await page.waitForSelector('[data-recent-panel-more-nav]', { timeout: 10000, state: 'attached' });
62
+ await page.waitForTimeout(400);
63
+ return page;
64
+ }
65
+
66
+ const probe = async (page) => page.evaluate(() => {
67
+ const el = document.querySelector('[data-recent-panel-more-nav]');
68
+ if (!el) return null;
69
+ const cs = getComputedStyle(el);
70
+ const txt = el.querySelector('[data-recent-panel-more]');
71
+ return {
72
+ visible: el.getAttribute('data-recent-panel-more-visible'),
73
+ role: el.getAttribute('role'),
74
+ tabIndex: el.getAttribute('tabindex'),
75
+ ariaHidden: el.getAttribute('aria-hidden'),
76
+ opacity: parseFloat(cs.opacity),
77
+ computedPointer:cs.pointerEvents,
78
+ transition: el.style.transition,
79
+ moreText: txt?.textContent,
80
+ moreCount: txt?.getAttribute('data-recent-panel-more'),
81
+ };
82
+ });
83
+
84
+ // Scenario A: 2 flows (below threshold)
85
+ const pageA = await setup(2);
86
+ const probeA = await probe(pageA);
87
+ await pageA.close();
88
+
89
+ // Scenario B: 5 flows (above threshold)
90
+ const pageB = await setup(5);
91
+ const probeB = await probe(pageB);
92
+ await pageB.close();
93
+ await browser.close();
94
+
95
+ const results = {
96
+ // Scenario A (2 flows, hidden)
97
+ A_present_in_dom: !!probeA,
98
+ A_visible_false: probeA?.visible === 'false',
99
+ A_opacity_0: probeA?.opacity === 0,
100
+ A_pointer_none: probeA?.computedPointer === 'none',
101
+ A_aria_hidden: probeA?.ariaHidden === 'true',
102
+ A_role_unset: probeA?.role === null || probeA?.role === '',
103
+ A_tabindex_neg: probeA?.tabIndex === '-1',
104
+ A_more_count_0: probeA?.moreCount === '0', // clamped at 0
105
+
106
+ // Scenario B (5 flows, visible)
107
+ B_present_in_dom: !!probeB,
108
+ B_visible_true: probeB?.visible === 'true',
109
+ B_opacity_positive: (probeB?.opacity ?? 0) > 0,
110
+ B_pointer_all: probeB?.computedPointer === 'all',
111
+ B_role_link: probeB?.role === 'link',
112
+ B_tabindex_0: probeB?.tabIndex === '0',
113
+ B_more_count_2: probeB?.moreCount === '2',
114
+ B_text_says_2: /^\+\s*2\s*more flow/.test(probeB?.moreText || ''),
115
+
116
+ // Both scenarios have opacity 300ms transition
117
+ A_has_transition: /opacity\s+(?:300ms|0\.3s)/.test(probeA?.transition || ''),
118
+ B_has_transition: /opacity\s+(?:300ms|0\.3s)/.test(probeB?.transition || ''),
119
+ };
120
+ const ok = Object.values(results).every(Boolean);
121
+ console.log(`${ok ? '✅' : '❌'} recent-panel more crossfade:`, JSON.stringify(results),
122
+ `\n probeA (2 flows hidden):`, probeA,
123
+ `\n probeB (5 flows visible):`, probeB);
124
+ process.exit(ok ? 0 : 1);