@sleep2agi/agent-network-dashboard 0.5.1-preview.13 → 0.5.1-preview.130

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 (274) 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 +7 -7
  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 +2 -2
  16. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  17. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  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 +2 -2
  25. package/.next/server/app/admin.segments/_full.segment.rsc +2 -2
  26. package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  27. package/.next/server/app/admin.segments/_index.segment.rsc +2 -2
  28. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  29. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
  30. package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
  31. package/.next/server/app/index.html +2 -2
  32. package/.next/server/app/index.rsc +3 -3
  33. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  34. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  35. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  36. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  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 +3 -3
  41. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  42. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  43. package/.next/server/app/login.segments/_index.segment.rsc +2 -2
  44. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  45. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  46. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  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 +2 -2
  50. package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
  51. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  52. package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
  53. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
  55. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  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 +2 -2
  59. package/.next/server/app/messages.segments/_full.segment.rsc +2 -2
  60. package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
  61. package/.next/server/app/messages.segments/_index.segment.rsc +2 -2
  62. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
  64. package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
  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 +2 -2
  68. package/.next/server/app/node.segments/_full.segment.rsc +2 -2
  69. package/.next/server/app/node.segments/_head.segment.rsc +1 -1
  70. package/.next/server/app/node.segments/_index.segment.rsc +2 -2
  71. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
  73. package/.next/server/app/node.segments/node.segment.rsc +1 -1
  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 +2 -2
  77. package/.next/server/app/nodes.segments/_full.segment.rsc +2 -2
  78. package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
  79. package/.next/server/app/nodes.segments/_index.segment.rsc +2 -2
  80. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
  82. package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
  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 +2 -2
  88. package/.next/server/app/server-logs.segments/_full.segment.rsc +2 -2
  89. package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
  90. package/.next/server/app/server-logs.segments/_index.segment.rsc +2 -2
  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 +1 -1
  93. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
  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 +2 -2
  97. package/.next/server/app/settings/networks.segments/_full.segment.rsc +2 -2
  98. package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
  99. package/.next/server/app/settings/networks.segments/_index.segment.rsc +2 -2
  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 +1 -1
  102. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
  103. package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
  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 +2 -2
  108. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +2 -2
  109. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
  110. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +2 -2
  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 +1 -1
  113. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
  114. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
  115. package/.next/server/app/settings.html +2 -2
  116. package/.next/server/app/settings.rsc +3 -3
  117. package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  118. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  119. package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  120. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  121. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  122. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  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 +2 -2
  127. package/.next/server/app/tasks.segments/_full.segment.rsc +2 -2
  128. package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
  129. package/.next/server/app/tasks.segments/_index.segment.rsc +2 -2
  130. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  131. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
  132. package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
  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/middleware-build-manifest.js +3 -3
  142. package/.next/server/pages/404.html +2 -2
  143. package/.next/server/pages/500.html +1 -1
  144. package/.next/server/server-reference-manifest.js +1 -1
  145. package/.next/server/server-reference-manifest.json +1 -1
  146. package/.next/static/chunks/0-at-u3ty_q5z.js +1 -0
  147. package/.next/static/chunks/0a4hmfvj-81x5.css +2 -0
  148. package/.next/static/chunks/0kz7356-vy97a.js +1 -0
  149. package/.next/static/chunks/{03~~oirxz7~vc.js → 0phbmewz8hp6a.js} +1 -1
  150. package/.next/static/chunks/11xegdodzgys0.js +4 -0
  151. package/.next/trace +2 -2
  152. package/.next/trace-build +1 -1
  153. package/app/components/TopoGraph.tsx +2528 -186
  154. package/package.json +1 -1
  155. package/screenshots/v0.11.0-147/after.png +0 -0
  156. package/scripts/p0-147-screenshot.mjs +53 -0
  157. package/scripts/topo-active-links-chip-hover-lift-test.mjs +93 -0
  158. package/scripts/topo-chip-digit-fontweight-test.mjs +105 -0
  159. package/scripts/topo-chip-digit-hover-bold-test.mjs +94 -0
  160. package/scripts/topo-chip-row-group-hover-brighten-test.mjs +107 -0
  161. package/scripts/topo-chip-row-hover-lift-test.mjs +95 -0
  162. package/scripts/topo-chrome-button-hover-lift-test.mjs +94 -0
  163. package/scripts/topo-chrome-fullscreen-icon-sw-test.mjs +92 -0
  164. package/scripts/topo-chrome-reset-icon-sw-test.mjs +80 -0
  165. package/scripts/topo-chrome-segmented-radius-test.mjs +100 -0
  166. package/scripts/topo-chrome-zoom-icon-sw-test.mjs +90 -0
  167. package/scripts/topo-click-ripple-opacity-test.mjs +99 -0
  168. package/scripts/topo-dashboard-version-attr-test.mjs +69 -0
  169. package/scripts/topo-dense-alias-opacity-test.mjs +68 -0
  170. package/scripts/topo-edge-badge-digit-fw-test.mjs +103 -0
  171. package/scripts/topo-edge-badge-fontsize-test.mjs +90 -0
  172. package/scripts/topo-edge-badge-hover-opacity-test.mjs +94 -0
  173. package/scripts/topo-edge-badge-hover-stroke-test.mjs +92 -0
  174. package/scripts/topo-edge-badge-letter-spacing-test.mjs +86 -0
  175. package/scripts/topo-edge-badge-opacity-test.mjs +80 -0
  176. package/scripts/topo-edge-badge-pin-opacity-test.mjs +86 -0
  177. package/scripts/topo-edge-badge-stroke-test.mjs +92 -0
  178. package/scripts/topo-edge-endpoint-hover-sw-test.mjs +127 -0
  179. package/scripts/topo-edge-freshness-floor-test.mjs +99 -0
  180. package/scripts/topo-edge-particle-hover-r-test.mjs +113 -0
  181. package/scripts/topo-edge-particle-radius-test.mjs +76 -0
  182. package/scripts/topo-edge-visible-linecap-test.mjs +89 -0
  183. package/scripts/topo-endpoint-ring-r-hover-test.mjs +89 -0
  184. package/scripts/topo-filter-pill-hover-lift-test.mjs +101 -0
  185. package/scripts/topo-filter-pill-hover-opacity-test.mjs +110 -0
  186. package/scripts/topo-filter-pill-value-fw-test.mjs +88 -0
  187. package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
  188. package/scripts/topo-flow-rail-hover-sw-test.mjs +113 -0
  189. package/scripts/topo-flow-rail-linecap-test.mjs +79 -0
  190. package/scripts/topo-freshness-chip-hierarchy-test.mjs +93 -0
  191. package/scripts/topo-freshness-chip-tabular-test.mjs +41 -0
  192. package/scripts/topo-freshness-floor-lift-test.mjs +92 -0
  193. package/scripts/topo-freshness-suffix-tabular-test.mjs +88 -0
  194. package/scripts/topo-fullscreen-icon-hover-scale-test.mjs +91 -0
  195. package/scripts/topo-group-box-geom-transition-test.mjs +110 -0
  196. package/scripts/topo-group-box-stroke-test.mjs +105 -0
  197. package/scripts/topo-group-label-count-fontweight-test.mjs +108 -0
  198. package/scripts/topo-group-label-count-fw-test.mjs +100 -0
  199. package/scripts/topo-group-label-fw-pin-test.mjs +99 -0
  200. package/scripts/topo-group-label-letter-spacing-test.mjs +113 -0
  201. package/scripts/topo-group-label-tint-geom-test.mjs +94 -0
  202. package/scripts/topo-group-label-tint-transition-test.mjs +97 -0
  203. package/scripts/topo-group-pip-fontsize-test.mjs +106 -0
  204. package/scripts/topo-hover-detail-body-fw-test.mjs +101 -0
  205. package/scripts/topo-hover-detail-model-fw-test.mjs +98 -0
  206. package/scripts/topo-hover-detail-opacity-test.mjs +98 -0
  207. package/scripts/topo-hover-detail-rx-test.mjs +81 -0
  208. package/scripts/topo-hub-core-fill-hover-test.mjs +85 -0
  209. package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
  210. package/scripts/topo-hub-digit-fw-hover-test.mjs +102 -0
  211. package/scripts/topo-hub-halo-light-trough-test.mjs +88 -0
  212. package/scripts/topo-hub-halo-r-hover-test.mjs +82 -0
  213. package/scripts/topo-hub-halo-radius-test.mjs +86 -0
  214. package/scripts/topo-hub-halo-trough-test.mjs +83 -0
  215. package/scripts/topo-hub-highlight-opacity-test.mjs +88 -0
  216. package/scripts/topo-hub-highlight-radius-test.mjs +90 -0
  217. package/scripts/topo-hub-hover-ring-opacity-test.mjs +96 -0
  218. package/scripts/topo-hub-hover-ring-stroke-test.mjs +86 -0
  219. package/scripts/topo-hub-spoke-hover-opacity-test.mjs +119 -0
  220. package/scripts/topo-hub-spoke-hover-sw-test.mjs +121 -0
  221. package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
  222. package/scripts/topo-label-card-opacity-hover-test.mjs +99 -0
  223. package/scripts/topo-layout-toggle-radius-test.mjs +87 -0
  224. package/scripts/topo-legend-count-active-opacity-test.mjs +102 -0
  225. package/scripts/topo-legend-count-pin-fw-test.mjs +90 -0
  226. package/scripts/topo-legend-label-fontweight-test.mjs +94 -0
  227. package/scripts/topo-legend-pin-ring-stroke-test.mjs +101 -0
  228. package/scripts/topo-legend-row-letter-spacing-test.mjs +101 -0
  229. package/scripts/topo-minimap-offline-opacity-test.mjs +90 -0
  230. package/scripts/topo-minimap-online-hover-opacity-test.mjs +92 -0
  231. package/scripts/topo-minimap-online-opacity-test.mjs +93 -0
  232. package/scripts/topo-minimap-online-radius-test.mjs +85 -0
  233. package/scripts/topo-minimap-viewport-linejoin-test.mjs +75 -0
  234. package/scripts/topo-minimap-viewport-opacity-test.mjs +96 -0
  235. package/scripts/topo-minimap-viewport-rx-test.mjs +85 -0
  236. package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
  237. package/scripts/topo-node-alias-letter-spacing-test.mjs +112 -0
  238. package/scripts/topo-node-halo-hover-opacity-test.mjs +104 -0
  239. package/scripts/topo-node-halo-light-offline-test.mjs +80 -0
  240. package/scripts/topo-node-halo-offline-opacity-test.mjs +87 -0
  241. package/scripts/topo-node-label-card-rx-test.mjs +85 -0
  242. package/scripts/topo-node-pulse-peak-test.mjs +89 -0
  243. package/scripts/topo-node-pulse-trough-test.mjs +91 -0
  244. package/scripts/topo-node-sub-text-fw-test.mjs +75 -0
  245. package/scripts/topo-node-sub-text-letter-spacing-test.mjs +115 -0
  246. package/scripts/topo-panel-count-fw-hover-test.mjs +105 -0
  247. package/scripts/topo-panel-stroke-hover-test.mjs +110 -0
  248. package/scripts/topo-pressure-bar-height-test.mjs +92 -0
  249. package/scripts/topo-pressure-kicker-fontweight-test.mjs +76 -0
  250. package/scripts/topo-recent-pip-radius-2-test.mjs +72 -0
  251. package/scripts/topo-recent-pip-radius-test.mjs +76 -0
  252. package/scripts/topo-recent-row-content-opacity-test.mjs +81 -0
  253. package/scripts/topo-recent-row-count-pin-fw-test.mjs +106 -0
  254. package/scripts/topo-recent-row-letter-spacing-test.mjs +109 -0
  255. package/scripts/topo-recent-row-pip-hover-r-test.mjs +104 -0
  256. package/scripts/topo-recent-row-text-fontweight-test.mjs +90 -0
  257. package/scripts/topo-runtime-icon-hover-test.mjs +96 -0
  258. package/scripts/topo-spoke-active-opacity-test.mjs +104 -0
  259. package/scripts/topo-spoke-active-stroke-test.mjs +95 -0
  260. package/scripts/topo-spoke-idle-opacity-test.mjs +91 -0
  261. package/scripts/topo-status-ring-hover-sw-test.mjs +105 -0
  262. package/scripts/topo-vendor-chip-hover-lift-test.mjs +87 -0
  263. package/scripts/topo-vendor-glyph-fontweight-test.mjs +102 -0
  264. package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
  265. package/scripts/topo-vendor-suffix-hover-brighten-test.mjs +87 -0
  266. package/scripts/topo-zoom-level-hover-fw-test.mjs +95 -0
  267. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +0 -1
  268. package/.next/static/chunks/0is7s1ik8dy24.js +0 -4
  269. package/.next/static/chunks/0kt7qs66ra2.2.css +0 -2
  270. package/.next/static/chunks/0yl__lz0pv5_7.js +0 -1
  271. package/.next/static/chunks/1090s4vyw-jrc.js +0 -1
  272. /package/.next/static/{Fa8EZgmgYGwvcoaGKpSmo → ZPH3qUPz78tGunqc1tq8i}/_buildManifest.js +0 -0
  273. /package/.next/static/{Fa8EZgmgYGwvcoaGKpSmo → ZPH3qUPz78tGunqc1tq8i}/_clientMiddlewareManifest.js +0 -0
  274. /package/.next/static/{Fa8EZgmgYGwvcoaGKpSmo → ZPH3qUPz78tGunqc1tq8i}/_ssgManifest.js +0 -0
@@ -0,0 +1,105 @@
1
+ /* Round 380 verification: cluster box stroke gets round linecap +
2
+ * round linejoin. Sibling SVG stroke-softening polish to R378 flow-
3
+ * rail linecap + R379 minimap viewport linejoin — extends the family
4
+ * to the group cluster boundary box (grid layout only).
5
+ *
6
+ * Family snapshot post-R380:
7
+ * R288 chrome icons strokeLinecap='round'
8
+ * R378 flow-rail dashes strokeLinecap='round'
9
+ * R380 group box dashes strokeLinecap='round' (this round)
10
+ * R379 viewport rect strokeLinejoin='round'
11
+ * R380 group box corners strokeLinejoin='round' (this round)
12
+ *
13
+ * Contract (cyber, grid layout):
14
+ * - [data-group-box-pinned] elements have stroke-linecap='round'
15
+ * + stroke-linejoin='round'.
16
+ * - data-group-box-linecap + data-group-box-linejoin === 'round'.
17
+ * - Pre-R380 invariants:
18
+ * * strokeWidth=1.5 at rest (R68 ladder)
19
+ * * strokeDasharray='6 6' at rest (R85 marching ants)
20
+ * * data-group-box-pinned 'false' at rest
21
+ * * data-group-box-lifted 'false' at rest
22
+ */
23
+ import { chromium } from 'playwright';
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
27
+ const browser = await chromium.launch({ headless: true });
28
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try {
32
+ localStorage.setItem('anet-theme', 'cyber');
33
+ sessionStorage.setItem('anet_v3_auth', '1');
34
+ localStorage.setItem('anet-layout', JSON.stringify('grid'));
35
+ } catch {}
36
+ });
37
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
38
+ await ctx.route('**/api/hub/status*', async (route) => {
39
+ const r = await route.fetch();
40
+ const b = await r.json();
41
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
42
+ const mk = (alias) => ({
43
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
44
+ network_id: nid, project_dir: null,
45
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
46
+ });
47
+ // Cluster groups via prefix alias → group boxes render.
48
+ await route.fulfill({ response: r, json: { ...b, sessions: [
49
+ mk('alpha-1'), mk('alpha-2'), mk('alpha-3'),
50
+ mk('beta-1'), mk('beta-2'),
51
+ ] } });
52
+ });
53
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
54
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
55
+
56
+ const page = await ctx.newPage();
57
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
58
+ await page.waitForSelector('[data-topo-chrome-layout="grid"]', { timeout: 15000 });
59
+ const layoutActive = await page.getAttribute('[data-topo-chrome-layout="grid"]', 'data-topo-chrome-layout-active');
60
+ if (layoutActive !== 'true') await page.click('[data-topo-chrome-layout="grid"]');
61
+ await page.waitForSelector('[data-group-box-pinned]', { timeout: 10000 });
62
+ await page.waitForTimeout(400);
63
+
64
+ const probe = await page.evaluate(() => {
65
+ const boxes = Array.from(document.querySelectorAll('[data-group-box-pinned]'));
66
+ return boxes.map(el => ({
67
+ linecapAttr: el.getAttribute('stroke-linecap'),
68
+ linejoinAttr: el.getAttribute('stroke-linejoin'),
69
+ linecapData: el.getAttribute('data-group-box-linecap'),
70
+ linejoinData: el.getAttribute('data-group-box-linejoin'),
71
+ strokeWidth: el.getAttribute('stroke-width'),
72
+ strokeDasharray: el.getAttribute('stroke-dasharray'),
73
+ pinnedAttr: el.getAttribute('data-group-box-pinned'),
74
+ liftedAttr: el.getAttribute('data-group-box-lifted'),
75
+ }));
76
+ });
77
+
78
+ await browser.close();
79
+
80
+ const allOk = probe.length >= 1 && probe.every(b =>
81
+ b.linecapAttr === 'round'
82
+ && b.linejoinAttr === 'round'
83
+ && b.linecapData === 'round'
84
+ && b.linejoinData === 'round'
85
+ && b.strokeWidth === '1.5' // R68 rest
86
+ && b.strokeDasharray === '6 6' // R85 rest
87
+ && b.pinnedAttr === 'false'
88
+ && b.liftedAttr === 'false'
89
+ );
90
+
91
+ const results = {
92
+ boxes_rendered: probe.length >= 1,
93
+ all_linecap_round: probe.every(b => b.linecapAttr === 'round'),
94
+ all_linejoin_round: probe.every(b => b.linejoinAttr === 'round'),
95
+ all_data_linecap: probe.every(b => b.linecapData === 'round'),
96
+ all_data_linejoin: probe.every(b => b.linejoinData === 'round'),
97
+ all_strokew_1_5_rest: probe.every(b => b.strokeWidth === '1.5'),
98
+ all_dash_6_6_rest: probe.every(b => b.strokeDasharray === '6 6'),
99
+ all_pinned_false: probe.every(b => b.pinnedAttr === 'false'),
100
+ all_lifted_false: probe.every(b => b.liftedAttr === 'false'),
101
+ };
102
+ const ok = allOk && Object.values(results).every(Boolean);
103
+ console.log(`${ok ? '✅' : '❌'} group box strokeLinecap + Linejoin = round:`, JSON.stringify(results),
104
+ '\n boxes:', probe.map(b => ({ linecap: b.linecapAttr, linejoin: b.linejoinAttr, sw: b.strokeWidth, dash: b.strokeDasharray })));
105
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,108 @@
1
+ /* Round 366 verification: group label member-count tspan fontWeight
2
+ * 400 → 500. Sibling polish to R363 recent-row alias text fw 400 →
3
+ * 500 + R364 legend-row label fw 400 → 500. Closes the per-row
4
+ * 'count is fw 500 against label-tier fw 700' pattern at the
5
+ * group-label scope (grid layout cluster mark).
6
+ *
7
+ * Hierarchy snapshot post-R366 across 3 row surfaces:
8
+ * recent count(hot/cold) fw 700/600 (R320)
9
+ * recent alias fw 500 (R363)
10
+ * legend count fw 600 (R309)
11
+ * legend label fw 500 (R364)
12
+ * group name fw 700 (legacy)
13
+ * group count fw 500 (R366, this round)
14
+ *
15
+ * Contract (cyber, grid layout — group label only appears in grid):
16
+ * - Each rendered [data-group-label-count] tspan has
17
+ * font-weight === '500'.
18
+ * - data-group-label-count-font-weight === '500'.
19
+ * - Pre-R366 invariants:
20
+ * * dx="6" + fontSize="11" + tabular-nums
21
+ * * R229 fill-inherit (no explicit fill on tspan)
22
+ * * data-group-label-count-value still surfaces the count
23
+ */
24
+ import { chromium } from 'playwright';
25
+ import { readFileSync } from 'node:fs';
26
+
27
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
28
+ const browser = await chromium.launch({ headless: true });
29
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
30
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
31
+ await ctx.addInitScript(() => {
32
+ try {
33
+ localStorage.setItem('anet-theme', 'cyber');
34
+ sessionStorage.setItem('anet_v3_auth', '1');
35
+ // Pre-select grid layout (R106 cluster algorithm + group labels).
36
+ localStorage.setItem('anet-layout', JSON.stringify('grid'));
37
+ } catch {}
38
+ });
39
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
40
+ await ctx.route('**/api/hub/status*', async (route) => {
41
+ const r = await route.fetch();
42
+ const b = await r.json();
43
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
44
+ const mk = (alias) => ({
45
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
46
+ network_id: nid, project_dir: null,
47
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
48
+ });
49
+ // Multiple alpha/beta nodes → cluster groups render with member counts.
50
+ await route.fulfill({ response: r, json: { ...b, sessions: [
51
+ mk('alpha-1'), mk('alpha-2'), mk('alpha-3'),
52
+ mk('beta-1'), mk('beta-2'),
53
+ ] } });
54
+ });
55
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
56
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
57
+
58
+ const page = await ctx.newPage();
59
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
60
+ // In case grid layout didn't take from localStorage, click the grid toggle.
61
+ await page.waitForSelector('[data-topo-chrome-layout="grid"]', { timeout: 15000 });
62
+ const layoutActive = await page.getAttribute('[data-topo-chrome-layout="grid"]', 'data-topo-chrome-layout-active');
63
+ if (layoutActive !== 'true') {
64
+ await page.click('[data-topo-chrome-layout="grid"]');
65
+ }
66
+ await page.waitForSelector('[data-group-label-count]', { timeout: 10000 });
67
+ await page.waitForTimeout(400);
68
+
69
+ const probe = await page.evaluate(() => {
70
+ const tspans = Array.from(document.querySelectorAll('[data-group-label-count]'));
71
+ return tspans.map(el => {
72
+ const cs = getComputedStyle(el);
73
+ return {
74
+ key: el.getAttribute('data-group-label-count'),
75
+ fontWeightAttr: el.getAttribute('font-weight'),
76
+ fontWeightData: el.getAttribute('data-group-label-count-font-weight'),
77
+ fontSizeAttr: el.getAttribute('font-size'),
78
+ countValue: el.getAttribute('data-group-label-count-value'),
79
+ fontVariant: cs.fontVariantNumeric,
80
+ explicitFill: el.getAttribute('fill'), // R229: should be null (inherit)
81
+ };
82
+ });
83
+ });
84
+
85
+ await browser.close();
86
+
87
+ const allOk = probe.length > 0 && probe.every(r =>
88
+ r.fontWeightAttr === '500'
89
+ && r.fontWeightData === '500'
90
+ && r.fontSizeAttr === '11'
91
+ && /tabular-nums/.test(r.fontVariant || '')
92
+ && (r.countValue || '').length > 0
93
+ && r.explicitFill === null // R229 fill-inherit invariant
94
+ );
95
+
96
+ const results = {
97
+ rows_rendered: probe.length >= 1,
98
+ all_rows_fw_500: probe.every(r => r.fontWeightAttr === '500'),
99
+ all_rows_data_500: probe.every(r => r.fontWeightData === '500'),
100
+ all_rows_fontsize_11: probe.every(r => r.fontSizeAttr === '11'),
101
+ all_rows_tabular: probe.every(r => /tabular-nums/.test(r.fontVariant || '')),
102
+ all_rows_have_count: probe.every(r => (r.countValue || '').length > 0),
103
+ all_rows_fill_inherit: probe.every(r => r.explicitFill === null),
104
+ };
105
+ const ok = allOk && Object.values(results).every(Boolean);
106
+ console.log(`${ok ? '✅' : '❌'} group-label count fw 400 → 500:`, JSON.stringify(results),
107
+ '\n rows:', probe.map(r => ({ key: r.key, fw: r.fontWeightAttr, count: r.countValue })));
108
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,100 @@
1
+ /* Round 444 verification: group label count tspan fontWeight lift —
2
+ * 500 → 600 on isPinned. Mirror of R416/R424/R425/R426 "data tightens
3
+ * under attention" pattern at group-label-count scope.
4
+ *
5
+ * Contract:
6
+ * - rest: every group-label-count reports font-weight '500' +
7
+ * pinned='false'
8
+ * - click a group-label-hit: that group's count flips to '600' +
9
+ * pinned='true'
10
+ * - siblings stay rest
11
+ */
12
+ import { chromium } from 'playwright';
13
+ import { readFileSync } from 'node:fs';
14
+
15
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
16
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
17
+
18
+ const browser = await chromium.launch({ headless: true });
19
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
20
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
21
+ await ctx.addInitScript(() => {
22
+ try {
23
+ localStorage.setItem('anet-theme', 'cyber');
24
+ localStorage.setItem('anet-topo-layout', 'grid');
25
+ sessionStorage.setItem('anet_v3_auth', '1');
26
+ } catch {}
27
+ });
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, status) => ({
33
+ alias, status, 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: [
38
+ mk('alpha·1', 'working'),
39
+ mk('alpha·2', 'idle'),
40
+ mk('beta·1', 'working'),
41
+ mk('beta·2', 'idle'),
42
+ ] } });
43
+ });
44
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
45
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
46
+
47
+ const page = await ctx.newPage();
48
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
49
+ await page.waitForSelector('[data-group-label-count]', { timeout: 15000 });
50
+ await page.waitForTimeout(400);
51
+
52
+ const readAll = () => page.evaluate(() => {
53
+ const ts = [...document.querySelectorAll('[data-group-label-count]')];
54
+ return ts.map(t => ({
55
+ key: t.getAttribute('data-group-label-count'),
56
+ fw: t.getAttribute('font-weight'),
57
+ pinned: t.getAttribute('data-group-label-count-pinned'),
58
+ }));
59
+ });
60
+
61
+ const rest = await readAll();
62
+ const firstKey = rest[0]?.key;
63
+
64
+ // Pin the first group by clicking its label-hit wrapper
65
+ let pinned = null;
66
+ if (firstKey) {
67
+ const hit = await page.$(`[data-group-label-hit="${firstKey}"]`);
68
+ if (hit) {
69
+ await hit.click();
70
+ await page.waitForTimeout(250);
71
+ pinned = await readAll();
72
+ }
73
+ }
74
+
75
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
76
+ const sourceWired = /fontWeight=\{isPinned \? '600' : '500'\}/.test(fileText);
77
+
78
+ await browser.close();
79
+
80
+ const restAll500 = rest.every(r => r.fw === '500');
81
+ const restNoPin = rest.every(r => r.pinned === 'false');
82
+ const pinnedEntry = pinned?.find(r => r.key === firstKey);
83
+ const pinnedFw_600 = pinnedEntry?.fw === '600';
84
+ const pinnedFlag = pinnedEntry?.pinned === 'true';
85
+ const othersRest = pinned ? pinned.filter(r => r.key !== firstKey).every(r => r.fw === '500' && r.pinned === 'false') : false;
86
+
87
+ const results = {
88
+ rest_count_ge_2: rest.length >= 2,
89
+ rest_all_fw_500: restAll500,
90
+ rest_no_pin: restNoPin,
91
+ pinned_target_fw_600: pinnedFw_600,
92
+ pinned_target_flag: pinnedFlag,
93
+ pinned_others_stay: othersRest,
94
+ source_wired: sourceWired,
95
+ };
96
+ const ok = Object.values(results).every(Boolean);
97
+ console.log(`${ok ? '✅' : '❌'} group label count fw pin:`, JSON.stringify(results),
98
+ '\n rest:', JSON.stringify(rest),
99
+ '\n pinned target:', JSON.stringify(pinnedEntry));
100
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,99 @@
1
+ /* Round 457 verification: group label parent text fontWeight 700 → 800
2
+ * on isPinned. Adds typographic weight axis at parent-text scope.
3
+ *
4
+ * Contract:
5
+ * - rest: every group-label parent <text> reports font-weight '700' +
6
+ * pinned='false'
7
+ * - click a group-label-hit: that group's <text> flips to '800' +
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 {
21
+ localStorage.setItem('anet-theme', 'cyber');
22
+ localStorage.setItem('anet-topo-layout', 'grid');
23
+ sessionStorage.setItem('anet_v3_auth', '1');
24
+ } 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
+ await route.fulfill({ response: r, json: { ...b, sessions: [
36
+ mk('alpha·1', 'working'),
37
+ mk('alpha·2', 'idle'),
38
+ mk('beta·1', 'working'),
39
+ mk('beta·2', 'idle'),
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-group-label]', { timeout: 15000 });
48
+ await page.waitForTimeout(400);
49
+
50
+ const readAll = () => page.evaluate(() => {
51
+ const ts = [...document.querySelectorAll('[data-group-label]')];
52
+ return ts.map(t => ({
53
+ key: t.getAttribute('data-group-label'),
54
+ fw: t.getAttribute('font-weight'),
55
+ fw_d: t.getAttribute('data-group-label-font-weight'),
56
+ pinned: t.getAttribute('data-group-label-pinned'),
57
+ }));
58
+ });
59
+
60
+ const rest = await readAll();
61
+ const firstKey = rest[0]?.key;
62
+
63
+ // Pin first group by clicking its hit-area
64
+ let pinned = null;
65
+ if (firstKey) {
66
+ const hit = await page.$(`[data-group-label-hit="${firstKey}"]`);
67
+ if (hit) {
68
+ await hit.click();
69
+ await page.waitForTimeout(250);
70
+ pinned = await readAll();
71
+ }
72
+ }
73
+
74
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
75
+ const sourceWired = /fontWeight=\{isPinned \? '800' : '700'\}/.test(fileText);
76
+
77
+ await browser.close();
78
+
79
+ const restAll700 = rest.every(r => r.fw === '700');
80
+ const restNoPin = rest.every(r => r.pinned === 'false');
81
+ const pinnedEntry = pinned?.find(r => r.key === firstKey);
82
+ const pinFw800 = pinnedEntry?.fw === '800';
83
+ const pinFlag = pinnedEntry?.pinned === 'true';
84
+ const othersRest = pinned ? pinned.filter(r => r.key !== firstKey).every(r => r.fw === '700' && r.pinned === 'false') : false;
85
+
86
+ const results = {
87
+ rest_count_ge_2: rest.length >= 2,
88
+ rest_all_fw_700: restAll700,
89
+ rest_no_pin: restNoPin,
90
+ pinned_target_fw_800: pinFw800,
91
+ pinned_target_flag: pinFlag,
92
+ pinned_others_stay: othersRest,
93
+ source_wired: sourceWired,
94
+ };
95
+ const ok = Object.values(results).every(Boolean);
96
+ console.log(`${ok ? '✅' : '❌'} group label fw pin:`, JSON.stringify(results),
97
+ '\n rest:', JSON.stringify(rest),
98
+ '\n pinned target:', JSON.stringify(pinnedEntry));
99
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,113 @@
1
+ /* Round 432 verification: group label text 3-tier letter-spacing —
2
+ * rest 0 / hover 0.25 / pinned 0.5. Extends R427 alias 3-tier and
3
+ * R431 edge-badge 3-tier to group-label scope. Hover-letter-spacing
4
+ * family now 8 anchors.
5
+ *
6
+ * Contract:
7
+ * - need >1 alias prefix group to mount group boxes (grid layout)
8
+ * - rest: every group-label reports letter-spacing '0px' (or
9
+ * 'normal' computed), pinned=false, hovered=false
10
+ * - hover one label-hit: that group-label reports letter-spacing
11
+ * '0.25px' + hovered=true (R432 mid tier)
12
+ * - source-file probe confirms 3-tier conditional
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
+ // Force grid layout (the layout toggle persists in localStorage)
25
+ try {
26
+ localStorage.setItem('anet-theme', 'cyber');
27
+ localStorage.setItem('anet-topo-layout', 'grid');
28
+ sessionStorage.setItem('anet_v3_auth', '1');
29
+ } 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
+ // Sessions with alias prefix groups: alpha·X / beta·X / gamma·X
41
+ await route.fulfill({ response: r, json: { ...b, sessions: [
42
+ mk('alpha·1', 'working'),
43
+ mk('alpha·2', 'idle'),
44
+ mk('beta·1', 'working'),
45
+ mk('beta·2', 'idle'),
46
+ mk('gamma·1', 'working'),
47
+ mk('gamma·2', 'idle'),
48
+ ] } });
49
+ });
50
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
51
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
52
+
53
+ const page = await ctx.newPage();
54
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
55
+ await page.waitForSelector('[data-group-label]', { timeout: 15000 });
56
+ await page.waitForTimeout(400);
57
+
58
+ const readAll = () => page.evaluate(() => {
59
+ const ts = [...document.querySelectorAll('[data-group-label]')];
60
+ return ts.map(t => ({
61
+ key: t.getAttribute('data-group-label'),
62
+ ls: t.style.letterSpacing,
63
+ pinned: t.getAttribute('data-group-label-pinned'),
64
+ hovered: t.getAttribute('data-group-label-hovered'),
65
+ }));
66
+ });
67
+
68
+ const rest = await readAll();
69
+ const firstKey = rest[0]?.key;
70
+ let hover = null;
71
+ if (firstKey) {
72
+ const box = await page.evaluate((key) => {
73
+ // Hover the label-hit <g> wrapper which dispatches onPointerEnter →
74
+ // setHoveredGroupLabel(key). The associated <text> shares the same
75
+ // box.key as data-group-label.
76
+ const hit = document.querySelector(`[data-group-label-hit="${key}"]`);
77
+ if (!hit) return null;
78
+ const b = hit.getBoundingClientRect();
79
+ return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
80
+ }, firstKey);
81
+ if (box) {
82
+ await page.mouse.move(box.x, box.y);
83
+ await page.waitForTimeout(300);
84
+ hover = await readAll();
85
+ await page.mouse.move(0, 0);
86
+ }
87
+ }
88
+
89
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
90
+ const sourceWired = /letterSpacing:\s*isPinned\s*\?\s*'0\.5px'\s*:\s*\n?\s*isHovered\s*\?\s*'0\.25px'\s*:\s*'0px'/.test(fileText);
91
+
92
+ await browser.close();
93
+
94
+ const isRest = (ls) => ls === '0px' || ls === '' || ls === 'normal';
95
+
96
+ const hoveredEntry = hover?.find(r => r.key === firstKey);
97
+ const othersRest = hover ? hover.filter(r => r.key !== firstKey).every(r => isRest(r.ls)) : false;
98
+
99
+ const results = {
100
+ rest_count_gte_2: rest.length >= 2,
101
+ rest_all_zero: rest.every(r => isRest(r.ls)),
102
+ rest_all_pinned_false: rest.every(r => r.pinned === 'false'),
103
+ rest_all_hovered_false: rest.every(r => r.hovered === 'false'),
104
+ hover_target_ls_0_25: hoveredEntry?.ls === '0.25px',
105
+ hover_target_hovered_attr: hoveredEntry?.hovered === 'true',
106
+ hover_others_stay_rest: othersRest,
107
+ source_three_tier_wired: sourceWired,
108
+ };
109
+ const ok = Object.values(results).every(Boolean);
110
+ console.log(`${ok ? '✅' : '❌'} group label 3-tier letter-spacing:`, JSON.stringify(results),
111
+ '\n rest count:', rest.length,
112
+ '\n hover target:', JSON.stringify(hoveredEntry));
113
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,94 @@
1
+ /* Round 460 verification: group-label tint rect transition list
2
+ * extends to include `x 200ms, width 200ms`. Pre-R460 the hitbox
3
+ * snap-jumped when a cluster grew/shrunk (member joins or leaves
4
+ * re-priced box.x / box.w); R460 tweens both geometry axes so
5
+ * the rect slides into the new bounds matching codex preview.125
6
+ * + R459 200ms motion vocabulary.
7
+ *
8
+ * Contract:
9
+ * - every <rect data-group-label-tint-geom-transition="x,width">
10
+ * renders inside the group cluster boundary
11
+ * - inline style includes 'x 200ms ease-out' AND 'width 200ms
12
+ * ease-out' alongside the R459 fill+opacity transitions
13
+ * - source-file conditional wired
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
+ const browser = await chromium.launch({ headless: true });
22
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
23
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
24
+ await ctx.addInitScript(() => {
25
+ try {
26
+ localStorage.setItem('anet-theme', 'cyber');
27
+ localStorage.setItem('anet-topo-layout', 'grid');
28
+ sessionStorage.setItem('anet_v3_auth', '1');
29
+ } 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·1', 'working'),
42
+ mk('alpha·2', 'idle'),
43
+ mk('beta·1', 'working'),
44
+ mk('beta·2', 'idle'),
45
+ ] } });
46
+ });
47
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
48
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
49
+
50
+ const page = await ctx.newPage();
51
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
52
+ await page.waitForSelector('[data-group-label]', { timeout: 15000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ const probe = await page.evaluate(() => {
56
+ const tints = [...document.querySelectorAll('[data-group-label-tint-geom-transition]')];
57
+ return {
58
+ count: tints.length,
59
+ nodes: tints.map(t => ({
60
+ geom: t.getAttribute('data-group-label-tint-geom-transition'),
61
+ style: t.getAttribute('style') || '',
62
+ })),
63
+ };
64
+ });
65
+
66
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
67
+ const sourceHasGeom = /data-group-label-tint-geom-transition="x,width"/.test(src);
68
+ const sourceHasXTween = /x 200ms ease-out/.test(src);
69
+ const sourceHasWidthTween = /width 200ms ease-out/.test(src);
70
+
71
+ await browser.close();
72
+
73
+ const countGe2 = probe.count >= 2;
74
+ const allDataAttrXW = probe.nodes.every(n => n.geom === 'x,width');
75
+ const allStyleHasX = probe.nodes.every(n => /x 200ms ease-out/.test(n.style));
76
+ const allStyleHasW = probe.nodes.every(n => /width 200ms ease-out/.test(n.style));
77
+ const allStylePreservesFillOpacity = probe.nodes.every(n =>
78
+ /fill 200ms ease-out/.test(n.style) && /opacity 200ms ease-out/.test(n.style));
79
+
80
+ const results = {
81
+ tint_count_ge_2: countGe2,
82
+ all_data_attr_xw: allDataAttrXW,
83
+ all_style_has_x: allStyleHasX,
84
+ all_style_has_width: allStyleHasW,
85
+ all_style_preserves_fill_opacity: allStylePreservesFillOpacity,
86
+ source_geom_attr_wired: sourceHasGeom,
87
+ source_x_tween_wired: sourceHasXTween,
88
+ source_w_tween_wired: sourceHasWidthTween,
89
+ };
90
+ const ok = Object.values(results).every(Boolean);
91
+ console.log(`${ok ? '✅' : '❌'} group label tint geom transition x+width:`, JSON.stringify(results),
92
+ '\n count:', probe.count,
93
+ '\n first style:', probe.nodes[0]?.style);
94
+ process.exit(ok ? 0 : 1);