@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,87 @@
1
+ /* Round 407 verification: node halo offline opacity lift — cyber
2
+ * 0.25 → 0.30 and light 0.4 → 0.45. Probes both themes via two
3
+ * browser contexts with a single offline (ghost) session.
4
+ *
5
+ * Contract:
6
+ * - Online node (status: idle): opacity '0.65' cyber / '0.85' light
7
+ * (unchanged by R407 — invariant probe)
8
+ * - Offline node (status: offline + stale last_seen_at):
9
+ * * cyber: opacity attr === '0.3'
10
+ * * light: opacity attr === '0.45'
11
+ * * data-node-halo-offline-opacity === '0.3' / '0.45'
12
+ * - Source-file verification: the conditional
13
+ * `(isLight ? 0.45 : 0.30)` is present.
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
20
+ const stale = new Date(Date.now() - 30 * 60 * 1000).toISOString();
21
+
22
+ async function probeTheme(theme) {
23
+ const browser = await chromium.launch({ headless: true });
24
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript((t) => {
27
+ try { localStorage.setItem('anet-theme', t); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
28
+ }, theme);
29
+ await ctx.route('**/api/hub/status*', async (route) => {
30
+ const r = await route.fetch();
31
+ const b = await r.json();
32
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
33
+ await route.fulfill({ response: r, json: { ...b, sessions: [
34
+ { alias: 'alpha', status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
35
+ { alias: 'ghost', status: 'offline', model: 'claude-opus-4', runtime: 'claude-code-cli', network_id: nid, project_dir: null, created_at: stale, updated_at: stale, last_seen_at: stale },
36
+ ] } });
37
+ });
38
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
39
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
40
+ const page = await ctx.newPage();
41
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
42
+ await page.waitForSelector('g[data-node]', { timeout: 15000 });
43
+ await page.waitForTimeout(400);
44
+ const probe = await page.evaluate(() => {
45
+ const nodes = Array.from(document.querySelectorAll('g[data-node]'));
46
+ const read = (alias) => {
47
+ const node = nodes.find((n) => n.getAttribute('data-node') === alias);
48
+ if (!node) return null;
49
+ const halo = node.querySelector('[data-node-halo-breath]');
50
+ return halo ? {
51
+ opacityAttr: halo.getAttribute('opacity'),
52
+ offlineData: halo.getAttribute('data-node-halo-offline-opacity'),
53
+ } : null;
54
+ };
55
+ return {
56
+ online: read('alpha'),
57
+ offline: read('ghost'),
58
+ };
59
+ });
60
+ await browser.close();
61
+ return probe;
62
+ }
63
+
64
+ const cyber = await probeTheme('cyber');
65
+ const light = await probeTheme('light');
66
+
67
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
68
+ const sourceHasOfflineLift = /isOnline\s*\?\s*\(isLight\s*\?\s*0\.85\s*:\s*0\.65\)\s*:\s*\(isLight\s*\?\s*0\.45\s*:\s*0\.30\)/.test(fileText);
69
+
70
+ const results = {
71
+ // Online (R407 doesn't touch online — invariant)
72
+ cyber_online_065: cyber?.online?.opacityAttr === '0.65',
73
+ light_online_085: light?.online?.opacityAttr === '0.85',
74
+ cyber_online_no_offline_attr: cyber?.online?.offlineData === null || cyber?.online?.offlineData === undefined,
75
+ // Offline (R407 lift)
76
+ cyber_offline_0_3: cyber?.offline?.opacityAttr === '0.3',
77
+ cyber_offline_data_0_3: cyber?.offline?.offlineData === '0.3',
78
+ light_offline_0_45: light?.offline?.opacityAttr === '0.45',
79
+ light_offline_data_0_45: light?.offline?.offlineData === '0.45',
80
+ // Source-file verification
81
+ source_offline_lift: sourceHasOfflineLift,
82
+ };
83
+ const ok = Object.values(results).every(Boolean);
84
+ console.log(`${ok ? '✅' : '❌'} node halo offline opacity lift:`, JSON.stringify(results),
85
+ '\n cyber:', cyber,
86
+ '\n light:', light);
87
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,85 @@
1
+ /* Round 411 verification: node label card rx 6 → 8. Corner-radius
2
+ * cascade family 8th anchor — compact card tier aligns with the
3
+ * R332/R375/R376 compact-chrome rx=8 tier.
4
+ *
5
+ * Contract:
6
+ * - Each node's label-card <rect> rx attr === '8'
7
+ * - data-node-label-card-rx === '8'
8
+ * - Pre-R411 invariants on the rect:
9
+ * * cardW (width) > 0
10
+ * * cardH (height) > 0
11
+ * * fill, stroke present
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) => ({
30
+ alias, status: 'idle', 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: [ mk('alpha'), mk('beta'), mk('gamma') ] } });
35
+ });
36
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
37
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
38
+
39
+ const page = await ctx.newPage();
40
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
41
+ await page.waitForSelector('[data-node-label-card]', { timeout: 15000 });
42
+ await page.waitForTimeout(400);
43
+
44
+ const probe = await page.evaluate(() => {
45
+ const cards = Array.from(document.querySelectorAll('[data-node-label-card]'));
46
+ if (cards.length === 0) return { count: 0 };
47
+ const sample = cards[0];
48
+ return {
49
+ count: cards.length,
50
+ rxAttr: sample.getAttribute('rx'),
51
+ rxData: sample.getAttribute('data-node-label-card-rx'),
52
+ widthAttr: sample.getAttribute('width'),
53
+ heightAttr: sample.getAttribute('height'),
54
+ fillPresent: !!sample.getAttribute('fill'),
55
+ strokePresent: !!sample.getAttribute('stroke'),
56
+ allRx8: cards.every((c) => c.getAttribute('rx') === '8'),
57
+ };
58
+ });
59
+
60
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
61
+ const sourceRx8 = /rx="8"\s*\n\s+fill=\{pal\.labelBox\.fill\}/.test(fileText) || /width=\{cardW\} height=\{cardH\} rx="8"/.test(fileText);
62
+ const sourceDataRx8 = /data-node-label-card-rx="8"/.test(fileText);
63
+
64
+ await browser.close();
65
+
66
+ const results = {
67
+ // At least one label card mounted
68
+ cards_count_ge_1: (probe.count || 0) >= 1,
69
+ // R411 wire
70
+ rx_attr_8: probe.rxAttr === '8',
71
+ rx_data_8: probe.rxData === '8',
72
+ all_cards_rx_8: probe.allRx8 === true,
73
+ // Pre-R411 invariants
74
+ width_present: Number(probe.widthAttr) > 0,
75
+ height_present: Number(probe.heightAttr) > 0,
76
+ fill_present: probe.fillPresent === true,
77
+ stroke_present: probe.strokePresent === true,
78
+ // Source-file canonical wire
79
+ source_rx_8: sourceRx8,
80
+ source_data_rx_8: sourceDataRx8,
81
+ };
82
+ const ok = Object.values(results).every(Boolean);
83
+ console.log(`${ok ? '✅' : '❌'} node label card rx 6 → 8:`, JSON.stringify(results),
84
+ '\n probe:', probe);
85
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,89 @@
1
+ /* Round 409 verification: active-node pulse SMIL peak opacity lift
2
+ * — cyber 0.18 → 0.20 / light 0.12 → 0.14. Active-state breath
3
+ * widens slightly at peak (+0.02 both themes) while trough stays.
4
+ *
5
+ * Contract:
6
+ * - Seed a flow link so a node becomes "active" (R243 isActive gate)
7
+ * - The active node's <g data-node-pulse> child has an <animate>
8
+ * opacity element with:
9
+ * * cyber: values='0.20;0.04;0.20' + data-node-pulse-peak='0.20'
10
+ * * light: values='0.14;0.02;0.14' + data-node-pulse-peak='0.14'
11
+ * - Pre-R409 invariants preserved:
12
+ * * dur='2.4s', calcMode='spline', repeatCount='indefinite'
13
+ * * keySplines='0.42 0 0.58 1;0.42 0 0.58 1'
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
20
+
21
+ async function probeTheme(theme) {
22
+ const browser = await chromium.launch({ headless: true });
23
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
24
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
25
+ await ctx.addInitScript((t) => {
26
+ try { localStorage.setItem('anet-theme', t); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
27
+ }, theme);
28
+ await ctx.route('**/api/hub/status*', async (route) => {
29
+ const r = await route.fetch();
30
+ const b = await r.json();
31
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
32
+ const mk = (alias) => ({
33
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
34
+ network_id: nid, project_dir: null,
35
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
36
+ });
37
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('alpha'), mk('beta'), mk('gamma') ] } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
40
+ json: { messages: [
41
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
42
+ ] },
43
+ }));
44
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForSelector('[data-node-pulse-active="true"]', { timeout: 15000 });
48
+ await page.waitForTimeout(500);
49
+ const probe = await page.evaluate(() => {
50
+ const pulseGroup = document.querySelector('[data-node-pulse-active="true"]');
51
+ if (!pulseGroup) return null;
52
+ const animates = pulseGroup.querySelectorAll('animate');
53
+ const opacityAnim = Array.from(animates).find(a => a.getAttribute('attributeName') === 'opacity');
54
+ return opacityAnim ? {
55
+ attributeName: opacityAnim.getAttribute('attributeName'),
56
+ values: opacityAnim.getAttribute('values'),
57
+ dur: opacityAnim.getAttribute('dur'),
58
+ calcMode: opacityAnim.getAttribute('calcMode'),
59
+ repeatCount: opacityAnim.getAttribute('repeatCount'),
60
+ keySplines: opacityAnim.getAttribute('keySplines'),
61
+ peakData: opacityAnim.getAttribute('data-node-pulse-peak'),
62
+ } : null;
63
+ });
64
+ await browser.close();
65
+ return probe;
66
+ }
67
+
68
+ const cyber = await probeTheme('cyber');
69
+ const light = await probeTheme('light');
70
+
71
+ const results = {
72
+ // R409: peak lifts to 0.20 cyber / 0.14 light
73
+ cyber_values_0_20: cyber?.values === '0.20;0.04;0.20',
74
+ cyber_peak_data_0_20: cyber?.peakData === '0.20',
75
+ light_values_0_14: light?.values === '0.14;0.02;0.14',
76
+ light_peak_data_0_14: light?.peakData === '0.14',
77
+ // Pre-R409 invariants
78
+ cyber_dur_2_4s: cyber?.dur === '2.4s',
79
+ cyber_spline: cyber?.calcMode === 'spline',
80
+ cyber_repeat_indefinite: cyber?.repeatCount === 'indefinite',
81
+ cyber_keySplines: cyber?.keySplines === '0.42 0 0.58 1;0.42 0 0.58 1',
82
+ light_dur_2_4s: light?.dur === '2.4s',
83
+ light_keySplines: light?.keySplines === '0.42 0 0.58 1;0.42 0 0.58 1',
84
+ };
85
+ const ok = Object.values(results).every(Boolean);
86
+ console.log(`${ok ? '✅' : '❌'} active-node pulse peak lift:`, JSON.stringify(results),
87
+ '\n cyber:', cyber,
88
+ '\n light:', light);
89
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,91 @@
1
+ /* Round 413 verification: active-node pulse SMIL trough opacity
2
+ * lift — cyber 0.04 → 0.05 / light 0.02 → 0.03. Extends the
3
+ * stale-state legibility lift family (8th anchor) and pairs with
4
+ * R409 peak lift so the per-node "active flow" breath reads
5
+ * confidently at both ends of its cycle.
6
+ *
7
+ * Contract:
8
+ * - Seed a flow link so a node becomes "active" (R243 isActive gate)
9
+ * - The active node's <animate> opacity element:
10
+ * * cyber: values='0.20;0.05;0.20' + data-node-pulse-trough='0.05'
11
+ * * light: values='0.14;0.03;0.14' + data-node-pulse-trough='0.03'
12
+ * - R409 peak invariant: data-node-pulse-peak='0.20' / '0.14'
13
+ * - Pre-R413 invariants preserved:
14
+ * * dur='2.4s', calcMode='spline', repeatCount='indefinite'
15
+ * * keySplines='0.42 0 0.58 1;0.42 0 0.58 1'
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
+ async function probeTheme(theme) {
24
+ const browser = await chromium.launch({ headless: true });
25
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
26
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
27
+ await ctx.addInitScript((t) => {
28
+ try { localStorage.setItem('anet-theme', t); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
29
+ }, theme);
30
+ await ctx.route('**/api/hub/status*', async (route) => {
31
+ const r = await route.fetch();
32
+ const b = await r.json();
33
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
34
+ const mk = (alias) => ({
35
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
36
+ network_id: nid, project_dir: null,
37
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
38
+ });
39
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('alpha'), mk('beta'), mk('gamma') ] } });
40
+ });
41
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
42
+ json: { messages: [
43
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
44
+ ] },
45
+ }));
46
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
47
+ const page = await ctx.newPage();
48
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
49
+ await page.waitForSelector('[data-node-pulse-active="true"]', { timeout: 15000 });
50
+ await page.waitForTimeout(500);
51
+ const probe = await page.evaluate(() => {
52
+ const pulseGroup = document.querySelector('[data-node-pulse-active="true"]');
53
+ if (!pulseGroup) return null;
54
+ const animates = pulseGroup.querySelectorAll('animate');
55
+ const opacityAnim = Array.from(animates).find(a => a.getAttribute('attributeName') === 'opacity');
56
+ return opacityAnim ? {
57
+ values: opacityAnim.getAttribute('values'),
58
+ peakData: opacityAnim.getAttribute('data-node-pulse-peak'),
59
+ troughData: opacityAnim.getAttribute('data-node-pulse-trough'),
60
+ dur: opacityAnim.getAttribute('dur'),
61
+ calcMode: opacityAnim.getAttribute('calcMode'),
62
+ keySplines: opacityAnim.getAttribute('keySplines'),
63
+ } : null;
64
+ });
65
+ await browser.close();
66
+ return probe;
67
+ }
68
+
69
+ const cyber = await probeTheme('cyber');
70
+ const light = await probeTheme('light');
71
+
72
+ const results = {
73
+ // R413: trough lifts to 0.05 cyber / 0.03 light
74
+ cyber_values_0_05: cyber?.values === '0.20;0.05;0.20',
75
+ cyber_trough_data_0_05: cyber?.troughData === '0.05',
76
+ light_values_0_03: light?.values === '0.14;0.03;0.14',
77
+ light_trough_data_0_03: light?.troughData === '0.03',
78
+ // R409 peak invariants
79
+ cyber_peak_data_0_20: cyber?.peakData === '0.20',
80
+ light_peak_data_0_14: light?.peakData === '0.14',
81
+ // Pre-R413 invariants preserved
82
+ cyber_dur_2_4s: cyber?.dur === '2.4s',
83
+ cyber_spline: cyber?.calcMode === 'spline',
84
+ cyber_keySplines: cyber?.keySplines === '0.42 0 0.58 1;0.42 0 0.58 1',
85
+ light_dur_2_4s: light?.dur === '2.4s',
86
+ };
87
+ const ok = Object.values(results).every(Boolean);
88
+ console.log(`${ok ? '✅' : '❌'} active-node pulse trough lift:`, JSON.stringify(results),
89
+ '\n cyber:', cyber,
90
+ '\n light:', light);
91
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,75 @@
1
+ /* Round 448 verification: node sub-text fontWeight 400 → 500.
2
+ * Static legibility lift for small mono text at fontSize=9.
3
+ * Sibling to R363/R364 small-text fw lifts.
4
+ *
5
+ * Contract:
6
+ * - every node sub-text reports font-weight '500'
7
+ * - geometry invariants preserved (R428 hover ls + R211 fill
8
+ * transition still intact)
9
+ */
10
+ import { chromium } from 'playwright';
11
+ import { readFileSync } from 'node:fs';
12
+
13
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
14
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
15
+
16
+ const browser = await chromium.launch({ headless: true });
17
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
18
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
19
+ await ctx.addInitScript(() => {
20
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
21
+ });
22
+ await ctx.route('**/api/hub/status*', async (route) => {
23
+ const r = await route.fetch();
24
+ const b = await r.json();
25
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
26
+ const mk = (alias, status) => ({
27
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
28
+ network_id: nid, project_dir: null,
29
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
30
+ });
31
+ await route.fulfill({ response: r, json: { ...b, sessions: [
32
+ mk('alpha', 'working'),
33
+ mk('beta', 'idle'),
34
+ mk('gamma', 'working'),
35
+ ] } });
36
+ });
37
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
38
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
39
+
40
+ const page = await ctx.newPage();
41
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
42
+ await page.waitForSelector('[data-node-sub-text]', { timeout: 15000 });
43
+ await page.waitForTimeout(400);
44
+
45
+ const subs = await page.evaluate(() => {
46
+ const ts = [...document.querySelectorAll('[data-node-sub-text]')];
47
+ return ts.map(t => ({
48
+ alias: t.getAttribute('data-node-sub-text'),
49
+ fw: t.getAttribute('font-weight'),
50
+ fw_data: t.getAttribute('data-node-sub-text-font-weight'),
51
+ text: t.textContent,
52
+ }));
53
+ });
54
+
55
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
56
+ const sourceWired = /fontWeight="500"\s*\n\s*data-node-sub-text=\{session\.alias\}/.test(fileText);
57
+
58
+ await browser.close();
59
+
60
+ const restAllFw500 = subs.every(s => s.fw === '500');
61
+ const restAllAttr500 = subs.every(s => s.fw_data === '500');
62
+ const restCountGe3 = subs.length >= 3;
63
+ const restAllHaveText = subs.every(s => s.text && s.text.length > 0);
64
+
65
+ const results = {
66
+ sub_count_ge_3: restCountGe3,
67
+ all_fw_500_attr: restAllFw500,
68
+ all_fw_data_attr_500: restAllAttr500,
69
+ all_have_text: restAllHaveText,
70
+ source_wired: sourceWired,
71
+ };
72
+ const ok = Object.values(results).every(Boolean);
73
+ console.log(`${ok ? '✅' : '❌'} node sub-text fw 500:`, JSON.stringify(results),
74
+ '\n subs:', JSON.stringify(subs));
75
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,115 @@
1
+ /* Round 428 verification: node sub-text (status label line beneath
2
+ * the alias) adopts hover letter-spacing tween 0 → 0.2px on
3
+ * hoveredAlias. Sibling to R427 alias-text 0 → 0.3 — the alias is
4
+ * primary identity (0.3 kerning), sub-text is secondary status (0.2).
5
+ *
6
+ * Contract:
7
+ * - rest: every sub-text has letter-spacing '0px' and hover='false'
8
+ * - hover one node: that sub-text has letter-spacing '0.2px' and
9
+ * hover='true'; siblings stay at rest
10
+ * - alias-text and sub-text tween together on the same hovered node
11
+ * (R427 0.3 + R428 0.2)
12
+ * - source-file probe confirms conditional + transition extended
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
19
+
20
+ const browser = await chromium.launch({ headless: true });
21
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
22
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
23
+ await ctx.addInitScript(() => {
24
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
25
+ });
26
+ 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, status) => ({
31
+ alias, status, 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
+ // small fleet — card-mode labels
36
+ await route.fulfill({ response: r, json: { ...b, sessions: [
37
+ mk('alpha', 'working'),
38
+ mk('beta', 'idle'),
39
+ mk('gamma', 'working'),
40
+ ] } });
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-node-sub-text]', { timeout: 15000 });
48
+ await page.waitForTimeout(400);
49
+
50
+ const readAll = () => page.evaluate(() => {
51
+ const subs = [...document.querySelectorAll('[data-node-sub-text]')];
52
+ const alis = [...document.querySelectorAll('[data-node-alias-text]')];
53
+ return {
54
+ sub: subs.map(t => ({
55
+ alias: t.getAttribute('data-node-sub-text'),
56
+ ls: t.style.letterSpacing,
57
+ hover: t.getAttribute('data-node-sub-text-hovered'),
58
+ })),
59
+ alias: alis.map(t => ({
60
+ alias: t.getAttribute('data-node-alias-text'),
61
+ ls: t.style.letterSpacing,
62
+ hover: t.getAttribute('data-node-alias-hovered'),
63
+ })),
64
+ };
65
+ });
66
+
67
+ const rest = await readAll();
68
+ const firstAlias = rest.sub[0]?.alias;
69
+ let hover = null;
70
+ if (firstAlias) {
71
+ const box = await page.evaluate((alias) => {
72
+ const t = document.querySelector(`[data-node-alias-text="${alias}"]`);
73
+ if (!t) return null;
74
+ const node = t.closest('[data-node]');
75
+ const target = node || t;
76
+ const b = target.getBoundingClientRect();
77
+ return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
78
+ }, firstAlias);
79
+ if (box) {
80
+ await page.mouse.move(box.x, box.y);
81
+ await page.waitForTimeout(300);
82
+ hover = await readAll();
83
+ await page.mouse.move(0, 0);
84
+ }
85
+ }
86
+
87
+ // Source-file probe
88
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
89
+ const sourceWired = /data-node-sub-text-hovered.*\n.*letterSpacing:\s*hoveredAlias\s*===\s*session\.alias\s*\?\s*'0\.2px'\s*:\s*'0px'/s.test(fileText);
90
+ const sourceTransition = /transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out'/.test(fileText);
91
+
92
+ await browser.close();
93
+
94
+ const isRest = (ls) => ls === '0px' || ls === '' || ls === 'normal';
95
+ const hoveredSubEntry = hover?.sub.find(r => r.alias === firstAlias);
96
+ const hoveredAliasEntry = hover?.alias.find(r => r.alias === firstAlias);
97
+ const othersSubRest = hover ? hover.sub.filter(r => r.alias !== firstAlias).every(r => isRest(r.ls)) : false;
98
+
99
+ const results = {
100
+ rest_sub_count_gte_3: rest.sub.length >= 3,
101
+ rest_sub_all_zero: rest.sub.every(r => isRest(r.ls)),
102
+ rest_sub_all_hover_false: rest.sub.every(r => r.hover === 'false'),
103
+ hover_sub_target_ls_0_2: hoveredSubEntry?.ls === '0.2px',
104
+ hover_sub_target_attr_true: hoveredSubEntry?.hover === 'true',
105
+ hover_sub_others_stay_rest: othersSubRest,
106
+ // R427/R428 sibling parity — alias and sub-text tween together on same hovered node
107
+ hover_alias_target_ls_0_3: hoveredAliasEntry?.ls === '0.3px',
108
+ source_conditional_wired: sourceWired,
109
+ source_transition_wired: sourceTransition,
110
+ };
111
+ const ok = Object.values(results).every(Boolean);
112
+ console.log(`${ok ? '✅' : '❌'} node sub-text letter-spacing hover:`, JSON.stringify(results),
113
+ '\n hover sub:', JSON.stringify(hoveredSubEntry),
114
+ '\n hover alias:', JSON.stringify(hoveredAliasEntry));
115
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,66 @@
1
+ /* Round 463 verification: topo-overlap-test.mjs grew a zombie-
2
+ * build guard that consumes R462's svg[data-dashboard-version]
3
+ * surface. Pre-R463 a stale next-server (dash zombie holding
4
+ * cached chunks from an earlier preview) would let overlap-test
5
+ * report fake green — bit us in R441 + R460. R463 closes that
6
+ * by reading the DOM-side version and comparing to the package.
7
+ * json version, failing fast with exit code 2 on mismatch.
8
+ *
9
+ * This test verifies:
10
+ * 1. The augmented topo-overlap-test source contains the
11
+ * guard machinery (regex assertions on the actual file).
12
+ * 2. The live dash agrees with package.json (sanity-check
13
+ * the current run state matches the guard's expectation).
14
+ * 3. The guard sentinel path is wired in the caller (sentinel
15
+ * object with .stale === true triggers exit code 2).
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 expected = JSON.parse(readFileSync('/home/vansin/agent-network-dashboard/package.json', 'utf8')).version;
22
+
23
+ const src = readFileSync('/home/vansin/agent-network-dashboard/scripts/topo-overlap-test.mjs', 'utf8');
24
+ const sourceHasGuardComment = /Round 463.*zombie-build guard/.test(src);
25
+ const sourceHasLiveProbe = /svg\.getAttribute\('data-dashboard-version'\)/.test(src);
26
+ const sourceHasMismatchBail = /STALE BUILD/.test(src);
27
+ const sourceHasSentinelObj = /\{ stale: true, live, expected \}/.test(src);
28
+ const sourceHasCallerCheck = /typeof r === 'object' && r\.stale/.test(src);
29
+ const sourceHasExit2 = /process\.exit\(2\)/.test(src);
30
+
31
+ // Run a live probe to confirm the dash + pkg are aligned (we're in
32
+ // a clean state where the guard would PASS silently).
33
+ const browser = await chromium.launch({ headless: true });
34
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 920 } });
35
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
36
+ await ctx.addInitScript(() => {
37
+ try {
38
+ localStorage.setItem('anet-theme', 'cyber');
39
+ localStorage.setItem('anet-topo-layout', 'grid');
40
+ sessionStorage.setItem('anet_v3_auth', '1');
41
+ } catch {}
42
+ });
43
+ const page = await ctx.newPage();
44
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
45
+ await page.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 15000 });
46
+ const live = await page.evaluate(() =>
47
+ document.querySelector('svg[viewBox="0 0 1000 680"]')?.getAttribute('data-dashboard-version')
48
+ );
49
+ await browser.close();
50
+
51
+ const liveMatchesPkg = live === expected;
52
+
53
+ const results = {
54
+ source_has_guard_comment: sourceHasGuardComment,
55
+ source_has_live_probe: sourceHasLiveProbe,
56
+ source_has_mismatch_bail: sourceHasMismatchBail,
57
+ source_has_sentinel_object: sourceHasSentinelObj,
58
+ source_has_caller_check: sourceHasCallerCheck,
59
+ source_has_exit_2: sourceHasExit2,
60
+ dash_live_matches_package: liveMatchesPkg,
61
+ };
62
+ const ok = Object.values(results).every(Boolean);
63
+ console.log(`${ok ? '✅' : '❌'} overlap-test stale-build guard:`, JSON.stringify(results),
64
+ '\n expected pkg:', expected,
65
+ '\n live DOM: ', live);
66
+ process.exit(ok ? 0 : 1);