@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,89 @@
1
+ /* Round 381 verification: edge visible flow path strokeLinecap='round'.
2
+ * Sibling SVG stroke-softening polish to R378 flow-rail linecap — both
3
+ * flow-element paths now share 'round' linecap. R380 group box closed
4
+ * dash-cap + corner softening; R381 closes the flow-path family at the
5
+ * visible primary path.
6
+ *
7
+ * Stroke-softening family snapshot post-R381:
8
+ * R288 chrome icons strokeLinecap='round'
9
+ * R378 flow-rail dashes strokeLinecap='round'
10
+ * R380 group box dashes strokeLinecap='round'
11
+ * R381 flow visible path strokeLinecap='round' (this round)
12
+ * R379 viewport rect strokeLinejoin='round'
13
+ * R380 group box corners strokeLinejoin='round'
14
+ *
15
+ * Contract:
16
+ * - [data-edge-visible] element stroke-linecap attr === 'round'.
17
+ * - data-edge-visible-linecap === 'round'.
18
+ * - Pre-R381 invariants:
19
+ * * fill='none', stroke=pal.flowEdge
20
+ * * markerEnd referencing arrow id
21
+ * * opacity-based freshness preserved (any value in 0..1)
22
+ * * transition list contains opacity + stroke-width + stroke
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 { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
33
+ });
34
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
35
+ await ctx.route('**/api/hub/status*', async (route) => {
36
+ const r = await route.fetch();
37
+ const b = await r.json();
38
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
39
+ const mk = (alias) => ({
40
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
41
+ network_id: nid, project_dir: null,
42
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
43
+ });
44
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b') ] } });
45
+ });
46
+ const now = Date.now();
47
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
48
+ { id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
49
+ network_id: 'default', created_at: new Date(now - 5000).toISOString() },
50
+ ] } }));
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-edge-visible]', { timeout: 15000 });
56
+ await page.waitForTimeout(300);
57
+
58
+ const probe = await page.evaluate(() => {
59
+ const p = document.querySelector('[data-edge-visible]');
60
+ const cs = p ? getComputedStyle(p) : null;
61
+ return {
62
+ linecapAttr: p?.getAttribute('stroke-linecap') ?? null,
63
+ linecapData: p?.getAttribute('data-edge-visible-linecap') ?? null,
64
+ fillAttr: p?.getAttribute('fill') ?? null,
65
+ markerEndAttr: p?.getAttribute('marker-end') ?? null,
66
+ transition: cs?.transition ?? null,
67
+ linkKey: p?.getAttribute('data-edge-visible') ?? null,
68
+ };
69
+ });
70
+
71
+ await browser.close();
72
+
73
+ const hasTrans = (s, prop) =>
74
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
75
+
76
+ const results = {
77
+ linecap_round: probe.linecapAttr === 'round',
78
+ data_linecap_round: probe.linecapData === 'round',
79
+ fill_none: probe.fillAttr === 'none',
80
+ marker_end_arrow: /^url\(#topo-arrow-/.test(probe.markerEndAttr || ''),
81
+ trans_has_opacity: hasTrans(probe.transition, 'opacity'),
82
+ trans_has_stroke_w: hasTrans(probe.transition, 'stroke-width'),
83
+ trans_has_stroke: hasTrans(probe.transition, 'stroke'),
84
+ link_key_present: (probe.linkKey || '').length > 0,
85
+ };
86
+ const ok = Object.values(results).every(Boolean);
87
+ console.log(`${ok ? '✅' : '❌'} edge visible path strokeLinecap='round':`, JSON.stringify(results),
88
+ '\n probe:', probe);
89
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,89 @@
1
+ /* Round 442 verification: endpoint emphasis ring radius hover lift —
2
+ * r=radius+7 → radius+8 on isEndpoint. Closes 3-axis hover-elevation
3
+ * parity at endpoint ring scope (r + sw + opacity).
4
+ *
5
+ * Contract:
6
+ * - rest: every endpoint ring reports radius='27' (default node
7
+ * radius 20 + 7) + active='false'
8
+ * - source-file probe confirms conditional + transition extension
9
+ *
10
+ * Test design: edge-hover dispatch on the hitbox path is unreliable
11
+ * for React onMouseEnter (same R423/R424 trap) — source-file probe
12
+ * is the canonical contract for the isEndpoint branch; DOM probe
13
+ * confirms rest branch resolves correctly.
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: 1500 } });
23
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
24
+ await ctx.addInitScript(() => {
25
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
26
+ });
27
+ await ctx.route('**/api/hub/status*', async (route) => {
28
+ const r = await route.fetch();
29
+ const b = await r.json();
30
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
31
+ const mk = (alias, status) => ({
32
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
33
+ network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ });
36
+ await route.fulfill({ response: r, json: { ...b, sessions: [
37
+ mk('alpha', 'working'),
38
+ mk('beta', 'idle'),
39
+ ] } });
40
+ });
41
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
42
+ json: { messages: [
43
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
44
+ ] },
45
+ }));
46
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
47
+
48
+ const page = await ctx.newPage();
49
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
50
+ await page.waitForSelector('[data-edge-endpoint-ring]', { timeout: 15000 });
51
+ await page.waitForTimeout(400);
52
+
53
+ const rest = await page.evaluate(() => {
54
+ const cs = [...document.querySelectorAll('[data-edge-endpoint-ring]')];
55
+ return cs.map(c => ({
56
+ r: parseFloat(c.getAttribute('data-edge-endpoint-ring-radius') || '0'),
57
+ active: c.getAttribute('data-edge-endpoint-active'),
58
+ sw: c.getAttribute('data-edge-endpoint-ring-stroke-width'),
59
+ }));
60
+ });
61
+
62
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
63
+ const sourceR = /const endpointR = isEndpoint \? radius \+ 8 : radius \+ 7/.test(fileText);
64
+ const sourceTransition = /transition: 'opacity 180ms ease-out, stroke-width 180ms ease-out, r 180ms ease-out'/.test(fileText);
65
+
66
+ await browser.close();
67
+
68
+ // nodeRadius default scales by nodeScale & status; in our fixture working alpha
69
+ // likely radius=20 → endpointR=27. Just check ≥2 rings mount at the rest value (radius+7).
70
+ const restAnyMounted = rest.length >= 2;
71
+ const restAllActiveFalse = rest.every(r => r.active === 'false');
72
+ const restAllSwRest = rest.every(r => r.sw === '1.6');
73
+ // Verify radius is consistent across all rings: radius+7 (any positive number, all equal)
74
+ const allSameRadius = rest.length > 0 && rest.every(r => r.r === rest[0].r);
75
+ const restRadiusPositive = (rest[0]?.r || 0) > 0;
76
+
77
+ const results = {
78
+ rest_count_ge_2: restAnyMounted,
79
+ rest_all_active_false: restAllActiveFalse,
80
+ rest_all_sw_1_6: restAllSwRest,
81
+ rest_all_radius_equal: allSameRadius,
82
+ rest_radius_positive: restRadiusPositive,
83
+ source_endpointR_wired: sourceR,
84
+ source_transition_wired: sourceTransition,
85
+ };
86
+ const ok = Object.values(results).every(Boolean);
87
+ console.log(`${ok ? '✅' : '❌'} endpoint-ring r hover lift:`, JSON.stringify(results),
88
+ '\n rest:', JSON.stringify(rest));
89
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,101 @@
1
+ /* Round 397 verification: filter pin pills gain hover lift
2
+ * translateY(-1px). Sibling to R143 recent-row lift idiom —
3
+ * extends the "rising on hover" gesture from SVG canvas surfaces
4
+ * to the HTML filter chip strip. All 4 filter pill variants
5
+ * (status / group / vendor / edge) carry the lift.
6
+ *
7
+ * Contract:
8
+ * - Source-file verification of the new className tokens
9
+ * (transition-transform + hover:-translate-y-px + transform-gpu)
10
+ * - The data-topo-filter-pill-hover-lift="true" sentinel attr
11
+ * is mounted when any filter pill is rendered (pinned filter
12
+ * present). Test mounts a status filter by clicking a status
13
+ * chip, then probes the resulting pill.
14
+ * - Pre-R397 invariants preserved on the pill className:
15
+ * * group / rounded-md / font-mono / font-medium / text-xs
16
+ * / border / anet-fade-in / anet-topo-chip-focus
17
+ *
18
+ * Note: hover dispatch on HTML chips in headless can fire but
19
+ * computing the post-hover translate-y reliably requires a mouse
20
+ * sweep. Test asserts the className tokens are present (which
21
+ * is what Tailwind compiles into `:hover` CSS) plus the sentinel
22
+ * attr — both deterministic from source.
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
29
+
30
+ const browser = await chromium.launch({ headless: true });
31
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
32
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
33
+ await ctx.addInitScript(() => {
34
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
35
+ });
36
+ await ctx.route('**/api/hub/status*', async (route) => {
37
+ const r = await route.fetch();
38
+ const b = await r.json();
39
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
40
+ const mk = (alias, status) => ({
41
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
42
+ network_id: nid, project_dir: null,
43
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
44
+ });
45
+ await route.fulfill({ response: r, json: { ...b, sessions: [
46
+ mk('alpha', 'working'),
47
+ mk('beta', 'idle'),
48
+ mk('gamma', 'idle'),
49
+ ] } });
50
+ });
51
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
52
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
53
+
54
+ const page = await ctx.newPage();
55
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
56
+ await page.waitForSelector('g[data-node]', { timeout: 15000 });
57
+ await page.waitForTimeout(400);
58
+
59
+ // Click a legend status row to pin the status filter → mounts the
60
+ // filter pill. Use the alpha node since `working` row exists in fixture.
61
+ await page.click('[data-legend-status="working"]');
62
+ await page.waitForSelector('[data-topo-filter-pill-hover-lift="true"]', { timeout: 5000 });
63
+
64
+ const probe = await page.evaluate(() => {
65
+ const pill = document.querySelector('[data-topo-filter-pill-hover-lift="true"]');
66
+ return pill ? {
67
+ className: pill.className,
68
+ activeFilter: pill.getAttribute('data-active-filter'),
69
+ } : null;
70
+ });
71
+
72
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
73
+ const occurrences = (fileText.match(/hover:-translate-y-px/g) || []).length;
74
+ const sentinelOccurrences = (fileText.match(/data-topo-filter-pill-hover-lift="true"/g) || []).length;
75
+
76
+ await browser.close();
77
+
78
+ const cls = probe?.className || '';
79
+ const results = {
80
+ // Sentinel attr mounted on the filter pill
81
+ pill_mounted: !!probe,
82
+ // R397: hover-lift tokens present in className
83
+ has_transition_transform: cls.includes('transition-transform'),
84
+ has_hover_translate: cls.includes('hover:-translate-y-px'),
85
+ has_transform_gpu: cls.includes('transform-gpu'),
86
+ has_duration_200: cls.includes('duration-200'),
87
+ has_ease_out: cls.includes('ease-out'),
88
+ // Pre-R397 className tokens preserved
89
+ has_rounded_md: cls.includes('rounded-md'),
90
+ has_font_mono: cls.includes('font-mono'),
91
+ has_anet_topo_chip_focus: cls.includes('anet-topo-chip-focus'),
92
+ // Source-file replace_all coverage: 3 "group ..." pills + 1 plain pill = 4 occurrences
93
+ source_4_pills_lifted: occurrences === 4,
94
+ source_4_sentinels: sentinelOccurrences === 4,
95
+ };
96
+ const ok = Object.values(results).every(Boolean);
97
+ console.log(`${ok ? '✅' : '❌'} filter pin pills hover lift translateY(-1px):`, JSON.stringify(results),
98
+ '\n className:', cls,
99
+ '\n activeFilter:', probe?.activeFilter,
100
+ '\n occurrences:', occurrences, '/ sentinels:', sentinelOccurrences);
101
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,110 @@
1
+ /* Round 355 verification: filter pin pills (status / group / vendor)
2
+ * gain pure-CSS group-hover lift of their inner opacity-70 spans
3
+ * (prefix `filter:` + count `· N`) to 100 % on pill hover. Marks the
4
+ * whole pill as scan-it interactive without layout reflow (opacity
5
+ * is paint-only). The R335 prefix-opacity tier + R323 count-opacity
6
+ * tier are preserved at rest; both ease to 100 % on pill hover.
7
+ *
8
+ * Contract (cyber, status filter pinned to 'working'):
9
+ * - Rest: filter-prefix + filter-pill-count computed opacity 0.7.
10
+ * - Hover pill: both spans computed opacity 1.
11
+ * - Inline transition contains 'opacity'.
12
+ * - Pre-R355 invariants:
13
+ * * R335 prefix span keeps `hidden sm:inline opacity-70` baseline
14
+ * * R323 count span keeps `opacity-70 tabular-nums` baseline
15
+ * * Pill click-to-clear unchanged (still has onClick / cursor)
16
+ */
17
+ import { chromium } from 'playwright';
18
+ import { readFileSync } from 'node:fs';
19
+
20
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
21
+ const browser = await chromium.launch({ headless: true });
22
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
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
+ sessionStorage.setItem('anet_v3_auth', '1');
28
+ // Pre-pin status='working' so the filter pill mounts on first paint.
29
+ localStorage.setItem('anet-pinnedStatus', JSON.stringify('working'));
30
+ } catch {}
31
+ });
32
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
33
+ await ctx.route('**/api/hub/status*', async (route) => {
34
+ const r = await route.fetch();
35
+ const b = await r.json();
36
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
37
+ const mk = (alias) => ({
38
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
39
+ network_id: nid, project_dir: null,
40
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
41
+ });
42
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b'), mk('c') ] } });
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
+ // If pre-pin via localStorage didn't take, click the working chip to pin.
50
+ await page.waitForSelector('[data-working-chip]', { timeout: 15000 });
51
+ const pillVisible = await page.locator('[data-active-filter="status"]').count();
52
+ if (pillVisible === 0) {
53
+ await page.click('[data-working-chip]');
54
+ await page.waitForSelector('[data-active-filter="status"]', { timeout: 5000 });
55
+ }
56
+ await page.waitForTimeout(300);
57
+
58
+ const restProbe = await page.evaluate(() => {
59
+ const pill = document.querySelector('[data-active-filter="status"]');
60
+ const prefix = pill?.querySelector('[data-filter-prefix]');
61
+ const count = pill?.querySelector('[data-filter-pill-count]');
62
+ return {
63
+ prefixOpacity: prefix ? getComputedStyle(prefix).opacity : null,
64
+ countOpacity: count ? getComputedStyle(count).opacity : null,
65
+ prefixTrans: prefix ? getComputedStyle(prefix).transition : null,
66
+ countTrans: count ? getComputedStyle(count).transition : null,
67
+ prefixCls: prefix?.getAttribute('class') ?? null,
68
+ countCls: count?.getAttribute('class') ?? null,
69
+ pillCursor: pill ? getComputedStyle(pill).cursor : null,
70
+ };
71
+ });
72
+
73
+ // Hover the pill.
74
+ await page.hover('[data-active-filter="status"]');
75
+ await page.waitForTimeout(300);
76
+ const hoverProbe = await page.evaluate(() => {
77
+ const pill = document.querySelector('[data-active-filter="status"]');
78
+ const prefix = pill?.querySelector('[data-filter-prefix]');
79
+ const count = pill?.querySelector('[data-filter-pill-count]');
80
+ return {
81
+ prefixOpacity: prefix ? getComputedStyle(prefix).opacity : null,
82
+ countOpacity: count ? getComputedStyle(count).opacity : null,
83
+ };
84
+ });
85
+
86
+ await browser.close();
87
+
88
+ const approxEq = (a, b, tol = 0.02) => {
89
+ const x = parseFloat(a); const y = parseFloat(b);
90
+ return Number.isFinite(x) && Number.isFinite(y) && Math.abs(x - y) <= tol;
91
+ };
92
+ const hasTrans = (s, prop) =>
93
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
94
+
95
+ const results = {
96
+ rest_prefix_0_7: approxEq(restProbe.prefixOpacity, 0.7),
97
+ rest_count_0_7: approxEq(restProbe.countOpacity, 0.7),
98
+ prefix_trans_has_op: hasTrans(restProbe.prefixTrans, 'opacity'),
99
+ count_trans_has_op: hasTrans(restProbe.countTrans, 'opacity'),
100
+ prefix_cls_has_op_70: /opacity-70/.test(restProbe.prefixCls || ''), // R335
101
+ count_cls_has_tabular: /tabular-nums/.test(restProbe.countCls || ''), // R323
102
+ pill_cursor_pointer: restProbe.pillCursor === 'pointer',
103
+ hover_prefix_1: approxEq(hoverProbe.prefixOpacity, 1),
104
+ hover_count_1: approxEq(hoverProbe.countOpacity, 1),
105
+ };
106
+ const ok = Object.values(results).every(Boolean);
107
+ console.log(`${ok ? '✅' : '❌'} filter pill hover-opacity lift:`, JSON.stringify(results),
108
+ '\n rest: ', restProbe,
109
+ '\n hover:', hoverProbe);
110
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,88 @@
1
+ /* Round 412 verification: filter pin pill VALUE picks up fw=600
2
+ * (font-semibold). Extends chip-internal-hierarchy arc (11th
3
+ * anchor) — pill now reads with proper data-tier emphasis around
4
+ * its R355 opacity-70 prefix/suffix.
5
+ *
6
+ * Contract:
7
+ * - Source-file verification of 4 occurrences of
8
+ * `<span className="font-semibold" data-filter-value>`
9
+ * - Runtime: pin a status filter, probe the data-filter-value
10
+ * span on the mounted pill — className includes font-semibold
11
+ * + textContent matches the pinned value.
12
+ * - Pre-R412 invariants on the pill:
13
+ * * data-filter-prefix span still opacity-70
14
+ * * data-filter-pill-count still opacity-70 tabular-nums
15
+ * * R397 hover-translate-y-px className still present
16
+ * * R332/R355/R356 className tokens preserved
17
+ */
18
+ import { chromium } from 'playwright';
19
+ import { readFileSync } from 'node:fs';
20
+
21
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
22
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
23
+
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
+ 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, status) => ({
35
+ alias, status, 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: [
40
+ mk('alpha', 'working'),
41
+ mk('beta', '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('g[data-node]', { timeout: 15000 });
50
+
51
+ // Click legend status row to pin filter → mounts the filter pill
52
+ await page.click('[data-legend-status="working"]');
53
+ await page.waitForSelector('[data-filter-value]', { timeout: 5000 });
54
+
55
+ const probe = await page.evaluate(() => {
56
+ const valueSpan = document.querySelector('[data-filter-value]');
57
+ if (!valueSpan) return null;
58
+ const pill = valueSpan.closest('[data-topo-filter-pill-hover-lift="true"]');
59
+ return {
60
+ valueText: valueSpan.textContent,
61
+ valueClass: valueSpan.className,
62
+ valueFwComputed: getComputedStyle(valueSpan).fontWeight,
63
+ pillClass: pill?.className ?? null,
64
+ pillActiveFilter: pill?.getAttribute('data-active-filter') ?? null,
65
+ };
66
+ });
67
+
68
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
69
+ const valueOccurrences = (fileText.match(/<span className="font-semibold" data-filter-value>/g) || []).length;
70
+
71
+ await browser.close();
72
+
73
+ const results = {
74
+ // Runtime: value span has font-semibold
75
+ value_has_font_semibold: probe?.valueClass.includes('font-semibold') === true,
76
+ value_text_working: probe?.valueText === 'working',
77
+ value_fw_600: probe?.valueFwComputed === '600',
78
+ // Pill mounted with R397 hover-lift
79
+ pill_has_hover_lift: probe?.pillClass?.includes('hover:-translate-y-px') === true,
80
+ pill_active_filter: probe?.pillActiveFilter === 'status',
81
+ // Source-file: 4 occurrences (status + group + vendor + edge filters)
82
+ source_4_value_spans: valueOccurrences === 4,
83
+ };
84
+ const ok = Object.values(results).every(Boolean);
85
+ console.log(`${ok ? '✅' : '❌'} filter pin pill VALUE fw 500 → 600:`, JSON.stringify(results),
86
+ '\n probe:', probe,
87
+ '\n source occurrences:', valueOccurrences);
88
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,99 @@
1
+ /* Round 356 verification: filter pin pill × buttons gain hover:scale-
2
+ * 110 (Tailwind 4 modern CSS `scale` property). Pre-R356 the × had
3
+ * only hover:opacity-70 — target dimmed under cursor but didn't
4
+ * lift. R356 adds a 10 % scale so the click-target reads as "press
5
+ * me" alongside the dim. Sibling polish to R354 vendor letter glyph
6
+ * + R350/R352/R353 chrome icon hover-scales. replace_all touched
7
+ * all 4 filter pin pill × buttons (status / group / vendor / edge).
8
+ *
9
+ * Tailwind 4 compiles scale-110 to the modern CSS `scale` property
10
+ * — probes getComputedStyle(el).scale (verified during R352).
11
+ *
12
+ * Contract (cyber, status filter pinned to 'working'):
13
+ * - Rest: × button computed scale 'none' (no scale applied).
14
+ * - Hover ×: scale === '1.1'.
15
+ * - Inline transition (computed) contains scale/transform.
16
+ * - Pre-R356 invariants: hover:opacity-70 still applies (button
17
+ * dims on hover), cursor: pointer, data-active-filter attr on
18
+ * parent pill still intact.
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 {
29
+ localStorage.setItem('anet-theme', 'cyber');
30
+ sessionStorage.setItem('anet_v3_auth', '1');
31
+ localStorage.setItem('anet-pinnedStatus', JSON.stringify('working'));
32
+ } catch {}
33
+ });
34
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
35
+ await ctx.route('**/api/hub/status*', async (route) => {
36
+ const r = await route.fetch();
37
+ const b = await r.json();
38
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
39
+ const mk = (alias) => ({
40
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
41
+ network_id: nid, project_dir: null,
42
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
43
+ });
44
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b'), mk('c') ] } });
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-working-chip]', { timeout: 15000 });
52
+ const pillVisible = await page.locator('[data-active-filter="status"]').count();
53
+ if (pillVisible === 0) {
54
+ await page.click('[data-working-chip]');
55
+ await page.waitForSelector('[data-active-filter="status"]', { timeout: 5000 });
56
+ }
57
+ await page.waitForTimeout(300);
58
+
59
+ const restProbe = await page.evaluate(() => {
60
+ const pill = document.querySelector('[data-active-filter="status"]');
61
+ const x = pill?.querySelector('button[aria-label^="Clear "]');
62
+ const cs = x ? getComputedStyle(x) : null;
63
+ return {
64
+ scale: cs?.scale ?? null,
65
+ transition: cs?.transition ?? null,
66
+ cursor: cs?.cursor ?? null,
67
+ className: x?.getAttribute('class') ?? null,
68
+ pillAttr: pill?.getAttribute('data-active-filter') ?? null,
69
+ };
70
+ });
71
+
72
+ await page.hover('[data-active-filter="status"] button[aria-label^="Clear "]');
73
+ await page.waitForTimeout(300);
74
+ const hoverProbe = await page.evaluate(() => {
75
+ const x = document.querySelector('[data-active-filter="status"] button[aria-label^="Clear "]');
76
+ return { scale: x ? getComputedStyle(x).scale : null };
77
+ });
78
+
79
+ await browser.close();
80
+
81
+ const isRestScale = (s) => s === 'none' || s === '1' || s === '1 1' || s === null;
82
+ const isHoverScale110 = (s) => s === '1.1' || s === '1.1 1.1';
83
+ const hasTrans = (s, prop) =>
84
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
85
+
86
+ const results = {
87
+ rest_scale_1: isRestScale(restProbe.scale),
88
+ trans_has_scale: hasTrans(restProbe.transition, 'scale') || hasTrans(restProbe.transition, 'transform'),
89
+ cursor_pointer: restProbe.cursor === 'pointer',
90
+ cls_has_hover_op_70: /hover:opacity-70/.test(restProbe.className || ''), // R343
91
+ cls_has_hover_scale: /hover:scale-110/.test(restProbe.className || ''),
92
+ pill_attr_status: restProbe.pillAttr === 'status',
93
+ hover_scale_110: isHoverScale110(hoverProbe.scale),
94
+ };
95
+ const ok = Object.values(results).every(Boolean);
96
+ console.log(`${ok ? '✅' : '❌'} filter pill × hover-scale:`, JSON.stringify(results),
97
+ '\n rest: ', restProbe,
98
+ '\n hover: ', hoverProbe);
99
+ process.exit(ok ? 0 : 1);