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

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 (284) 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/05kol0eq4~_d5.js +4 -0
  150. package/.next/static/chunks/{03~~oirxz7~vc.js → 07gxjdi4w5ot1.js} +1 -1
  151. package/.next/static/chunks/0a4hmfvj-81x5.css +2 -0
  152. package/.next/static/chunks/0b_0vo6gv.2d0.js +1 -0
  153. package/.next/static/chunks/0o~yjk9gw3w3d.js +1 -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 +2558 -188
  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-group-tint-rx-pin-test.mjs +107 -0
  212. package/scripts/topo-hover-detail-body-fw-test.mjs +101 -0
  213. package/scripts/topo-hover-detail-model-fw-test.mjs +98 -0
  214. package/scripts/topo-hover-detail-opacity-test.mjs +98 -0
  215. package/scripts/topo-hover-detail-rx-test.mjs +81 -0
  216. package/scripts/topo-hub-core-fill-hover-test.mjs +85 -0
  217. package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
  218. package/scripts/topo-hub-digit-fw-hover-test.mjs +102 -0
  219. package/scripts/topo-hub-halo-light-trough-test.mjs +88 -0
  220. package/scripts/topo-hub-halo-r-hover-test.mjs +82 -0
  221. package/scripts/topo-hub-halo-radius-test.mjs +86 -0
  222. package/scripts/topo-hub-halo-trough-test.mjs +83 -0
  223. package/scripts/topo-hub-highlight-opacity-test.mjs +88 -0
  224. package/scripts/topo-hub-highlight-radius-test.mjs +90 -0
  225. package/scripts/topo-hub-hover-ring-opacity-test.mjs +96 -0
  226. package/scripts/topo-hub-hover-ring-stroke-test.mjs +86 -0
  227. package/scripts/topo-hub-spoke-hover-opacity-test.mjs +119 -0
  228. package/scripts/topo-hub-spoke-hover-sw-test.mjs +121 -0
  229. package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
  230. package/scripts/topo-label-card-opacity-hover-test.mjs +99 -0
  231. package/scripts/topo-layout-toggle-radius-test.mjs +87 -0
  232. package/scripts/topo-legend-count-active-opacity-test.mjs +102 -0
  233. package/scripts/topo-legend-count-pin-fw-test.mjs +90 -0
  234. package/scripts/topo-legend-label-fontweight-test.mjs +94 -0
  235. package/scripts/topo-legend-pin-ring-stroke-test.mjs +101 -0
  236. package/scripts/topo-legend-row-letter-spacing-test.mjs +101 -0
  237. package/scripts/topo-minimap-offline-opacity-test.mjs +90 -0
  238. package/scripts/topo-minimap-online-hover-opacity-test.mjs +92 -0
  239. package/scripts/topo-minimap-online-opacity-test.mjs +93 -0
  240. package/scripts/topo-minimap-online-radius-test.mjs +85 -0
  241. package/scripts/topo-minimap-viewport-linejoin-test.mjs +75 -0
  242. package/scripts/topo-minimap-viewport-opacity-test.mjs +96 -0
  243. package/scripts/topo-minimap-viewport-rx-test.mjs +85 -0
  244. package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
  245. package/scripts/topo-node-alias-letter-spacing-test.mjs +112 -0
  246. package/scripts/topo-node-halo-hover-opacity-test.mjs +104 -0
  247. package/scripts/topo-node-halo-light-offline-test.mjs +80 -0
  248. package/scripts/topo-node-halo-offline-opacity-test.mjs +87 -0
  249. package/scripts/topo-node-label-card-rx-test.mjs +85 -0
  250. package/scripts/topo-node-pulse-peak-test.mjs +89 -0
  251. package/scripts/topo-node-pulse-trough-test.mjs +91 -0
  252. package/scripts/topo-node-sub-text-fw-test.mjs +75 -0
  253. package/scripts/topo-node-sub-text-letter-spacing-test.mjs +115 -0
  254. package/scripts/topo-overlap-stale-build-guard-test.mjs +66 -0
  255. package/scripts/topo-overlap-test.mjs +42 -1
  256. package/scripts/topo-panel-count-fw-hover-test.mjs +105 -0
  257. package/scripts/topo-panel-stroke-hover-test.mjs +110 -0
  258. package/scripts/topo-pressure-bar-height-test.mjs +92 -0
  259. package/scripts/topo-pressure-kicker-fontweight-test.mjs +76 -0
  260. package/scripts/topo-recent-pip-radius-2-test.mjs +72 -0
  261. package/scripts/topo-recent-pip-radius-test.mjs +76 -0
  262. package/scripts/topo-recent-row-content-opacity-test.mjs +81 -0
  263. package/scripts/topo-recent-row-count-pin-fw-test.mjs +106 -0
  264. package/scripts/topo-recent-row-letter-spacing-test.mjs +109 -0
  265. package/scripts/topo-recent-row-pip-hover-r-test.mjs +104 -0
  266. package/scripts/topo-recent-row-text-fontweight-test.mjs +90 -0
  267. package/scripts/topo-runtime-icon-hover-test.mjs +96 -0
  268. package/scripts/topo-spoke-active-opacity-test.mjs +104 -0
  269. package/scripts/topo-spoke-active-stroke-test.mjs +95 -0
  270. package/scripts/topo-spoke-idle-opacity-test.mjs +91 -0
  271. package/scripts/topo-status-ring-hover-sw-test.mjs +105 -0
  272. package/scripts/topo-vendor-chip-hover-lift-test.mjs +87 -0
  273. package/scripts/topo-vendor-glyph-fontweight-test.mjs +102 -0
  274. package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
  275. package/scripts/topo-vendor-suffix-hover-brighten-test.mjs +87 -0
  276. package/scripts/topo-zoom-level-hover-fw-test.mjs +95 -0
  277. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +0 -1
  278. package/.next/static/chunks/03mfrl9fd9smg.js +0 -1
  279. package/.next/static/chunks/0kt7qs66ra2.2.css +0 -2
  280. package/.next/static/chunks/116zy6jdhuaa2.js +0 -1
  281. package/.next/static/chunks/12m~64tlig.y4.js +0 -4
  282. /package/.next/static/{rDAS8dVgx4ldg7AtRGdUB → Bv_r6o_p1UTgHlTvQaZNt}/_buildManifest.js +0 -0
  283. /package/.next/static/{rDAS8dVgx4ldg7AtRGdUB → Bv_r6o_p1UTgHlTvQaZNt}/_clientMiddlewareManifest.js +0 -0
  284. /package/.next/static/{rDAS8dVgx4ldg7AtRGdUB → Bv_r6o_p1UTgHlTvQaZNt}/_ssgManifest.js +0 -0
@@ -0,0 +1,121 @@
1
+ /* Round 435 verification: hub-spoke stroke-width hover lift —
2
+ * sibling to R430 opacity hover at the same surface. 2-axis hover
3
+ * signature now: paint (opacity R430) + geometry (stroke-width R435).
4
+ *
5
+ * Rest tiers:
6
+ * idle sw 1.00 / α 0.50
7
+ * active sw 2.25 / α 0.80
8
+ * Hover tiers (hoveredAlias === session.alias):
9
+ * idle sw 1.25 / α 0.70
10
+ * active sw 2.50 / α 0.95
11
+ *
12
+ * Contract:
13
+ * - rest: all idle spokes report data-topo-hub-spoke-stroke-width '1'
14
+ * - hover one node: exactly one spoke flips to hover with sw '1.25'
15
+ * AND opacity '0.7' (both R430+R435 axes lift together)
16
+ * - siblings stay rest sw '1' / opacity '0.5'
17
+ * - source-file probe confirms wired stroke-width conditional
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, status) => ({
36
+ alias, status, 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: [
41
+ mk('alpha', 'working'),
42
+ mk('beta', 'idle'),
43
+ mk('gamma', 'idle'),
44
+ ] } });
45
+ });
46
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
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-topo-hub-spoke-active]', { timeout: 15000 });
52
+ await page.waitForTimeout(400);
53
+
54
+ const readAll = () => page.evaluate(() => {
55
+ const paths = [...document.querySelectorAll('[data-topo-hub-spoke-active]')];
56
+ return paths.map((p, i) => ({
57
+ idx: i,
58
+ active: p.getAttribute('data-topo-hub-spoke-active'),
59
+ hovered: p.getAttribute('data-topo-hub-spoke-hovered'),
60
+ opacity: p.getAttribute('data-topo-hub-spoke-opacity'),
61
+ sw: p.getAttribute('data-topo-hub-spoke-stroke-width'),
62
+ sw_a: p.getAttribute('stroke-width'),
63
+ }));
64
+ });
65
+
66
+ const rest = await readAll();
67
+
68
+ // Hover the first node's <g> group
69
+ const firstAlias = await page.evaluate(() => {
70
+ const t = document.querySelector('[data-node-alias-text]');
71
+ return t?.getAttribute('data-node-alias-text');
72
+ });
73
+ let hover = null;
74
+ if (firstAlias) {
75
+ const box = await page.evaluate((alias) => {
76
+ const t = document.querySelector(`[data-node-alias-text="${alias}"]`);
77
+ if (!t) return null;
78
+ const node = t.closest('[data-node]');
79
+ const target = node || t;
80
+ const b = target.getBoundingClientRect();
81
+ return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
82
+ }, firstAlias);
83
+ if (box) {
84
+ await page.mouse.move(box.x, box.y);
85
+ await page.waitForTimeout(300);
86
+ hover = await readAll();
87
+ await page.mouse.move(0, 0);
88
+ }
89
+ }
90
+
91
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
92
+ const sourceWired = /const spokeStrokeWidth = isActiveSpoke\s*\n\s*\?\s*\(isHoveredSpoke\s*\?\s*2\.5\s*:\s*2\.25\)\s*\n\s*:\s*\(isHoveredSpoke\s*\?\s*1\.25\s*:\s*1\)/.test(fileText);
93
+
94
+ await browser.close();
95
+
96
+ const restAllIdle = rest.length > 0 && rest.every(r => r.active === 'false');
97
+ const restAllSw1 = rest.every(r => r.sw === '1');
98
+ const restAllOpacity = rest.every(r => r.opacity === '0.5');
99
+ const restNoHover = rest.every(r => r.hovered === 'false');
100
+ const hoveredCount = hover ? hover.filter(s => s.hovered === 'true').length : -1;
101
+ const hoveredEntry = hover?.find(s => s.hovered === 'true');
102
+ const hoverSw_1_25 = hoveredEntry?.sw === '1.25';
103
+ const hoverOpacity_0_7 = hoveredEntry?.opacity === '0.7';
104
+ const othersRestSw = hover ? hover.filter(s => s.hovered === 'false').every(s => s.sw === '1') : false;
105
+
106
+ const results = {
107
+ rest_spoke_count_ge_3: rest.length >= 3,
108
+ rest_all_idle: restAllIdle,
109
+ rest_all_sw_1: restAllSw1,
110
+ rest_all_opacity_0_5: restAllOpacity,
111
+ rest_no_hover: restNoHover,
112
+ hover_exactly_one_match: hoveredCount === 1,
113
+ hover_target_sw_1_25: hoverSw_1_25,
114
+ hover_target_opacity_0_7: hoverOpacity_0_7, /* R430 parity */
115
+ hover_others_stay_sw_1: othersRestSw,
116
+ source_strokeWidth_3tier: sourceWired,
117
+ };
118
+ const ok = Object.values(results).every(Boolean);
119
+ console.log(`${ok ? '✅' : '❌'} hub-spoke hover sw (R435):`, JSON.stringify(results),
120
+ '\n hover target:', JSON.stringify(hoveredEntry));
121
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,80 @@
1
+ /* Round 382 verification: hub-spoke path strokeLinecap='round'.
2
+ * Sibling SVG stroke-softening polish to R378 flow-rail dashes +
3
+ * R380 group box dashes — three dashed-stroke surfaces now share
4
+ * 'round' linecap.
5
+ *
6
+ * Dashed-stroke linecap family snapshot post-R382:
7
+ * R378 flow-rail '2 12' -> soft 3-px pills
8
+ * R380 group box '6 6' -> soft 7.5-px pills
9
+ * R382 hub spoke '6 14' -> soft 7-px pills (this round)
10
+ *
11
+ * Contract:
12
+ * - Every [data-topo-hub-spoke-active] path has stroke-linecap='round'.
13
+ * - data-topo-hub-spoke-linecap === 'round'.
14
+ * - Idle spokes still have strokeDasharray='6 14' (R382 doesn't touch
15
+ * the dasharray); active spokes still have 'none'.
16
+ * - Pre-R382 invariants preserved on each spoke: strokeWidth (1 idle /
17
+ * 2 active), R241 stroke + stroke-width + opacity 250ms transition.
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 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(() => {
27
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
28
+ });
29
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
30
+ await ctx.route('**/api/hub/status*', async (route) => {
31
+ const r = await route.fetch();
32
+ const b = await r.json();
33
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
34
+ const mk = (alias) => ({
35
+ alias, status: 'working', 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('a'), mk('b'), mk('c') ] } });
40
+ });
41
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
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-topo-hub-spoke-active]', { timeout: 15000 });
47
+ await page.waitForTimeout(300);
48
+
49
+ const probe = await page.evaluate(() => {
50
+ const spokes = Array.from(document.querySelectorAll('[data-topo-hub-spoke-active]'));
51
+ return spokes.map(el => ({
52
+ activeAttr: el.getAttribute('data-topo-hub-spoke-active'),
53
+ linecapAttr: el.getAttribute('stroke-linecap'),
54
+ linecapData: el.getAttribute('data-topo-hub-spoke-linecap'),
55
+ strokeWidthAttr: el.getAttribute('stroke-width'),
56
+ dasharrayAttr: el.getAttribute('stroke-dasharray'),
57
+ }));
58
+ });
59
+
60
+ await browser.close();
61
+
62
+ const allOk = probe.length >= 1 && probe.every(s =>
63
+ s.linecapAttr === 'round'
64
+ && s.linecapData === 'round'
65
+ && (s.activeAttr === 'true'
66
+ ? (s.strokeWidthAttr === '2' && s.dasharrayAttr === 'none')
67
+ : (s.strokeWidthAttr === '1' && s.dasharrayAttr === '6 14'))
68
+ );
69
+
70
+ const results = {
71
+ spokes_rendered: probe.length >= 1,
72
+ all_linecap_round: probe.every(s => s.linecapAttr === 'round'),
73
+ all_data_linecap: probe.every(s => s.linecapData === 'round'),
74
+ per_state_invariants_intact: allOk,
75
+ };
76
+ const ok = Object.values(results).every(Boolean);
77
+ console.log(`${ok ? '✅' : '❌'} hub-spoke strokeLinecap='round':`, JSON.stringify(results),
78
+ '\n count:', probe.length,
79
+ '\n sample:', probe.slice(0, 3));
80
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,99 @@
1
+ /* Round 429 verification: node label-card body opacity 0.94 → 1.0 on
2
+ * hover (cyber theme). Sibling to R348 panel rect opacity lift at
3
+ * panel scope. Adds a solidity axis to node hover signature alongside
4
+ * R217 stroke tint + R142 drop-shadow + R26 lift + R427/R428 ls.
5
+ *
6
+ * Contract:
7
+ * - rest (cyber): every label-card reports opacity '0.94'
8
+ * - rest elevation attr 'idle' on all cards
9
+ * - hover one node: that card opacity '1' + elevation 'hover'
10
+ * - siblings stay at 0.94 / 'idle'
11
+ * - source-file probe confirms conditional + R246 transition intact
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', 'working'),
38
+ ] } });
39
+ });
40
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
41
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
42
+
43
+ const page = await ctx.newPage();
44
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
45
+ await page.waitForSelector('[data-node-label-card]', { timeout: 15000 });
46
+ await page.waitForTimeout(400);
47
+
48
+ const readAll = () => page.evaluate(() => {
49
+ const rects = [...document.querySelectorAll('[data-node-label-card]')];
50
+ return rects.map(t => ({
51
+ alias: t.getAttribute('data-node-label-card'),
52
+ opacity: t.getAttribute('opacity'),
53
+ elevation: t.getAttribute('data-node-label-card-elevation'),
54
+ }));
55
+ });
56
+
57
+ const rest = await readAll();
58
+ const firstAlias = rest[0]?.alias;
59
+ let hover = null;
60
+ if (firstAlias) {
61
+ const box = await page.evaluate((alias) => {
62
+ const t = document.querySelector(`[data-node-label-card="${alias}"]`);
63
+ if (!t) return null;
64
+ const node = t.closest('[data-node]');
65
+ const target = node || t;
66
+ const b = target.getBoundingClientRect();
67
+ return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
68
+ }, firstAlias);
69
+ if (box) {
70
+ await page.mouse.move(box.x, box.y);
71
+ await page.waitForTimeout(300);
72
+ hover = await readAll();
73
+ await page.mouse.move(0, 0);
74
+ }
75
+ }
76
+
77
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
78
+ const sourceWired = /opacity=\{\s*\n?\s*!reducedMotion && hoveredAlias === session\.alias\s*\n?\s*\?\s*1\s*\n?\s*:\s*\(isLight \? 1 : 0\.94\)\s*\n?\s*\}/.test(fileText);
79
+ const sourceTransition = /transition: 'filter 220ms ease-out, stroke 220ms ease-out, fill 220ms ease-out, opacity 220ms ease-out'/.test(fileText);
80
+
81
+ await browser.close();
82
+
83
+ const hoveredEntry = hover?.find(r => r.alias === firstAlias);
84
+ const othersRest = hover ? hover.filter(r => r.alias !== firstAlias).every(r => r.opacity === '0.94' && r.elevation === 'idle') : false;
85
+
86
+ const results = {
87
+ rest_card_count_gte_3: rest.length >= 3,
88
+ rest_all_opacity_0_94: rest.every(r => r.opacity === '0.94'),
89
+ rest_all_elevation_idle: rest.every(r => r.elevation === 'idle'),
90
+ hover_target_opacity_1: hoveredEntry?.opacity === '1',
91
+ hover_target_elevation: hoveredEntry?.elevation === 'hover',
92
+ hover_others_stay_idle: othersRest,
93
+ source_conditional_wired: sourceWired,
94
+ source_transition_intact: sourceTransition,
95
+ };
96
+ const ok = Object.values(results).every(Boolean);
97
+ console.log(`${ok ? '✅' : '❌'} label card opacity hover:`, JSON.stringify(results),
98
+ '\n hover target:', JSON.stringify(hoveredEntry));
99
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,87 @@
1
+ /* Round 375 verification: Layout-toggle wrapper rounded-md → rounded-
2
+ * lg (6 → 8 px). Extends the R330/R331/R332 corner-radius cascade to
3
+ * the chrome-strip layout-toggle wrapper. Pre-R375 the wrapper at
4
+ * rounded-md (6 px) was the only chrome-strip container still using
5
+ * the smaller corner radius — both R330 outer wrapper and R332
6
+ * minimap sit at ≥ 8 px; R375 brings the toggle into the rounded-lg
7
+ * tier where minimap already lives.
8
+ *
9
+ * Contract:
10
+ * - data-topo-chrome-layout-trailer element computed border-radius
11
+ * === '8px' (rounded-lg).
12
+ * - data-topo-chrome-layout-radius === 'rounded-lg'.
13
+ * - Pre-R375 invariants:
14
+ * * border still present (1 px default Tailwind)
15
+ * * R268 inline borderColor + 200ms border-color transition
16
+ * * R329 mr-0.5 spacing preserved
17
+ * * inline-flex + overflow-hidden preserved
18
+ * * Inner buttons (Ring + Grid) still mounted as flex children
19
+ */
20
+ import { chromium } from 'playwright';
21
+ import { readFileSync } from 'node:fs';
22
+
23
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
24
+ const browser = await chromium.launch({ headless: true });
25
+ 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(() => {
28
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
29
+ });
30
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
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: 'working', 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('a'), mk('b'), mk('c') ] } });
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-chrome-layout-trailer]', { timeout: 15000 });
48
+ await page.waitForTimeout(300);
49
+
50
+ const probe = await page.evaluate(() => {
51
+ const w = document.querySelector('[data-topo-chrome-layout-trailer]');
52
+ const cs = w ? getComputedStyle(w) : null;
53
+ const ring = w?.querySelector('[data-topo-chrome-layout="ring"]');
54
+ const grid = w?.querySelector('[data-topo-chrome-layout="grid"]');
55
+ return {
56
+ borderRadius: cs?.borderRadius ?? null,
57
+ radiusData: w?.getAttribute('data-topo-chrome-layout-radius') ?? null,
58
+ borderWidth: cs?.borderWidth ?? null,
59
+ display: cs?.display ?? null,
60
+ overflow: cs?.overflow ?? null,
61
+ transition: cs?.transition ?? null,
62
+ cls: w?.getAttribute('class') ?? null,
63
+ ringPresent: !!ring,
64
+ gridPresent: !!grid,
65
+ };
66
+ });
67
+
68
+ await browser.close();
69
+
70
+ const hasTrans = (s, prop) =>
71
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
72
+
73
+ const results = {
74
+ border_radius_8px: probe.borderRadius === '8px', // rounded-lg
75
+ data_radius_lg: probe.radiusData === 'rounded-lg',
76
+ border_width_1px: probe.borderWidth === '1px',
77
+ display_inline_flex: probe.display === 'inline-flex' || probe.display === 'flex',
78
+ overflow_hidden: probe.overflow === 'hidden',
79
+ trans_has_border_color: hasTrans(probe.transition, 'border-color'), // R268
80
+ cls_has_mr_0_5: /mr-0\.5/.test(probe.cls || ''), // R329
81
+ ring_button_present: probe.ringPresent,
82
+ grid_button_present: probe.gridPresent,
83
+ };
84
+ const ok = Object.values(results).every(Boolean);
85
+ console.log(`${ok ? '✅' : '❌'} Layout-toggle wrapper rounded-md → rounded-lg:`, JSON.stringify(results),
86
+ '\n probe:', probe);
87
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,102 @@
1
+ /* Round 449 verification: legend-row count active-state opacity
2
+ * 0.95 → 1.0 on (hoveredStatus===row.key || isPinned). Closes the 5%
3
+ * alpha gap on populated active rows.
4
+ *
5
+ * Contract:
6
+ * - rest: every populated legend-count reports opacity '0.65' (R204)
7
+ * - click 'working' status: that count opacity '1'
8
+ * - empty (0-count) rows stay at R204 dim (0.28/0.30) unaffected
9
+ * - siblings stay at rest 0.65
10
+ */
11
+ import { chromium } from 'playwright';
12
+ import { readFileSync } from 'node:fs';
13
+
14
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
15
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
16
+
17
+ const browser = await chromium.launch({ headless: true });
18
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
22
+ });
23
+ await ctx.route('**/api/hub/status*', async (route) => {
24
+ const r = await route.fetch();
25
+ const b = await r.json();
26
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
27
+ const mk = (alias, status) => ({
28
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
29
+ network_id: nid, project_dir: null,
30
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
31
+ });
32
+ await route.fulfill({ response: r, json: { ...b, sessions: [
33
+ mk('alpha', 'working'),
34
+ mk('beta', 'working'),
35
+ mk('gamma', 'idle'),
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
+
41
+ const page = await ctx.newPage();
42
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
43
+ await page.waitForSelector('[data-legend-count]', { timeout: 15000 });
44
+ await page.waitForTimeout(400);
45
+
46
+ const readAll = () => page.evaluate(() => {
47
+ const ts = [...document.querySelectorAll('[data-legend-count]')];
48
+ return ts.map(t => ({
49
+ key: t.getAttribute('data-legend-count'),
50
+ opacity: t.getAttribute('opacity'),
51
+ empty: t.getAttribute('data-legend-count-empty'),
52
+ }));
53
+ });
54
+
55
+ const rest = await readAll();
56
+
57
+ // Click 'working' status row to pin
58
+ let pinned = null;
59
+ const workingGroup = await page.$(`[data-legend-status="working"]`);
60
+ if (workingGroup) {
61
+ await workingGroup.click();
62
+ await page.waitForTimeout(250);
63
+ pinned = await readAll();
64
+ }
65
+
66
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
67
+ const sourceWired = /:\s*\(hoveredStatus === row\.key \|\| isPinned \? 1 : 0\.65\)/.test(fileText);
68
+
69
+ await browser.close();
70
+
71
+ // rest: populated rows (working=2, idle=1) should be 0.65; offline (count=0) should be 0.28/0.30
72
+ const workingRest = rest.find(r => r.key === 'working');
73
+ const idleRest = rest.find(r => r.key === 'idle');
74
+ const offlineRest = rest.find(r => r.key === 'offline');
75
+
76
+ const restWorking_065 = workingRest?.opacity === '0.65';
77
+ const restIdle_065 = idleRest?.opacity === '0.65';
78
+ const restOfflineEmpty = offlineRest?.empty === 'true' && (offlineRest?.opacity === '0.3' || offlineRest?.opacity === '0.28');
79
+
80
+ // pinned: working should be 1
81
+ const pinnedWorking = pinned?.find(r => r.key === 'working');
82
+ const pinnedIdle = pinned?.find(r => r.key === 'idle');
83
+ const pinnedOffline = pinned?.find(r => r.key === 'offline');
84
+
85
+ const pinWorking_1 = pinnedWorking?.opacity === '1';
86
+ const pinIdleRest = pinnedIdle?.opacity === '0.65';
87
+ const pinOfflineEmpty = pinnedOffline?.empty === 'true';
88
+
89
+ const results = {
90
+ rest_working_065: restWorking_065,
91
+ rest_idle_065: restIdle_065,
92
+ rest_offline_empty_dim: restOfflineEmpty,
93
+ pin_working_opacity_1: pinWorking_1,
94
+ pin_idle_stays_rest_065: pinIdleRest,
95
+ pin_offline_stays_empty: pinOfflineEmpty,
96
+ source_wired: sourceWired,
97
+ };
98
+ const ok = Object.values(results).every(Boolean);
99
+ console.log(`${ok ? '✅' : '❌'} legend count active opacity:`, JSON.stringify(results),
100
+ '\n rest:', JSON.stringify(rest),
101
+ '\n pinned:', JSON.stringify(pinned));
102
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,90 @@
1
+ /* Round 446 verification: legend-row count fontWeight lift on isPinned.
2
+ * 600 → 700 when the status tier is pinned. Mirror of R444/R445 at
3
+ * legend-row scope.
4
+ *
5
+ * Contract:
6
+ * - rest: every legend-count reports font-weight '600' + pinned='false'
7
+ * - click a legend-status group: that row's count flips to '700' +
8
+ * pinned='true'; siblings stay rest
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', 'working'),
34
+ mk('gamma', 'idle'),
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-legend-count]', { timeout: 15000 });
43
+ await page.waitForTimeout(400);
44
+
45
+ const readAll = () => page.evaluate(() => {
46
+ const ts = [...document.querySelectorAll('[data-legend-count]')];
47
+ return ts.map(t => ({
48
+ key: t.getAttribute('data-legend-count'),
49
+ fw: t.getAttribute('font-weight'),
50
+ pinned: t.getAttribute('data-legend-count-pinned'),
51
+ }));
52
+ });
53
+
54
+ const rest = await readAll();
55
+
56
+ // Click the 'working' status row to pin it
57
+ let pinned = null;
58
+ const workingGroup = await page.$(`[data-legend-status="working"]`);
59
+ if (workingGroup) {
60
+ await workingGroup.click();
61
+ await page.waitForTimeout(250);
62
+ pinned = await readAll();
63
+ }
64
+
65
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
66
+ const sourceWired = /fontWeight=\{isPinned \? '700' : '600'\}/.test(fileText);
67
+
68
+ await browser.close();
69
+
70
+ const restAll600 = rest.every(r => r.fw === '600');
71
+ const restNoPin = rest.every(r => r.pinned === 'false');
72
+ const pinnedEntry = pinned?.find(r => r.key === 'working');
73
+ const pinFw_700 = pinnedEntry?.fw === '700';
74
+ const pinFlag = pinnedEntry?.pinned === 'true';
75
+ const othersStayRest = pinned ? pinned.filter(r => r.key !== 'working').every(r => r.fw === '600' && r.pinned === 'false') : false;
76
+
77
+ const results = {
78
+ rest_three_rows: rest.length === 3,
79
+ rest_all_fw_600: restAll600,
80
+ rest_no_pin: restNoPin,
81
+ pinned_target_fw_700: pinFw_700,
82
+ pinned_target_flag: pinFlag,
83
+ pinned_others_stay: othersStayRest,
84
+ source_wired: sourceWired,
85
+ };
86
+ const ok = Object.values(results).every(Boolean);
87
+ console.log(`${ok ? '✅' : '❌'} legend count fw pin:`, JSON.stringify(results),
88
+ '\n rest:', JSON.stringify(rest),
89
+ '\n pinned target:', JSON.stringify(pinnedEntry));
90
+ process.exit(ok ? 0 : 1);