@sleep2agi/agent-network-dashboard 0.5.1-preview.14 → 0.5.1-preview.140

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 (283) 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 +32 -32
  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.js.nft.json +1 -1
  84. package/.next/server/app/page_client-reference-manifest.js +1 -1
  85. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  86. package/.next/server/app/server-logs.html +2 -2
  87. package/.next/server/app/server-logs.rsc +14 -14
  88. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  89. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  90. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  91. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  92. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  93. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  94. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  95. package/.next/server/app/settings/networks.html +2 -2
  96. package/.next/server/app/settings/networks.rsc +14 -14
  97. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  98. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  99. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  100. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  101. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  102. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  103. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  104. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  105. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  106. package/.next/server/app/settings/tokens.html +2 -2
  107. package/.next/server/app/settings/tokens.rsc +14 -14
  108. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  109. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  110. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  111. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  112. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  113. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  114. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  115. package/.next/server/app/settings.html +2 -2
  116. package/.next/server/app/settings.rsc +14 -14
  117. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  118. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  119. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  120. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  121. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  122. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  123. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  124. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  125. package/.next/server/app/tasks.html +2 -2
  126. package/.next/server/app/tasks.rsc +14 -14
  127. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  128. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  129. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  130. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  131. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  132. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  133. package/.next/server/chunks/ssr/{[root-of-the-server]__03b.f76._.js → [root-of-the-server]__0sv~g.o._.js} +2 -2
  134. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +1 -0
  135. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  136. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  137. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  138. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  139. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  140. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  141. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  142. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  143. package/.next/server/middleware-build-manifest.js +3 -3
  144. package/.next/server/pages/404.html +2 -2
  145. package/.next/server/pages/500.html +1 -1
  146. package/.next/server/server-reference-manifest.js +1 -1
  147. package/.next/server/server-reference-manifest.json +1 -1
  148. package/.next/static/chunks/{0l_~q07bhpkcx.js → 03a4--7ncekmk.js} +1 -1
  149. package/.next/static/chunks/0a4hmfvj-81x5.css +2 -0
  150. package/.next/static/chunks/0hkc0z2pgava~.js +1 -0
  151. package/.next/static/chunks/0xv-z5onm7655.js +1 -0
  152. package/.next/static/chunks/{03~~oirxz7~vc.js → 0xv9kehogd-35.js} +1 -1
  153. package/.next/static/chunks/18aaelbu9gx4w.js +4 -0
  154. package/.next/trace +2 -2
  155. package/.next/trace-build +1 -1
  156. package/app/components/ServersDrawer.tsx +17 -0
  157. package/app/components/TopoGraph.tsx +2538 -187
  158. package/package.json +1 -1
  159. package/screenshots/v0.10.2-disk-verify/disk-render-full.png +0 -0
  160. package/screenshots/v0.10.2-disk-verify/disk-render.png +0 -0
  161. package/screenshots/v0.11.0-147/after.png +0 -0
  162. package/scripts/p0-147-screenshot.mjs +53 -0
  163. package/scripts/p0-servers-drawer-screenshot.mjs +2 -0
  164. package/scripts/topo-active-links-chip-hover-lift-test.mjs +93 -0
  165. package/scripts/topo-chip-digit-fontweight-test.mjs +105 -0
  166. package/scripts/topo-chip-digit-hover-bold-test.mjs +94 -0
  167. package/scripts/topo-chip-row-group-hover-brighten-test.mjs +107 -0
  168. package/scripts/topo-chip-row-hover-lift-test.mjs +95 -0
  169. package/scripts/topo-chrome-button-hover-lift-test.mjs +94 -0
  170. package/scripts/topo-chrome-fullscreen-icon-sw-test.mjs +92 -0
  171. package/scripts/topo-chrome-reset-icon-sw-test.mjs +80 -0
  172. package/scripts/topo-chrome-segmented-radius-test.mjs +100 -0
  173. package/scripts/topo-chrome-zoom-icon-sw-test.mjs +90 -0
  174. package/scripts/topo-click-ripple-opacity-test.mjs +99 -0
  175. package/scripts/topo-dashboard-version-attr-test.mjs +69 -0
  176. package/scripts/topo-dense-alias-opacity-test.mjs +68 -0
  177. package/scripts/topo-edge-badge-digit-fw-test.mjs +103 -0
  178. package/scripts/topo-edge-badge-fontsize-test.mjs +90 -0
  179. package/scripts/topo-edge-badge-hover-opacity-test.mjs +94 -0
  180. package/scripts/topo-edge-badge-hover-stroke-test.mjs +92 -0
  181. package/scripts/topo-edge-badge-letter-spacing-test.mjs +86 -0
  182. package/scripts/topo-edge-badge-opacity-test.mjs +80 -0
  183. package/scripts/topo-edge-badge-pin-opacity-test.mjs +86 -0
  184. package/scripts/topo-edge-badge-stroke-test.mjs +92 -0
  185. package/scripts/topo-edge-endpoint-hover-sw-test.mjs +127 -0
  186. package/scripts/topo-edge-freshness-floor-test.mjs +99 -0
  187. package/scripts/topo-edge-particle-hover-r-test.mjs +113 -0
  188. package/scripts/topo-edge-particle-radius-test.mjs +76 -0
  189. package/scripts/topo-edge-visible-linecap-test.mjs +89 -0
  190. package/scripts/topo-endpoint-ring-r-hover-test.mjs +89 -0
  191. package/scripts/topo-filter-pill-hover-lift-test.mjs +101 -0
  192. package/scripts/topo-filter-pill-hover-opacity-test.mjs +110 -0
  193. package/scripts/topo-filter-pill-value-fw-test.mjs +88 -0
  194. package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
  195. package/scripts/topo-flow-rail-hover-sw-test.mjs +113 -0
  196. package/scripts/topo-flow-rail-linecap-test.mjs +79 -0
  197. package/scripts/topo-freshness-chip-hierarchy-test.mjs +93 -0
  198. package/scripts/topo-freshness-chip-tabular-test.mjs +41 -0
  199. package/scripts/topo-freshness-floor-lift-test.mjs +92 -0
  200. package/scripts/topo-freshness-suffix-tabular-test.mjs +88 -0
  201. package/scripts/topo-group-box-geom-transition-test.mjs +110 -0
  202. package/scripts/topo-group-box-rx-pin-test.mjs +103 -0
  203. package/scripts/topo-group-box-stroke-test.mjs +105 -0
  204. package/scripts/topo-group-label-count-fontweight-test.mjs +108 -0
  205. package/scripts/topo-group-label-count-fw-test.mjs +100 -0
  206. package/scripts/topo-group-label-fw-pin-test.mjs +99 -0
  207. package/scripts/topo-group-label-letter-spacing-test.mjs +113 -0
  208. package/scripts/topo-group-label-tint-geom-test.mjs +94 -0
  209. package/scripts/topo-group-label-tint-transition-test.mjs +97 -0
  210. package/scripts/topo-group-pip-fontsize-test.mjs +106 -0
  211. package/scripts/topo-hover-detail-body-fw-test.mjs +101 -0
  212. package/scripts/topo-hover-detail-model-fw-test.mjs +98 -0
  213. package/scripts/topo-hover-detail-opacity-test.mjs +98 -0
  214. package/scripts/topo-hover-detail-rx-test.mjs +81 -0
  215. package/scripts/topo-hub-core-fill-hover-test.mjs +85 -0
  216. package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
  217. package/scripts/topo-hub-digit-fw-hover-test.mjs +102 -0
  218. package/scripts/topo-hub-halo-light-trough-test.mjs +88 -0
  219. package/scripts/topo-hub-halo-r-hover-test.mjs +82 -0
  220. package/scripts/topo-hub-halo-radius-test.mjs +86 -0
  221. package/scripts/topo-hub-halo-trough-test.mjs +83 -0
  222. package/scripts/topo-hub-highlight-opacity-test.mjs +88 -0
  223. package/scripts/topo-hub-highlight-radius-test.mjs +90 -0
  224. package/scripts/topo-hub-hover-ring-opacity-test.mjs +96 -0
  225. package/scripts/topo-hub-hover-ring-stroke-test.mjs +86 -0
  226. package/scripts/topo-hub-spoke-hover-opacity-test.mjs +119 -0
  227. package/scripts/topo-hub-spoke-hover-sw-test.mjs +121 -0
  228. package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
  229. package/scripts/topo-label-card-opacity-hover-test.mjs +99 -0
  230. package/scripts/topo-layout-toggle-radius-test.mjs +87 -0
  231. package/scripts/topo-legend-count-active-opacity-test.mjs +102 -0
  232. package/scripts/topo-legend-count-pin-fw-test.mjs +90 -0
  233. package/scripts/topo-legend-label-fontweight-test.mjs +94 -0
  234. package/scripts/topo-legend-pin-ring-stroke-test.mjs +101 -0
  235. package/scripts/topo-legend-row-letter-spacing-test.mjs +101 -0
  236. package/scripts/topo-minimap-offline-opacity-test.mjs +90 -0
  237. package/scripts/topo-minimap-online-hover-opacity-test.mjs +92 -0
  238. package/scripts/topo-minimap-online-opacity-test.mjs +93 -0
  239. package/scripts/topo-minimap-online-radius-test.mjs +85 -0
  240. package/scripts/topo-minimap-viewport-linejoin-test.mjs +75 -0
  241. package/scripts/topo-minimap-viewport-opacity-test.mjs +96 -0
  242. package/scripts/topo-minimap-viewport-rx-test.mjs +85 -0
  243. package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
  244. package/scripts/topo-node-alias-letter-spacing-test.mjs +112 -0
  245. package/scripts/topo-node-halo-hover-opacity-test.mjs +104 -0
  246. package/scripts/topo-node-halo-light-offline-test.mjs +80 -0
  247. package/scripts/topo-node-halo-offline-opacity-test.mjs +87 -0
  248. package/scripts/topo-node-label-card-rx-test.mjs +85 -0
  249. package/scripts/topo-node-pulse-peak-test.mjs +89 -0
  250. package/scripts/topo-node-pulse-trough-test.mjs +91 -0
  251. package/scripts/topo-node-sub-text-fw-test.mjs +75 -0
  252. package/scripts/topo-node-sub-text-letter-spacing-test.mjs +115 -0
  253. package/scripts/topo-overlap-stale-build-guard-test.mjs +66 -0
  254. package/scripts/topo-overlap-test.mjs +42 -1
  255. package/scripts/topo-panel-count-fw-hover-test.mjs +105 -0
  256. package/scripts/topo-panel-stroke-hover-test.mjs +110 -0
  257. package/scripts/topo-pressure-bar-height-test.mjs +92 -0
  258. package/scripts/topo-pressure-kicker-fontweight-test.mjs +76 -0
  259. package/scripts/topo-recent-pip-radius-2-test.mjs +72 -0
  260. package/scripts/topo-recent-pip-radius-test.mjs +76 -0
  261. package/scripts/topo-recent-row-content-opacity-test.mjs +81 -0
  262. package/scripts/topo-recent-row-count-pin-fw-test.mjs +106 -0
  263. package/scripts/topo-recent-row-letter-spacing-test.mjs +109 -0
  264. package/scripts/topo-recent-row-pip-hover-r-test.mjs +104 -0
  265. package/scripts/topo-recent-row-text-fontweight-test.mjs +90 -0
  266. package/scripts/topo-runtime-icon-hover-test.mjs +96 -0
  267. package/scripts/topo-spoke-active-opacity-test.mjs +104 -0
  268. package/scripts/topo-spoke-active-stroke-test.mjs +95 -0
  269. package/scripts/topo-spoke-idle-opacity-test.mjs +91 -0
  270. package/scripts/topo-status-ring-hover-sw-test.mjs +105 -0
  271. package/scripts/topo-vendor-chip-hover-lift-test.mjs +87 -0
  272. package/scripts/topo-vendor-glyph-fontweight-test.mjs +102 -0
  273. package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
  274. package/scripts/topo-vendor-suffix-hover-brighten-test.mjs +87 -0
  275. package/scripts/topo-zoom-level-hover-fw-test.mjs +95 -0
  276. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +0 -1
  277. package/.next/static/chunks/03mfrl9fd9smg.js +0 -1
  278. package/.next/static/chunks/0kt7qs66ra2.2.css +0 -2
  279. package/.next/static/chunks/116zy6jdhuaa2.js +0 -1
  280. package/.next/static/chunks/12m~64tlig.y4.js +0 -4
  281. /package/.next/static/{rDAS8dVgx4ldg7AtRGdUB → nLppKA36xKrZ-Bv6UcCHa}/_buildManifest.js +0 -0
  282. /package/.next/static/{rDAS8dVgx4ldg7AtRGdUB → nLppKA36xKrZ-Bv6UcCHa}/_clientMiddlewareManifest.js +0 -0
  283. /package/.next/static/{rDAS8dVgx4ldg7AtRGdUB → nLppKA36xKrZ-Bv6UcCHa}/_ssgManifest.js +0 -0
@@ -0,0 +1,113 @@
1
+ /* Round 437 verification: flow-rail (dashed underline) strokeWidth
2
+ * hover lift — 1 → 1.5 on (isHoveredEdge || isEndpointHoveredEdge).
3
+ * Pairs with R50 visible-path width lift + R436 endpoint width lift
4
+ * so both edge paint layers thicken together on hover.
5
+ *
6
+ * Contract:
7
+ * - rest: every flow-rail reports stroke-width '1' + lifted='false'
8
+ * - hover one node alias: rails on edges incident to that node
9
+ * report stroke-width '1.5' + lifted='true'; non-incident rails
10
+ * stay rest
11
+ * - source-file probe confirms conditional + transition extension
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
18
+
19
+ const browser = await chromium.launch({ headless: true });
20
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
21
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
22
+ await ctx.addInitScript(() => {
23
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
24
+ });
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, status) => ({
30
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
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: [
35
+ mk('alpha', 'working'),
36
+ mk('beta', 'idle'),
37
+ mk('gamma', 'idle'),
38
+ ] } });
39
+ });
40
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
41
+ json: { messages: [
42
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
43
+ { id: 'm2', from_alias: 'alpha', to_alias: 'gamma', content: 'pong', created_at: fresh, network_id: 'default' },
44
+ { id: 'm3', from_alias: 'gamma', to_alias: 'beta', content: 'pang', created_at: fresh, network_id: 'default' },
45
+ ] },
46
+ }));
47
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
48
+
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForSelector('[data-edge-flow-rail]', { timeout: 15000 });
52
+ await page.waitForTimeout(400);
53
+
54
+ const readAll = () => page.evaluate(() => {
55
+ const ps = [...document.querySelectorAll('[data-edge-flow-rail]')];
56
+ return ps.map(p => ({
57
+ key: p.getAttribute('data-edge-flow-rail'),
58
+ sw: parseFloat(p.getAttribute('data-edge-flow-rail-stroke-width') || '0'),
59
+ sw_a: p.getAttribute('stroke-width'),
60
+ lifted: p.getAttribute('data-edge-flow-rail-lifted'),
61
+ }));
62
+ });
63
+
64
+ const rest = await readAll();
65
+
66
+ let hover = null;
67
+ const box = await page.evaluate(() => {
68
+ const t = document.querySelector('[data-node-alias-text="alpha"]');
69
+ if (!t) return null;
70
+ const node = t.closest('[data-node]');
71
+ const target = node || t;
72
+ const b = target.getBoundingClientRect();
73
+ return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
74
+ });
75
+ if (box) {
76
+ await page.mouse.move(box.x, box.y);
77
+ await page.waitForTimeout(300);
78
+ hover = await readAll();
79
+ await page.mouse.move(0, 0);
80
+ }
81
+
82
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
83
+ const sourceWired = /strokeWidth=\{\(isHoveredEdge \|\| isEndpointHoveredEdge\) \? 1\.5 : 1\}/.test(fileText);
84
+ const sourceTransition = /transition: 'opacity 300ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out'/.test(fileText);
85
+
86
+ await browser.close();
87
+
88
+ const restAllSw1 = rest.every(r => r.sw === 1);
89
+ const restNoneLifted = rest.every(r => r.lifted === 'false');
90
+
91
+ const isAlphaKey = (k) => /alpha/i.test(k);
92
+ const hoverByKey = new Map((hover || []).map(h => [h.key, h]));
93
+ const alphaEntries = [...hoverByKey.entries()].filter(([k]) => isAlphaKey(k));
94
+ const nonAlphaEntries = [...hoverByKey.entries()].filter(([k]) => !isAlphaKey(k));
95
+
96
+ const alphaAllLifted = alphaEntries.length > 0 && alphaEntries.every(([, h]) => h.sw === 1.5 && h.lifted === 'true');
97
+ const nonAlphaRestSw = nonAlphaEntries.every(([, h]) => h.sw === 1 && h.lifted === 'false');
98
+
99
+ const results = {
100
+ rest_count_ge_2: rest.length >= 2,
101
+ rest_all_sw_1: restAllSw1,
102
+ rest_none_lifted: restNoneLifted,
103
+ hover_alpha_count_ge_2: alphaEntries.length >= 2,
104
+ hover_alpha_all_lifted: alphaAllLifted,
105
+ hover_non_alpha_unaffected: nonAlphaRestSw,
106
+ source_strokeWidth_wired: sourceWired,
107
+ source_transition_wired: sourceTransition,
108
+ };
109
+ const ok = Object.values(results).every(Boolean);
110
+ console.log(`${ok ? '✅' : '❌'} flow-rail hover sw lift:`, JSON.stringify(results),
111
+ '\n rest:', JSON.stringify(rest),
112
+ '\n hover:', JSON.stringify(hover));
113
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,79 @@
1
+ /* Round 378 verification: edge flow-path dashed-rail picks up
2
+ * strokeLinecap='round'. Pre-R378 '2 12' dashes painted as sharp
3
+ * 1×2 rectangles; R378 rounds each cap so the dashes read as soft
4
+ * 3-px pills.
5
+ *
6
+ * Contract:
7
+ * - [data-edge-flow-rail] element stroke-linecap attr === 'round'.
8
+ * - data-edge-flow-rail-linecap === 'round'.
9
+ * - Pre-R378 invariants:
10
+ * * strokeWidth='1' preserved
11
+ * * strokeDasharray='2 12' preserved
12
+ * * R57 / R245 / R47 transition list (opacity + stroke) preserved
13
+ * * data-edge-flow-rail key still surfaces the link
14
+ */
15
+ import { chromium } from 'playwright';
16
+ import { readFileSync } from 'node:fs';
17
+
18
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
19
+ const browser = await chromium.launch({ headless: true });
20
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
21
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
22
+ await ctx.addInitScript(() => {
23
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
24
+ });
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ const r = await route.fetch();
28
+ const b = await r.json();
29
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
30
+ const mk = (alias) => ({
31
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
32
+ network_id: nid, project_dir: null,
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
34
+ });
35
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b') ] } });
36
+ });
37
+ const now = Date.now();
38
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
39
+ { id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
40
+ network_id: 'default', created_at: new Date(now - 5000).toISOString() },
41
+ ] } }));
42
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
43
+
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
46
+ await page.waitForSelector('[data-edge-flow-rail]', { timeout: 15000 });
47
+ await page.waitForTimeout(300);
48
+
49
+ const probe = await page.evaluate(() => {
50
+ const rail = document.querySelector('[data-edge-flow-rail]');
51
+ const cs = rail ? getComputedStyle(rail) : null;
52
+ return {
53
+ linecapAttr: rail?.getAttribute('stroke-linecap') ?? null,
54
+ linecapData: rail?.getAttribute('data-edge-flow-rail-linecap') ?? null,
55
+ strokeWidth: rail?.getAttribute('stroke-width') ?? null,
56
+ strokeDasharray: rail?.getAttribute('stroke-dasharray') ?? null,
57
+ transition: cs?.transition ?? null,
58
+ linkKey: rail?.getAttribute('data-edge-flow-rail') ?? null,
59
+ };
60
+ });
61
+
62
+ await browser.close();
63
+
64
+ const hasTrans = (s, prop) =>
65
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
66
+
67
+ const results = {
68
+ linecap_round: probe.linecapAttr === 'round',
69
+ data_linecap_round: probe.linecapData === 'round',
70
+ stroke_width_1: probe.strokeWidth === '1',
71
+ dasharray_2_12: probe.strokeDasharray === '2 12',
72
+ trans_has_opacity: hasTrans(probe.transition, 'opacity'),
73
+ trans_has_stroke: hasTrans(probe.transition, 'stroke'),
74
+ link_key_present: (probe.linkKey || '').length > 0,
75
+ };
76
+ const ok = Object.values(results).every(Boolean);
77
+ console.log(`${ok ? '✅' : '❌'} flow-rail strokeLinecap='round':`, JSON.stringify(results),
78
+ '\n probe:', probe);
79
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,93 @@
1
+ /* Round 410 verification: FreshnessChip body picks up chip-internal-
2
+ * hierarchy — `{sec}` digit (fw=600 data tier) + `s` unit (opacity
3
+ * 0.7 label tier). Extends R333-R341/R362/R369/R389 arc to 10th
4
+ * anchor.
5
+ *
6
+ * Contract:
7
+ * - R275 stale-only render: chip only mounts when sec > 10. Test
8
+ * waits for /api/hub/status to settle, then advances lastSyncRef
9
+ * by 11+ seconds via a long wait (the FreshnessChip ticks every
10
+ * 1s via setInterval). To avoid the slow wait we instead probe
11
+ * the SOURCE-FILE for the wire change, then probe the chip if
12
+ * it happens to mount during the test window.
13
+ * - Source-file:
14
+ * * <span className="font-semibold" data-freshness-chip-digit>
15
+ * * <span className="opacity-70" data-freshness-chip-unit>
16
+ * - If chip mounts at runtime: data-freshness-chip-digit + -unit
17
+ * spans both present
18
+ */
19
+ import { chromium } from 'playwright';
20
+ import { readFileSync } from 'node:fs';
21
+
22
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
23
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
24
+
25
+ const browser = await chromium.launch({ headless: true });
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
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ const mk = (alias) => ({
36
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
37
+ network_id: nid, project_dir: null,
38
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
39
+ });
40
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('alpha') ] } });
41
+ });
42
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
44
+
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForSelector('[data-topo-hub]', { timeout: 15000 });
48
+
49
+ // Wait for FreshnessChip to mount (R275 stale-only: sec > 10). The
50
+ // chip ticks every 1s. Wait 12s so lastSyncRef-based sec crosses 10.
51
+ await page.waitForTimeout(12000);
52
+
53
+ const probe = await page.evaluate(() => {
54
+ const chip = document.querySelector('[data-freshness-chip]');
55
+ if (!chip) return { mounted: false };
56
+ const digit = chip.querySelector('[data-freshness-chip-digit]');
57
+ const unit = chip.querySelector('[data-freshness-chip-unit]');
58
+ return {
59
+ mounted: true,
60
+ chipStale: chip.getAttribute('data-freshness-chip-stale'),
61
+ digitClass: digit?.className ?? null,
62
+ digitText: digit?.textContent ?? null,
63
+ unitClass: unit?.className ?? null,
64
+ unitText: unit?.textContent ?? null,
65
+ };
66
+ });
67
+
68
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
69
+ const sourceHasDigitSpan = /<span className="font-semibold" data-freshness-chip-digit>/.test(fileText);
70
+ const sourceHasUnitSpan = /<span className="opacity-70" data-freshness-chip-unit>s<\/span>/.test(fileText);
71
+
72
+ await browser.close();
73
+
74
+ // Either runtime probe catches the chip OR source-file verification
75
+ // alone establishes the wire change.
76
+ const results = {
77
+ // Source-file canonical wire (deterministic)
78
+ source_digit_span: sourceHasDigitSpan,
79
+ source_unit_span: sourceHasUnitSpan,
80
+ // Runtime: chip might be present if test wait crossed 10s; assertions
81
+ // are conditional on mounted (only enforce structure when chip exists).
82
+ runtime_check_passed: !probe.mounted || (
83
+ probe.chipStale === 'true' &&
84
+ probe.digitClass?.includes('font-semibold') === true &&
85
+ /^\d+$/.test(probe.digitText || '') &&
86
+ probe.unitClass?.includes('opacity-70') === true &&
87
+ probe.unitText === 's'
88
+ ),
89
+ };
90
+ const ok = Object.values(results).every(Boolean);
91
+ console.log(`${ok ? '✅' : '❌'} FreshnessChip chip-internal-hierarchy:`, JSON.stringify(results),
92
+ '\n probe:', probe);
93
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,41 @@
1
+ /* Round 377 verification: FreshnessChip baseClass picks up `tabular-
2
+ * nums`. The chip-row's last untouched chip joins the R224-R357
3
+ * tabular-nums sweep. `font-mono` already gave equal-width glyphs;
4
+ * R377 adds the explicit invariant the rest of the chip row carries.
5
+ *
6
+ * The chip only renders when stale (R275 gate: sec > 10s since
7
+ * last SWR sync). Test verifies the source-file invariant directly
8
+ * (className contains 'tabular-nums') since simulating > 10 s of
9
+ * no SWR refresh in a Playwright test is brittle.
10
+ *
11
+ * Contract:
12
+ * - Source file TopoGraph.tsx contains the new baseClass with
13
+ * 'tabular-nums' between 'font-medium' and 'border'.
14
+ * - Source file mentions R377 round marker.
15
+ * - All preserved invariants: font-mono, font-medium (R313/R315),
16
+ * transition-colors duration-300 (R187), rounded-md, border.
17
+ */
18
+ import { readFileSync } from 'node:fs';
19
+
20
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
21
+
22
+ const baseClassLineMatch = src.match(/const baseClass = "([^"]+)"/);
23
+ const baseClass = baseClassLineMatch?.[1] ?? '';
24
+
25
+ const results = {
26
+ source_has_baseclass: !!baseClassLineMatch,
27
+ baseclass_has_tabular_nums: /\btabular-nums\b/.test(baseClass),
28
+ baseclass_has_font_mono: /\bfont-mono\b/.test(baseClass),
29
+ baseclass_has_font_medium: /\bfont-medium\b/.test(baseClass),
30
+ baseclass_has_rounded_md: /\brounded-md\b/.test(baseClass),
31
+ baseclass_has_border: /\bborder\b/.test(baseClass),
32
+ baseclass_has_transition: /\btransition-colors\b/.test(baseClass),
33
+ baseclass_duration_300: /\bduration-300\b/.test(baseClass),
34
+ // Ensure ordering: font-medium → tabular-nums → border.
35
+ ordering_correct: /font-medium tabular-nums border/.test(baseClass),
36
+ round_marker_present: /Round 377 \/ Loop/.test(src),
37
+ };
38
+ const ok = Object.values(results).every(Boolean);
39
+ console.log(`${ok ? '✅' : '❌'} FreshnessChip baseClass tabular-nums:`, JSON.stringify(results),
40
+ '\n baseClass:', baseClass);
41
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,92 @@
1
+ /* Round 358 verification: freshness-ramp stale floor lifted 0.25 →
2
+ * 0.30 across 3 coordinated scopes:
3
+ * - active-links chip dot (line ~3055)
4
+ * - recent-panel header count (line ~6524)
5
+ * - recent-row freshness pip (line ~7021)
6
+ *
7
+ * The ramp shape stays the same:
8
+ * 0-30s → 1.0 (fully fresh)
9
+ * 30-300s → smooth decay 1.0 → 0.30
10
+ * > 300s → 0.30 (stale floor)
11
+ *
12
+ * R358 polish: stale state becomes ~20 % more readable (0.30 / 0.25
13
+ * = 1.2x) while the fresh-vs-stale ratio stays strong (1.0 / 0.30 ≈
14
+ * 3.3× vs prior 4×). Joins the R317 subordinate-text-lift family
15
+ * at the freshness scope.
16
+ *
17
+ * Test approach: feed a STALE message (~ 600s ago, well past the 300s
18
+ * floor boundary) to the route mock, then read all 3 freshness-alpha
19
+ * data attrs. Each should be '0.30'.
20
+ *
21
+ * Contract:
22
+ * - Active-links chip dot data-active-links-freshness-alpha === '0.30'.
23
+ * - Recent-panel header count data-recent-panel-count-freshness-alpha
24
+ * === '0.30'.
25
+ * - Recent-row pip data-recent-row-freshness-alpha === '0.30'.
26
+ * - SVG fill colors include alpha 0.3 (rgba form).
27
+ */
28
+ import { chromium } from 'playwright';
29
+ import { readFileSync } from 'node:fs';
30
+
31
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
32
+ const browser = await chromium.launch({ headless: true });
33
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
34
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
35
+ await ctx.addInitScript(() => {
36
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
37
+ });
38
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
39
+ await ctx.route('**/api/hub/status*', async (route) => {
40
+ const r = await route.fetch();
41
+ const b = await r.json();
42
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
43
+ const mk = (alias) => ({
44
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
45
+ network_id: nid, project_dir: null,
46
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
47
+ });
48
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b') ] } });
49
+ });
50
+
51
+ // STALE message: created_at ~ 600s ago + last_at ~ 600s ago.
52
+ const now = Date.now();
53
+ const staleIso = new Date(now - 600 * 1000).toISOString();
54
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
55
+ { id: 'm1', from_alias: 'a', to_alias: 'b', content: 'old ping',
56
+ network_id: 'default', created_at: staleIso, last_at: staleIso },
57
+ ] } }));
58
+ await ctx.route('**/api/hub/tasks*', (r) => r.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.waitForSelector('[data-active-links-freshness-dot]', { timeout: 15000 });
63
+ await page.waitForSelector('[data-recent-panel-count-freshness-alpha]', { timeout: 5000 });
64
+ await page.waitForSelector('[data-recent-row-freshness-alpha]', { timeout: 5000 });
65
+ await page.waitForTimeout(400);
66
+
67
+ const probe = await page.evaluate(() => {
68
+ const dot = document.querySelector('[data-active-links-freshness-dot]');
69
+ const count = document.querySelector('[data-recent-panel-count-freshness-alpha]');
70
+ const pip = document.querySelector('[data-recent-row-freshness-alpha]');
71
+ return {
72
+ dotAlpha: dot?.getAttribute('data-active-links-freshness-alpha') ?? null,
73
+ countAlpha: count?.getAttribute('data-recent-panel-count-freshness-alpha') ?? null,
74
+ pipAlpha: pip?.getAttribute('data-recent-row-freshness-alpha') ?? null,
75
+ // The pip uses a static fill + a separate `opacity` SVG attr —
76
+ // probe that, not the fill colour.
77
+ pipOpacity: pip?.getAttribute('opacity') ?? null,
78
+ };
79
+ });
80
+
81
+ await browser.close();
82
+
83
+ const results = {
84
+ dot_alpha_0_30: probe.dotAlpha === '0.30',
85
+ count_alpha_0_30: probe.countAlpha === '0.30',
86
+ pip_alpha_0_30: probe.pipAlpha === '0.30',
87
+ pip_opacity_0_3: /^0\.3$|^0\.30$/.test(probe.pipOpacity || ''),
88
+ };
89
+ const ok = Object.values(results).every(Boolean);
90
+ console.log(`${ok ? '✅' : '❌'} freshness floor lift 0.25 → 0.30:`, JSON.stringify(results),
91
+ '\n probe:', probe);
92
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,88 @@
1
+ /* Round 357 verification: active-links chip freshness suffix wrapper
2
+ * picks up `tabular-nums` for digit width-lock. Pre-R357 the literal
3
+ * "last {rel}" text (e.g. "last 5s ago", "last 10s ago") had
4
+ * natural-figure digits — the ticker updates every second, so the
5
+ * 9→10 boundary on seconds and 59s→1m flip both jittered ~1-2 px of
6
+ * glyph width, propagating through the inline-flex chip row and
7
+ * nudging the freshness DOT + chip's left edge. Tabular-nums on the
8
+ * wrapper applies to all descendant digits (letters render at
9
+ * natural widths). Joins the R224-R232 info-density tabular-nums
10
+ * sweep at the chip-row freshness scope.
11
+ *
12
+ * Contract (cyber, fresh messages so the suffix renders):
13
+ * - Freshness wrapper element computed font-variant-numeric
14
+ * resolves to 'tabular-nums'.
15
+ * - data-active-links-freshness-wrapper attr present on the span.
16
+ * - The wrapper still carries text-gray-400 (R342) — invariant.
17
+ * - The freshness dot still has its color transition (R342) —
18
+ * invariant.
19
+ * - The active-links chip still mounts with the suffix child.
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
+ 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
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
32
+ await ctx.route('**/api/hub/status*', async (route) => {
33
+ const r = await route.fetch();
34
+ const b = await r.json();
35
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
36
+ const mk = (alias) => ({
37
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
38
+ network_id: nid, project_dir: null,
39
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
40
+ });
41
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b'), mk('c') ] } });
42
+ });
43
+ // A fresh message so the active-links chip surfaces a suffix.
44
+ const now = Date.now();
45
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
46
+ { id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
47
+ network_id: 'default', created_at: new Date(now - 5000).toISOString() },
48
+ ] } }));
49
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
50
+
51
+ const page = await ctx.newPage();
52
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
53
+ await page.waitForSelector('[data-active-links-freshness-wrapper]', { timeout: 15000 });
54
+ await page.waitForTimeout(300);
55
+
56
+ const probe = await page.evaluate(() => {
57
+ const wrap = document.querySelector('[data-active-links-freshness-wrapper]');
58
+ const dot = wrap?.querySelector('[data-active-links-freshness-dot]');
59
+ const cs = wrap ? getComputedStyle(wrap) : null;
60
+ const dotCs = dot ? getComputedStyle(dot) : null;
61
+ return {
62
+ wrapperFontVariant: cs?.fontVariantNumeric ?? null,
63
+ wrapperColor: cs?.color ?? null,
64
+ wrapperCls: wrap?.getAttribute('class') ?? null,
65
+ wrapperText: wrap?.textContent ?? null,
66
+ dotPresent: !!dot,
67
+ dotTransition: dotCs?.transition ?? null,
68
+ dotColor: dotCs?.color ?? null,
69
+ };
70
+ });
71
+
72
+ await browser.close();
73
+
74
+ const hasTrans = (s, prop) =>
75
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
76
+
77
+ const results = {
78
+ tabular_nums: /tabular-nums/.test(probe.wrapperFontVariant || ''),
79
+ cls_has_text_gray: /text-gray-400/.test(probe.wrapperCls || ''), // R342
80
+ cls_has_tabular: /tabular-nums/.test(probe.wrapperCls || ''),
81
+ text_has_last: /last/.test(probe.wrapperText || ''),
82
+ dot_present: probe.dotPresent,
83
+ dot_has_color_trans: hasTrans(probe.dotTransition, 'color'), // R342
84
+ };
85
+ const ok = Object.values(results).every(Boolean);
86
+ console.log(`${ok ? '✅' : '❌'} freshness suffix tabular-nums:`, JSON.stringify(results),
87
+ '\n probe:', probe);
88
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,110 @@
1
+ /* Round 461 verification: parent group-box rect transition list
2
+ * extends to include x + y + width + height (200ms ease-out each).
3
+ * Pre-R461 the outer 200×140 px container snap-jumped on cluster
4
+ * resize while the inner R460 hitbox tint rect (160×18) slid —
5
+ * jarring two-rate motion at the same surface. R461 unifies the
6
+ * pair so the whole cluster boundary slides as one Hero D-coherent
7
+ * motion envelope at 200ms.
8
+ *
9
+ * Contract:
10
+ * - every <rect data-group-box-geom-transition="x,y,width,height">
11
+ * renders (one per cluster)
12
+ * - inline style contains all 4 geometry tweens at 200ms
13
+ * - existing R66 / R248 transition axes (stroke, stroke-width,
14
+ * fill-opacity, filter, fill) all preserved
15
+ * - source-file wired
16
+ */
17
+ import { chromium } from 'playwright';
18
+ import { readFileSync } from 'node:fs';
19
+
20
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
21
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
22
+
23
+ const browser = await chromium.launch({ headless: true });
24
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript(() => {
27
+ try {
28
+ localStorage.setItem('anet-theme', 'cyber');
29
+ localStorage.setItem('anet-topo-layout', 'grid');
30
+ sessionStorage.setItem('anet_v3_auth', '1');
31
+ } catch {}
32
+ });
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, status) => ({
38
+ alias, status, 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·1', 'working'),
44
+ mk('alpha·2', 'idle'),
45
+ mk('beta·1', 'working'),
46
+ mk('beta·2', 'idle'),
47
+ ] } });
48
+ });
49
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
50
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
51
+
52
+ const page = await ctx.newPage();
53
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
54
+ await page.waitForSelector('[data-group-box-geom-transition]', { timeout: 15000 });
55
+ await page.waitForTimeout(400);
56
+
57
+ const probe = await page.evaluate(() => {
58
+ const boxes = [...document.querySelectorAll('[data-group-box-geom-transition]')];
59
+ return {
60
+ count: boxes.length,
61
+ nodes: boxes.map(b => ({
62
+ geom: b.getAttribute('data-group-box-geom-transition'),
63
+ style: b.getAttribute('style') || '',
64
+ })),
65
+ };
66
+ });
67
+
68
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
69
+ const parentBoxBlockMatch = src.match(/data-group-box-pinned[\s\S]{0,5500}?\/>/);
70
+ const parentBoxBlock = parentBoxBlockMatch ? parentBoxBlockMatch[0] : '';
71
+ const sourceHasGeomAttr = /data-group-box-geom-transition="x,y,width,height"/.test(parentBoxBlock);
72
+ const sourceHasAll4Tweens = /x 200ms ease-out/.test(parentBoxBlock)
73
+ && /y 200ms ease-out/.test(parentBoxBlock)
74
+ && /width 200ms ease-out/.test(parentBoxBlock)
75
+ && /height 200ms ease-out/.test(parentBoxBlock);
76
+ const sourcePreservesR66 = /stroke 200ms ease-out/.test(parentBoxBlock)
77
+ && /stroke-width 200ms ease-out/.test(parentBoxBlock)
78
+ && /fill-opacity 200ms ease-out/.test(parentBoxBlock)
79
+ && /filter 200ms ease-out/.test(parentBoxBlock)
80
+ && /fill 200ms ease-out/.test(parentBoxBlock);
81
+
82
+ await browser.close();
83
+
84
+ const countGe2 = probe.count >= 2;
85
+ const allDataAttr = probe.nodes.every(n => n.geom === 'x,y,width,height');
86
+ const allStyle4xy = probe.nodes.every(n =>
87
+ /x 200ms ease-out/.test(n.style) &&
88
+ /y 200ms ease-out/.test(n.style) &&
89
+ /width 200ms ease-out/.test(n.style) &&
90
+ /height 200ms ease-out/.test(n.style));
91
+ const allStyleR66 = probe.nodes.every(n =>
92
+ /stroke 200ms ease-out/.test(n.style) &&
93
+ /fill-opacity 200ms ease-out/.test(n.style) &&
94
+ /filter 200ms ease-out/.test(n.style) &&
95
+ /fill 200ms ease-out/.test(n.style));
96
+
97
+ const results = {
98
+ box_count_ge_2: countGe2,
99
+ all_data_attr_xywh: allDataAttr,
100
+ all_style_has_4_geom: allStyle4xy,
101
+ all_style_preserves_r66: allStyleR66,
102
+ source_geom_attr_wired: sourceHasGeomAttr,
103
+ source_all_4_tweens: sourceHasAll4Tweens,
104
+ source_preserves_r66: sourcePreservesR66,
105
+ };
106
+ const ok = Object.values(results).every(Boolean);
107
+ console.log(`${ok ? '✅' : '❌'} group box geom transition x+y+width+height:`, JSON.stringify(results),
108
+ '\n count:', probe.count,
109
+ '\n first style (truncated):', (probe.nodes[0]?.style || '').slice(0, 220));
110
+ process.exit(ok ? 0 : 1);