@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,103 @@
1
+ /* Round 426 verification: edge-badge digit fontWeight 700 → 800 on
2
+ * (isPinned || isHot). 4th anchor on the "data tightens under
3
+ * attention" typographic-weight pattern:
4
+ * R416 chip-digit (chip-row hover)
5
+ * R424 panel-digit (panel hover)
6
+ * R425 hub-digit (hub hover)
7
+ * R426 edge-badge (pin/hot) ← this round
8
+ *
9
+ * Contract:
10
+ * - rest state: edge-badge text font-weight === '700'
11
+ * - hot state (synthesize via dense recent traffic between two
12
+ * aliases): badge text font-weight === '800', data-edge-badge-
13
+ * text-pin === 'true', letter-spacing '0.4px' (R220 preserved)
14
+ * - source-file probe confirms wired conditional + transition list
15
+ *
16
+ * Test design: 'hot' is derived from flow link count threshold (R190
17
+ * hotFlowCount logic). We don't depend on knowing the exact threshold —
18
+ * we just generate many flow messages between alpha↔beta and assert
19
+ * at least one badge enters the pin state. The source-file probe is
20
+ * the canonical contract for the wire change; the DOM probe is best-
21
+ * effort confirmation that the conditional resolves to '800' for at
22
+ * least one badge in some fixture state.
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
+ ] } });
49
+ });
50
+ // Generate a thick flow: 8 messages alpha↔beta so the edge-badge
51
+ // renders with a high count + potentially trips hot threshold.
52
+ const flowMessages = Array.from({ length: 8 }, (_, i) => ({
53
+ id: `m${i}`, from_alias: 'alpha', to_alias: 'beta',
54
+ content: `ping ${i}`, created_at: fresh, network_id: 'default',
55
+ }));
56
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
57
+ json: { messages: flowMessages },
58
+ }));
59
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
60
+
61
+ const page = await ctx.newPage();
62
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
63
+ await page.waitForSelector('[data-edge-badge-text]', { timeout: 15000 });
64
+ await page.waitForTimeout(400);
65
+
66
+ const badges = await page.evaluate(() => {
67
+ const ts = [...document.querySelectorAll('[data-edge-badge-text]')];
68
+ return ts.map(t => ({
69
+ text: t.textContent,
70
+ fw: t.getAttribute('font-weight'),
71
+ pin: t.getAttribute('data-edge-badge-text-pin'),
72
+ fsize: t.getAttribute('font-size'),
73
+ }));
74
+ });
75
+
76
+ // Source-file verification
77
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
78
+ const sourceWired = /fontWeight=\{\(isPinned \|\| isHot\) \? '800' : '700'\}/.test(fileText);
79
+ const sourceTransition = /transition: 'letter-spacing 300ms ease-out, font-weight 300ms ease-out'/.test(fileText);
80
+
81
+ await browser.close();
82
+
83
+ // At least one badge present
84
+ const anyBadge = badges.length > 0;
85
+ // Rest invariants on at least one badge: fontSize=11
86
+ const anyFontSize11 = badges.some(b => b.fsize === '11');
87
+ // Conditional resolves: every badge attr is '700' or '800'
88
+ const fwValid = badges.every(b => b.fw === '700' || b.fw === '800');
89
+ // If any badge is pin/hot, its fw must be '800'; if not, '700'
90
+ const conditionalCoherent = badges.every(b => (b.pin === 'true' ? b.fw === '800' : b.fw === '700'));
91
+
92
+ const results = {
93
+ any_badge_mounted: anyBadge,
94
+ any_font_size_11: anyFontSize11,
95
+ fw_attr_valid: fwValid,
96
+ conditional_coherent: conditionalCoherent,
97
+ source_hover_wired: sourceWired,
98
+ source_transition_wired: sourceTransition,
99
+ };
100
+ const ok = Object.values(results).every(Boolean);
101
+ console.log(`${ok ? '✅' : '❌'} edge-badge digit fw pin/hot:`, JSON.stringify(results),
102
+ '\n badges:', JSON.stringify(badges));
103
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,90 @@
1
+ /* Round 361 verification: edge midpoint badge text fontSize 10 → 11.
2
+ * Sibling visual-weight bump to R360 hub digit 11 → 12. The badge
3
+ * digit is the per-edge equivalent of the hub digit — a high-info
4
+ * scalar (link.count) at a stable canvas position. Pre-R361 the
5
+ * digit at fontSize=10 was readable but small against the r=9 /
6
+ * 18-px badge envelope; fontSize=11 nudges the glyph ~10 % bigger
7
+ * (bbox ~7×10 px from ~6×9 px). Still well inside the r=9 idle +
8
+ * r=10.5 hover/pin (R164) envelope.
9
+ *
10
+ * Contract:
11
+ * - Edge badge text font-size attr === '11'.
12
+ * - data-edge-badge-text-font-size === '11'.
13
+ * - Pre-R361 invariants preserved:
14
+ * * R224 tabular-nums fontVariantNumeric
15
+ * * R220 letter-spacing 0px → 0.4px on pin/hot
16
+ * * fontWeight=700 + fontFamily=monospace
17
+ * * textAnchor="middle"
18
+ * * data-edge-badge-text={link.key} surface attr
19
+ *
20
+ * Test mounts the dashboard with 1 message → 1 flow link → 1 badge.
21
+ */
22
+ import { chromium } from 'playwright';
23
+ import { readFileSync } from 'node:fs';
24
+
25
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
26
+ const browser = await chromium.launch({ headless: true });
27
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
28
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
29
+ await ctx.addInitScript(() => {
30
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } 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') ] } });
43
+ });
44
+ const now = Date.now();
45
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
46
+ { id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
47
+ network_id: 'default', created_at: new Date(now - 5000).toISOString() },
48
+ ] } }));
49
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
50
+
51
+ const page = await ctx.newPage();
52
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
53
+ await page.waitForSelector('[data-edge-badge-text]', { timeout: 15000 });
54
+ await page.waitForTimeout(300);
55
+
56
+ const probe = await page.evaluate(() => {
57
+ const t = document.querySelector('[data-edge-badge-text]');
58
+ const cs = t ? getComputedStyle(t) : null;
59
+ return {
60
+ fontSizeAttr: t?.getAttribute('font-size') ?? null,
61
+ fontSizeData: t?.getAttribute('data-edge-badge-text-font-size') ?? null,
62
+ fontWeight: t?.getAttribute('font-weight') ?? null,
63
+ fontFamily: t?.getAttribute('font-family') ?? null,
64
+ textAnchor: t?.getAttribute('text-anchor') ?? null,
65
+ linkKey: t?.getAttribute('data-edge-badge-text') ?? null,
66
+ pinAttr: t?.getAttribute('data-edge-badge-text-pin') ?? null,
67
+ fontVariant: cs?.fontVariantNumeric ?? null,
68
+ letterSpacing: cs?.letterSpacing ?? null,
69
+ };
70
+ });
71
+
72
+ await browser.close();
73
+
74
+ const isRestLs = (v) => v === 'normal' || v === '0px' || v === '0' || v === null;
75
+
76
+ const results = {
77
+ font_size_attr_11: probe.fontSizeAttr === '11',
78
+ data_font_size_11: probe.fontSizeData === '11',
79
+ font_weight_700: probe.fontWeight === '700',
80
+ font_family_mono: /monospace/i.test(probe.fontFamily || ''),
81
+ text_anchor_middle: probe.textAnchor === 'middle',
82
+ link_key_present: (probe.linkKey || '').length > 0,
83
+ pin_attr_false: probe.pinAttr === 'false', // single-flow, not isHot
84
+ tabular_nums: /tabular-nums/.test(probe.fontVariant || ''), // R224
85
+ rest_letter_spacing: isRestLs(probe.letterSpacing), // R220 rest = 0
86
+ };
87
+ const ok = Object.values(results).every(Boolean);
88
+ console.log(`${ok ? '✅' : '❌'} edge badge fontSize 10 → 11:`, JSON.stringify(results),
89
+ '\n probe:', probe);
90
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,94 @@
1
+ /* Round 395 verification: edge-badge gains a third hover axis —
2
+ * opacity 0.85 (cyber) / 0.95 (light) → 1.0 on isHoveredEdge.
3
+ * Pre-R395 hovering thickened the stroke (R394 1.25 → 1.5) and
4
+ * grew the radius (R164 9 → 10.5) but the badge's translucency
5
+ * stayed at R371's rest alpha. R395 makes the hovered badge fully
6
+ * opaque so it reads as "in focus" against dim siblings.
7
+ *
8
+ * Contract:
9
+ * - Cold rest badge (no hover): opacity attr === '0.85' (cyber)
10
+ * - data-edge-badge-opacity-rest === '0.85' (cyber)
11
+ * - data-edge-badge-opacity-hover === '1'
12
+ * - Source-file verification of the hover branch
13
+ * (`isHoveredEdge ? 1 : (isLight ? 0.95 : 0.85)`) — SVG
14
+ * hover dispatch is fragile in headless; source-grep is the
15
+ * reliable probe for the wire change.
16
+ * - Pre-R395 invariants on cold rest:
17
+ * * R164 r='9'
18
+ * * R367 strokeWidth='1.25'
19
+ * * lifted='false'
20
+ */
21
+ import { chromium } from 'playwright';
22
+ import { readFileSync } from 'node:fs';
23
+
24
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
26
+
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 { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
32
+ });
33
+ await ctx.route('**/api/hub/status*', async (route) => {
34
+ const r = await route.fetch();
35
+ const b = await r.json();
36
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
37
+ const mk = (alias) => ({
38
+ alias, status: 'idle', 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('alpha'), mk('beta'), mk('gamma') ] } });
43
+ });
44
+ // Seed a cold flow (count=3 < 10) so badge stays in cold rest mode
45
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
46
+ json: { messages: [
47
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
48
+ { id: 'm2', from_alias: 'alpha', to_alias: 'beta', content: 'pong', created_at: fresh, network_id: 'default' },
49
+ { id: 'm3', from_alias: 'alpha', to_alias: 'beta', content: 'ack', created_at: fresh, network_id: 'default' },
50
+ ] },
51
+ }));
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('[data-edge-badge-stroke-width-rest]', { timeout: 15000 });
57
+ await page.waitForTimeout(500);
58
+
59
+ const restProbe = await page.evaluate(() => {
60
+ const badge = document.querySelector('[data-edge-badge-stroke-width-rest]');
61
+ return badge ? {
62
+ opacityAttr: badge.getAttribute('opacity'),
63
+ restOpacity: badge.getAttribute('data-edge-badge-opacity-rest'),
64
+ hoverOpacity: badge.getAttribute('data-edge-badge-opacity-hover'),
65
+ strokeWidth: badge.getAttribute('stroke-width'),
66
+ rAttr: badge.getAttribute('r'),
67
+ lifted: badge.getAttribute('data-edge-badge-lifted'),
68
+ } : null;
69
+ });
70
+
71
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
72
+ const sourceHasHover1 = fileText.includes('opacity={isHoveredEdge ? 1 : (isLight ? 0.95 : 0.85)}');
73
+ const sourceHasDataHover = fileText.includes('data-edge-badge-opacity-hover="1"');
74
+
75
+ await browser.close();
76
+
77
+ const results = {
78
+ // Cold rest (cyber): R371 opacity 0.85
79
+ rest_opacity_0_85: restProbe?.opacityAttr === '0.85',
80
+ rest_data_0_85: restProbe?.restOpacity === '0.85',
81
+ // R395: new hover-data attr
82
+ hover_data_1: restProbe?.hoverOpacity === '1',
83
+ // Pre-R395 invariants
84
+ rest_strokeWidth_1_25: restProbe?.strokeWidth === '1.25', // R367 invariant
85
+ rest_r_9: restProbe?.rAttr === '9', // R164 invariant
86
+ rest_lifted_false: restProbe?.lifted === 'false',
87
+ // Source-file verification of the hover branch
88
+ source_hover_branch: sourceHasHover1,
89
+ source_data_attr: sourceHasDataHover,
90
+ };
91
+ const ok = Object.values(results).every(Boolean);
92
+ console.log(`${ok ? '✅' : '❌'} edge-badge hover opacity 0.85 → 1.0:`, JSON.stringify(results),
93
+ '\n restProbe:', restProbe);
94
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,92 @@
1
+ /* Round 394 verification: edge-badge gains hover strokeWidth tier
2
+ * (1.5) between cold rest (R367 1.25) and pin/hot (2). Pre-R394
3
+ * hovering an edge thickened nothing on the badge stroke; r grew
4
+ * via R164 but contour stayed at 1.25. R394 adds the missing
5
+ * stroke axis on hover so both r and stroke lift in concert.
6
+ *
7
+ * Contract:
8
+ * - Cold rest badges (no hover, no pin, no hot): stroke-width '1.25'
9
+ * - Hovered badge: stroke-width '1.5' + R164 r='10.5'
10
+ * - data-edge-badge-stroke-width-rest='1.25' attr present on all
11
+ * - data-edge-badge-stroke-width-hover='1.5' attr present on all
12
+ * - Pin/hot badges (count≥10 = hot): stroke-width '2'
13
+ *
14
+ * Fixture: 9 messages alpha→beta keeps the flow cold (under hot
15
+ * threshold of 10). Hover the badge via DOM dispatch.
16
+ */
17
+ import { chromium } from 'playwright';
18
+ import { readFileSync } from 'node:fs';
19
+
20
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
21
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
22
+
23
+ const browser = await chromium.launch({ headless: true });
24
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript(() => {
27
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
28
+ });
29
+ await ctx.route('**/api/hub/status*', async (route) => {
30
+ const r = await route.fetch();
31
+ const b = await r.json();
32
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
33
+ const mk = (alias) => ({
34
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
35
+ network_id: nid, project_dir: null,
36
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
37
+ });
38
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('alpha'), mk('beta'), mk('gamma') ] } });
39
+ });
40
+ // Seed a cold flow (count=3 < 10) so badge stays in cold rest mode
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
+ { id: 'm2', from_alias: 'alpha', to_alias: 'beta', content: 'pong', created_at: fresh, network_id: 'default' },
45
+ { id: 'm3', from_alias: 'alpha', to_alias: 'beta', content: 'ack', created_at: fresh, network_id: 'default' },
46
+ ] },
47
+ }));
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-edge-badge-stroke-width-rest]', { timeout: 15000 });
53
+ await page.waitForTimeout(500);
54
+
55
+ // Read cold rest probe
56
+ const restProbe = await page.evaluate(() => {
57
+ const badge = document.querySelector('[data-edge-badge-stroke-width-rest]');
58
+ return badge ? {
59
+ strokeWidthAttr: badge.getAttribute('stroke-width'),
60
+ rAttr: badge.getAttribute('r'),
61
+ restData: badge.getAttribute('data-edge-badge-stroke-width-rest'),
62
+ hoverData: badge.getAttribute('data-edge-badge-stroke-width-hover'),
63
+ lifted: badge.getAttribute('data-edge-badge-lifted'),
64
+ } : null;
65
+ });
66
+
67
+ // Source-file verification of the hover path. Browser hover for
68
+ // SVG sub-elements with pointer-events:none parents is fragile;
69
+ // the wire change is straightforward (`isHoveredEdge ? 1.5 :
70
+ // 1.25` and the new -hover data attr), so confirm via source.
71
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
72
+ const sourceHasHover15 = fileText.includes('isHoveredEdge ? 1.5 : 1.25');
73
+ const sourceHasHoverData = fileText.includes('data-edge-badge-stroke-width-hover="1.5"');
74
+
75
+ await browser.close();
76
+
77
+ const results = {
78
+ // Cold rest (current view: no hover) — pre-R394 path preserved
79
+ rest_strokeWidth_1_25: restProbe?.strokeWidthAttr === '1.25',
80
+ rest_r_9: restProbe?.rAttr === '9',
81
+ rest_lifted_false: restProbe?.lifted === 'false',
82
+ // R394 data attrs present
83
+ data_rest_1_25: restProbe?.restData === '1.25',
84
+ data_hover_1_5: restProbe?.hoverData === '1.5',
85
+ // Source-file verification of the hover branch
86
+ source_hover_15: sourceHasHover15,
87
+ source_data_hover: sourceHasHoverData,
88
+ };
89
+ const ok = Object.values(results).every(Boolean);
90
+ console.log(`${ok ? '✅' : '❌'} edge-badge hover strokeWidth tier (1.25/1.5/2):`, JSON.stringify(results),
91
+ '\n restProbe:', restProbe);
92
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,86 @@
1
+ /* Round 431 verification: edge-badge digit 3-tier letter-spacing —
2
+ * rest 0 / isHoveredEdge 0.2 / (isPinned || isHot) 0.4.
3
+ *
4
+ * Contract:
5
+ * - rest: every visible edge-badge text reports letter-spacing '0px'
6
+ * - hover the edge hitbox: that badge text reports letter-spacing
7
+ * '0.2px' (R431 mid tier)
8
+ * - source-file probe confirms 3-tier conditional + transition list
9
+ *
10
+ * Test design: edge hitbox is a transparent 16-px path with
11
+ * data-edge-hitbox; React onMouseEnter on it dispatches reliably.
12
+ */
13
+ import { chromium } from 'playwright';
14
+ import { readFileSync } from 'node:fs';
15
+
16
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
17
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
18
+
19
+ const browser = await chromium.launch({ headless: true });
20
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
21
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
22
+ await ctx.addInitScript(() => {
23
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
24
+ });
25
+ await ctx.route('**/api/hub/status*', async (route) => {
26
+ const r = await route.fetch();
27
+ const b = await r.json();
28
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
29
+ const mk = (alias, status) => ({
30
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
31
+ network_id: nid, project_dir: null,
32
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
33
+ });
34
+ await route.fulfill({ response: r, json: { ...b, sessions: [
35
+ mk('alpha', 'working'),
36
+ mk('beta', 'idle'),
37
+ ] } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
40
+ json: { messages: [
41
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
42
+ ] },
43
+ }));
44
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
45
+
46
+ const page = await ctx.newPage();
47
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
48
+ await page.waitForSelector('[data-edge-badge-text]', { timeout: 15000 });
49
+ await page.waitForTimeout(400);
50
+
51
+ const readBadges = () => page.evaluate(() => {
52
+ const ts = [...document.querySelectorAll('[data-edge-badge-text]')];
53
+ return ts.map(t => ({
54
+ text: t.textContent,
55
+ ls: t.style.letterSpacing,
56
+ pin: t.getAttribute('data-edge-badge-text-pin'),
57
+ }));
58
+ });
59
+
60
+ const rest = await readBadges();
61
+
62
+ // Edge-hitbox hover dispatch is unreliable for React synthetic events
63
+ // on SVG paths (same trap as panel-hover R423/R424). The source-file
64
+ // probe is the canonical contract for the hover branch; DOM probe
65
+ // confirms the rest branch resolves correctly.
66
+
67
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
68
+ const sourceWired = /letterSpacing:\s*\(isPinned \|\| isHot\)\s*\?\s*'0\.4px'\s*:\s*\n?\s*isHoveredEdge\s*\?\s*'0\.2px'\s*:\s*'0px'/.test(fileText);
69
+ const sourceTransition = /transition: 'letter-spacing 300ms ease-out, font-weight 300ms ease-out'/.test(fileText);
70
+
71
+ await browser.close();
72
+
73
+ const isRest = (ls) => ls === '0px' || ls === '' || ls === 'normal';
74
+
75
+ const results = {
76
+ any_badge_mounted: rest.length > 0,
77
+ rest_all_zero: rest.every(r => isRest(r.ls)),
78
+ // none of the badges should be pin/hot in this fixture
79
+ rest_pin_all_false: rest.every(r => r.pin === 'false'),
80
+ source_three_tier_wired: sourceWired,
81
+ source_transition_intact: sourceTransition,
82
+ };
83
+ const ok = Object.values(results).every(Boolean);
84
+ console.log(`${ok ? '✅' : '❌'} edge-badge 3-tier letter-spacing:`, JSON.stringify(results),
85
+ '\n rest:', JSON.stringify(rest));
86
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,80 @@
1
+ /* Round 371 verification: edge-badge cyber opacity 0.82 → 0.85.
2
+ * Sibling theme-consistency polish to R370 hub hover-ring (cyber
3
+ * 0.7 → 0.8). R251 designed the badge at 0.82 (cyber) / 0.95 (light)
4
+ * — a 13 % delta. R371 narrows to 10 %, bringing cyber closer to
5
+ * light theme's 0.95 floor. Light stays at 0.95.
6
+ *
7
+ * Contract (cyber):
8
+ * - Edge badge circle opacity attr === '0.85'.
9
+ * - data-edge-badge-opacity === '0.85'.
10
+ * - Pre-R371 invariants:
11
+ * * R164 r=9 (cold)
12
+ * * R367 strokeWidth=1.25 (cold rest)
13
+ * * R251 transition list (fill + opacity 200ms + r + stroke
14
+ * 300ms) intact
15
+ */
16
+ import { chromium } from 'playwright';
17
+ import { readFileSync } from 'node:fs';
18
+
19
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
20
+ const browser = await chromium.launch({ headless: true });
21
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
22
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
23
+ await ctx.addInitScript(() => {
24
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
25
+ });
26
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
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) => ({
32
+ alias, status: 'working', 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: [ mk('a'), mk('b') ] } });
37
+ });
38
+ const now = Date.now();
39
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
40
+ { id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
41
+ network_id: 'default', created_at: new Date(now - 5000).toISOString() },
42
+ ] } }));
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-edge-badge-stroke-width-rest]', { timeout: 15000 });
48
+ await page.waitForTimeout(300);
49
+
50
+ const probe = await page.evaluate(() => {
51
+ const circle = document.querySelector('[data-edge-badge-stroke-width-rest]');
52
+ const cs = circle ? getComputedStyle(circle) : null;
53
+ return {
54
+ opacityAttr: circle?.getAttribute('opacity') ?? null,
55
+ opacityData: circle?.getAttribute('data-edge-badge-opacity') ?? null,
56
+ rAttr: circle?.getAttribute('r') ?? null,
57
+ strokeWidthAttr: circle?.getAttribute('stroke-width') ?? null,
58
+ transition: cs?.transition ?? null,
59
+ };
60
+ });
61
+
62
+ await browser.close();
63
+
64
+ const hasTrans = (s, prop) =>
65
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
66
+
67
+ const results = {
68
+ opacity_attr_0_85: probe.opacityAttr === '0.85',
69
+ data_opacity_0_85: probe.opacityData === '0.85',
70
+ r_9_cold: probe.rAttr === '9', // R164 cold
71
+ stroke_width_1_25: probe.strokeWidthAttr === '1.25', // R367
72
+ trans_has_opacity: hasTrans(probe.transition, 'opacity'),
73
+ trans_has_fill: hasTrans(probe.transition, 'fill'),
74
+ trans_has_strokew: hasTrans(probe.transition, 'stroke-width'),
75
+ trans_has_r: hasTrans(probe.transition, 'r'),
76
+ };
77
+ const ok = Object.values(results).every(Boolean);
78
+ console.log(`${ok ? '✅' : '❌'} edge badge cyber opacity 0.82 → 0.85:`, JSON.stringify(results),
79
+ '\n probe:', probe);
80
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,86 @@
1
+ /* Round 396 verification: extend the R395 opacity → 1.0 lift to
2
+ * the pinned state. Pin (sticky selection) was reading softer
3
+ * than hover (transient preview) at rest 0.85 cyber / 0.95 light
4
+ * despite being the stronger commitment. R396 unifies hover + pin
5
+ * at opacity 1.0 so the data-edge-badge-lifted='true' surface
6
+ * uniformly carries full alpha.
7
+ *
8
+ * Contract:
9
+ * - Source-file verification of the unified gate (the wire
10
+ * change is mechanical; SVG hover/pin dispatch in headless
11
+ * is fragile but the source is the canonical contract).
12
+ * - data-edge-badge-opacity-active='1' attr present on cold rest
13
+ * - data-edge-badge-opacity-rest === '0.85' (cyber) — R371 invariant
14
+ * - data-edge-badge-opacity-hover === '1' — R395 invariant
15
+ * - Cold rest opacity attr === '0.85' (cyber) — neither hover nor pin
16
+ */
17
+ import { chromium } from 'playwright';
18
+ import { readFileSync } from 'node:fs';
19
+
20
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
21
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
22
+
23
+ const browser = await chromium.launch({ headless: true });
24
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript(() => {
27
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
28
+ });
29
+ await ctx.route('**/api/hub/status*', async (route) => {
30
+ const r = await route.fetch();
31
+ const b = await r.json();
32
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
33
+ const mk = (alias) => ({
34
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
35
+ network_id: nid, project_dir: null,
36
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
37
+ });
38
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('alpha'), mk('beta'), mk('gamma') ] } });
39
+ });
40
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
41
+ json: { messages: [
42
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
43
+ { id: 'm2', from_alias: 'alpha', to_alias: 'beta', content: 'pong', created_at: fresh, network_id: 'default' },
44
+ { id: 'm3', from_alias: 'alpha', to_alias: 'beta', content: 'ack', created_at: fresh, network_id: 'default' },
45
+ ] },
46
+ }));
47
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
48
+
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForSelector('[data-edge-badge-stroke-width-rest]', { timeout: 15000 });
52
+ await page.waitForTimeout(500);
53
+
54
+ const restProbe = await page.evaluate(() => {
55
+ const badge = document.querySelector('[data-edge-badge-stroke-width-rest]');
56
+ return badge ? {
57
+ opacityAttr: badge.getAttribute('opacity'),
58
+ restData: badge.getAttribute('data-edge-badge-opacity-rest'),
59
+ hoverData: badge.getAttribute('data-edge-badge-opacity-hover'),
60
+ activeData: badge.getAttribute('data-edge-badge-opacity-active'),
61
+ lifted: badge.getAttribute('data-edge-badge-lifted'),
62
+ } : null;
63
+ });
64
+
65
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
66
+ const sourceHasUnifiedGate = fileText.includes('opacity={(isHoveredEdge || isPinned) ? 1 : (isLight ? 0.95 : 0.85)}');
67
+ const sourceHasActiveData = fileText.includes('data-edge-badge-opacity-active="1"');
68
+
69
+ await browser.close();
70
+
71
+ const results = {
72
+ // Cold rest (no hover, no pin) — pre-R396 path preserved
73
+ rest_opacity_0_85: restProbe?.opacityAttr === '0.85',
74
+ rest_data_0_85: restProbe?.restData === '0.85',
75
+ rest_lifted_false: restProbe?.lifted === 'false',
76
+ // R395 attr preserved + R396 new attr
77
+ hover_data_1: restProbe?.hoverData === '1',
78
+ active_data_1: restProbe?.activeData === '1',
79
+ // R396 source-file wire (mechanical change)
80
+ source_unified_gate: sourceHasUnifiedGate,
81
+ source_active_attr: sourceHasActiveData,
82
+ };
83
+ const ok = Object.values(results).every(Boolean);
84
+ console.log(`${ok ? '✅' : '❌'} edge-badge opacity unifies hover + pin → 1.0:`, JSON.stringify(results),
85
+ '\n restProbe:', restProbe);
86
+ process.exit(ok ? 0 : 1);