@sleep2agi/agent-network-dashboard 0.5.1-preview.1 → 0.5.1-preview.100

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 (253) 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 +6 -6
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/server/app/_global-error.html +1 -1
  6. package/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/_not-found.html +2 -2
  14. package/.next/server/app/_not-found.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  18. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  22. package/.next/server/app/admin.html +2 -2
  23. package/.next/server/app/admin.rsc +2 -2
  24. package/.next/server/app/admin.segments/_full.segment.rsc +2 -2
  25. package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  26. package/.next/server/app/admin.segments/_index.segment.rsc +2 -2
  27. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  28. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
  29. package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
  30. package/.next/server/app/index.html +2 -2
  31. package/.next/server/app/index.rsc +3 -3
  32. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  33. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  34. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  35. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  36. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  37. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  38. package/.next/server/app/login.html +2 -2
  39. package/.next/server/app/login.rsc +3 -3
  40. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  41. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  42. package/.next/server/app/login.segments/_index.segment.rsc +2 -2
  43. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  44. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  45. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  46. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  47. package/.next/server/app/logs.html +2 -2
  48. package/.next/server/app/logs.rsc +2 -2
  49. package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
  50. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  51. package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
  52. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  53. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
  54. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  55. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  56. package/.next/server/app/messages.html +2 -2
  57. package/.next/server/app/messages.rsc +2 -2
  58. package/.next/server/app/messages.segments/_full.segment.rsc +2 -2
  59. package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
  60. package/.next/server/app/messages.segments/_index.segment.rsc +2 -2
  61. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  62. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
  63. package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
  64. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  65. package/.next/server/app/node.html +2 -2
  66. package/.next/server/app/node.rsc +2 -2
  67. package/.next/server/app/node.segments/_full.segment.rsc +2 -2
  68. package/.next/server/app/node.segments/_head.segment.rsc +1 -1
  69. package/.next/server/app/node.segments/_index.segment.rsc +2 -2
  70. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  71. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
  72. package/.next/server/app/node.segments/node.segment.rsc +1 -1
  73. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  74. package/.next/server/app/nodes.html +2 -2
  75. package/.next/server/app/nodes.rsc +2 -2
  76. package/.next/server/app/nodes.segments/_full.segment.rsc +2 -2
  77. package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
  78. package/.next/server/app/nodes.segments/_index.segment.rsc +2 -2
  79. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  80. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
  81. package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
  82. package/.next/server/app/page_client-reference-manifest.js +1 -1
  83. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/server-logs.html +2 -2
  85. package/.next/server/app/server-logs.rsc +2 -2
  86. package/.next/server/app/server-logs.segments/_full.segment.rsc +2 -2
  87. package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
  88. package/.next/server/app/server-logs.segments/_index.segment.rsc +2 -2
  89. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  90. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
  91. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
  92. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app/settings/networks.html +2 -2
  94. package/.next/server/app/settings/networks.rsc +2 -2
  95. package/.next/server/app/settings/networks.segments/_full.segment.rsc +2 -2
  96. package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
  97. package/.next/server/app/settings/networks.segments/_index.segment.rsc +2 -2
  98. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  99. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
  100. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
  101. package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
  102. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  103. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  104. package/.next/server/app/settings/tokens.html +2 -2
  105. package/.next/server/app/settings/tokens.rsc +2 -2
  106. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +2 -2
  107. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
  108. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +2 -2
  109. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  110. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
  111. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
  112. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
  113. package/.next/server/app/settings.html +2 -2
  114. package/.next/server/app/settings.rsc +3 -3
  115. package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  116. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  117. package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  118. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  119. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  120. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  121. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  122. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  123. package/.next/server/app/tasks.html +2 -2
  124. package/.next/server/app/tasks.rsc +2 -2
  125. package/.next/server/app/tasks.segments/_full.segment.rsc +2 -2
  126. package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
  127. package/.next/server/app/tasks.segments/_index.segment.rsc +2 -2
  128. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
  130. package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
  131. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  132. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  133. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  134. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  135. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  136. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  137. package/.next/server/middleware-build-manifest.js +3 -3
  138. package/.next/server/pages/404.html +2 -2
  139. package/.next/server/pages/500.html +1 -1
  140. package/.next/static/chunks/0.pk4~tmpnb0_.js +1 -0
  141. package/.next/static/chunks/004_pmytjt5g..js +4 -0
  142. package/.next/static/chunks/017hq2-5l~_98.css +2 -0
  143. package/.next/static/chunks/0poutjg8.1a9n.js +1 -0
  144. package/.next/trace +2 -2
  145. package/.next/trace-build +1 -1
  146. package/app/components/TopoGraph.tsx +2126 -145
  147. package/package.json +1 -1
  148. package/scripts/topo-active-links-chip-hover-lift-test.mjs +93 -0
  149. package/scripts/topo-active-links-freshness-color-test.mjs +86 -0
  150. package/scripts/topo-chip-digit-fontweight-test.mjs +105 -0
  151. package/scripts/topo-chip-digit-hover-bold-test.mjs +94 -0
  152. package/scripts/topo-chip-row-group-hover-brighten-test.mjs +107 -0
  153. package/scripts/topo-chip-row-hover-lift-test.mjs +95 -0
  154. package/scripts/topo-chrome-button-hover-lift-test.mjs +94 -0
  155. package/scripts/topo-chrome-segmented-radius-test.mjs +100 -0
  156. package/scripts/topo-click-ripple-opacity-test.mjs +99 -0
  157. package/scripts/topo-edge-badge-digit-fw-test.mjs +103 -0
  158. package/scripts/topo-edge-badge-fontsize-test.mjs +90 -0
  159. package/scripts/topo-edge-badge-hover-opacity-test.mjs +94 -0
  160. package/scripts/topo-edge-badge-hover-stroke-test.mjs +92 -0
  161. package/scripts/topo-edge-badge-letter-spacing-test.mjs +86 -0
  162. package/scripts/topo-edge-badge-opacity-test.mjs +80 -0
  163. package/scripts/topo-edge-badge-pin-opacity-test.mjs +86 -0
  164. package/scripts/topo-edge-badge-stroke-test.mjs +92 -0
  165. package/scripts/topo-edge-endpoint-hover-sw-test.mjs +127 -0
  166. package/scripts/topo-edge-freshness-floor-test.mjs +99 -0
  167. package/scripts/topo-edge-particle-hover-r-test.mjs +113 -0
  168. package/scripts/topo-edge-particle-radius-test.mjs +76 -0
  169. package/scripts/topo-edge-visible-linecap-test.mjs +89 -0
  170. package/scripts/topo-filter-pill-hover-lift-test.mjs +101 -0
  171. package/scripts/topo-filter-pill-hover-opacity-test.mjs +110 -0
  172. package/scripts/topo-filter-pill-value-fw-test.mjs +88 -0
  173. package/scripts/topo-filter-pill-weight-test.mjs +75 -0
  174. package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
  175. package/scripts/topo-flow-rail-hover-sw-test.mjs +113 -0
  176. package/scripts/topo-flow-rail-linecap-test.mjs +79 -0
  177. package/scripts/topo-freshness-chip-hierarchy-test.mjs +93 -0
  178. package/scripts/topo-freshness-chip-tabular-test.mjs +41 -0
  179. package/scripts/topo-freshness-floor-lift-test.mjs +92 -0
  180. package/scripts/topo-freshness-suffix-tabular-test.mjs +88 -0
  181. package/scripts/topo-fullscreen-icon-hover-scale-test.mjs +91 -0
  182. package/scripts/topo-group-box-stroke-test.mjs +105 -0
  183. package/scripts/topo-group-label-count-fontweight-test.mjs +108 -0
  184. package/scripts/topo-group-label-letter-spacing-test.mjs +113 -0
  185. package/scripts/topo-hover-detail-body-fw-test.mjs +101 -0
  186. package/scripts/topo-hover-detail-model-fw-test.mjs +98 -0
  187. package/scripts/topo-hover-detail-opacity-test.mjs +98 -0
  188. package/scripts/topo-hover-detail-rx-test.mjs +81 -0
  189. package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
  190. package/scripts/topo-hub-digit-fw-hover-test.mjs +102 -0
  191. package/scripts/topo-hub-halo-light-trough-test.mjs +88 -0
  192. package/scripts/topo-hub-halo-radius-test.mjs +86 -0
  193. package/scripts/topo-hub-halo-trough-test.mjs +83 -0
  194. package/scripts/topo-hub-highlight-opacity-test.mjs +88 -0
  195. package/scripts/topo-hub-highlight-radius-test.mjs +90 -0
  196. package/scripts/topo-hub-hover-ring-opacity-test.mjs +96 -0
  197. package/scripts/topo-hub-hover-ring-stroke-test.mjs +86 -0
  198. package/scripts/topo-hub-spoke-hover-opacity-test.mjs +119 -0
  199. package/scripts/topo-hub-spoke-hover-sw-test.mjs +121 -0
  200. package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
  201. package/scripts/topo-label-card-opacity-hover-test.mjs +99 -0
  202. package/scripts/topo-layout-toggle-hover-tracking-test.mjs +109 -0
  203. package/scripts/topo-layout-toggle-radius-test.mjs +87 -0
  204. package/scripts/topo-legend-label-fontweight-test.mjs +94 -0
  205. package/scripts/topo-legend-pin-ring-stroke-test.mjs +101 -0
  206. package/scripts/topo-legend-row-letter-spacing-test.mjs +101 -0
  207. package/scripts/topo-minimap-offline-opacity-test.mjs +90 -0
  208. package/scripts/topo-minimap-online-hover-opacity-test.mjs +92 -0
  209. package/scripts/topo-minimap-online-opacity-test.mjs +93 -0
  210. package/scripts/topo-minimap-online-radius-test.mjs +85 -0
  211. package/scripts/topo-minimap-viewport-hover-test.mjs +109 -0
  212. package/scripts/topo-minimap-viewport-linejoin-test.mjs +75 -0
  213. package/scripts/topo-minimap-viewport-rx-test.mjs +85 -0
  214. package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
  215. package/scripts/topo-more-flows-hover-letterspacing-test.mjs +98 -0
  216. package/scripts/topo-node-alias-letter-spacing-test.mjs +112 -0
  217. package/scripts/topo-node-halo-offline-opacity-test.mjs +87 -0
  218. package/scripts/topo-node-label-card-rx-test.mjs +85 -0
  219. package/scripts/topo-node-pulse-peak-test.mjs +89 -0
  220. package/scripts/topo-node-pulse-trough-test.mjs +91 -0
  221. package/scripts/topo-node-sub-text-letter-spacing-test.mjs +115 -0
  222. package/scripts/topo-panel-count-fw-hover-test.mjs +105 -0
  223. package/scripts/topo-panel-count-letterspacing-test.mjs +89 -0
  224. package/scripts/topo-panel-rect-opacity-hover-test.mjs +109 -0
  225. package/scripts/topo-panel-stroke-hover-test.mjs +110 -0
  226. package/scripts/topo-panel-title-hover-letterspacing-test.mjs +97 -0
  227. package/scripts/topo-pin-intersection-unit-test.mjs +100 -0
  228. package/scripts/topo-pressure-bar-height-test.mjs +92 -0
  229. package/scripts/topo-pressure-kicker-fontweight-test.mjs +76 -0
  230. package/scripts/topo-recent-pip-radius-2-test.mjs +72 -0
  231. package/scripts/topo-recent-pip-radius-test.mjs +76 -0
  232. package/scripts/topo-recent-row-content-opacity-test.mjs +81 -0
  233. package/scripts/topo-recent-row-letter-spacing-test.mjs +109 -0
  234. package/scripts/topo-recent-row-text-fontweight-test.mjs +90 -0
  235. package/scripts/topo-reset-hover-rotate-test.mjs +102 -0
  236. package/scripts/topo-spoke-active-opacity-test.mjs +104 -0
  237. package/scripts/topo-spoke-active-stroke-test.mjs +95 -0
  238. package/scripts/topo-spoke-idle-opacity-test.mjs +91 -0
  239. package/scripts/topo-status-ring-hover-sw-test.mjs +105 -0
  240. package/scripts/topo-vendor-chip-hover-lift-test.mjs +87 -0
  241. package/scripts/topo-vendor-glyph-fontweight-test.mjs +102 -0
  242. package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
  243. package/scripts/topo-vendor-suffix-hover-brighten-test.mjs +87 -0
  244. package/scripts/topo-zoom-icon-hover-scale-test.mjs +114 -0
  245. package/scripts/topo-zoom-level-hover-fw-test.mjs +95 -0
  246. package/scripts/topo-zoom-level-hover-letterspacing-test.mjs +91 -0
  247. package/.next/static/chunks/0aauz~36q5n2a.css +0 -2
  248. package/.next/static/chunks/0wratil0li9xw.js +0 -4
  249. package/.next/static/chunks/10paf4uz-_0g1.js +0 -1
  250. package/.next/static/chunks/12wcm9omjb_x6.js +0 -1
  251. /package/.next/static/{4t8OH-lNFieRsHEC2eZfD → gcwnb1fGF5WXL0umdpAPw}/_buildManifest.js +0 -0
  252. /package/.next/static/{4t8OH-lNFieRsHEC2eZfD → gcwnb1fGF5WXL0umdpAPw}/_clientMiddlewareManifest.js +0 -0
  253. /package/.next/static/{4t8OH-lNFieRsHEC2eZfD → gcwnb1fGF5WXL0umdpAPw}/_ssgManifest.js +0 -0
@@ -0,0 +1,94 @@
1
+ /* Round 400 verification (milestone): chrome reset + fullscreen
2
+ * buttons gain hover translateY(-1px) lift. Closes the hover-lift
3
+ * gesture across every standalone interactive HTML element in
4
+ * TopoGraph. Segmented controls (zoom/-+, nodeSize S/M/L,
5
+ * Layout Ring/Grid) intentionally stay planted to preserve the
6
+ * unified-strip appearance.
7
+ *
8
+ * Contract:
9
+ * - Reset button:
10
+ * * data-topo-chrome-reset-hover-lift === 'true'
11
+ * * className includes 'hover:-translate-y-px'
12
+ * * className includes 'transition-transform' + 'transform-gpu'
13
+ * - Fullscreen button:
14
+ * * data-topo-chrome-fullscreen-hover-lift === 'true'
15
+ * * className includes 'hover:-translate-y-px'
16
+ * * className includes 'transition-transform' + 'transform-gpu'
17
+ * - Pre-R400 invariants preserved:
18
+ * * R196 active:bg deeper press states
19
+ * * R288 strokeWidth=2.5 on chrome icons
20
+ * * R350 hover-rotate (reset) / R353 group-hover scale (fullscreen)
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
27
+
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
+ await ctx.route('**/api/hub/status*', async (route) => {
35
+ const r = await route.fetch();
36
+ const b = await r.json();
37
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
38
+ const mk = (alias) => ({
39
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
40
+ network_id: nid, project_dir: null,
41
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
42
+ });
43
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('alpha'), mk('beta') ] } });
44
+ });
45
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
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-topo-chrome-reset]', { timeout: 15000 });
51
+ await page.waitForTimeout(400);
52
+
53
+ const probe = await page.evaluate(() => {
54
+ const reset = document.querySelector('[data-topo-chrome-reset]');
55
+ const fs = document.querySelector('[data-topo-chrome-fullscreen]');
56
+ return {
57
+ reset: reset ? {
58
+ className: reset.className,
59
+ lift: reset.getAttribute('data-topo-chrome-reset-hover-lift'),
60
+ } : null,
61
+ fs: fs ? {
62
+ className: fs.className,
63
+ lift: fs.getAttribute('data-topo-chrome-fullscreen-hover-lift'),
64
+ } : null,
65
+ };
66
+ });
67
+
68
+ await browser.close();
69
+
70
+ const rc = probe.reset?.className || '';
71
+ const fc = probe.fs?.className || '';
72
+ const results = {
73
+ // Reset button R400 wiring
74
+ reset_lift_true: probe.reset?.lift === 'true',
75
+ reset_has_hover_translate: rc.includes('hover:-translate-y-px'),
76
+ reset_has_transition_transform: rc.includes('transition-transform'),
77
+ reset_has_transform_gpu: rc.includes('transform-gpu'),
78
+ reset_has_transition_colors: rc.includes('transition-colors'), // pre-R400 preserved
79
+ reset_has_active_bg: rc.includes('active:bg-white/10'), // R196 preserved
80
+ reset_has_rounded_md: rc.includes('rounded-md'),
81
+ // Fullscreen button R400 wiring
82
+ fs_lift_true: probe.fs?.lift === 'true',
83
+ fs_has_hover_translate: fc.includes('hover:-translate-y-px'),
84
+ fs_has_transition_transform: fc.includes('transition-transform'),
85
+ fs_has_transform_gpu: fc.includes('transform-gpu'),
86
+ fs_has_transition_colors: fc.includes('transition-colors'),
87
+ fs_has_group: fc.includes('group'), // R353 invariant
88
+ fs_has_rounded_md: fc.includes('rounded-md'),
89
+ };
90
+ const ok = Object.values(results).every(Boolean);
91
+ console.log(`${ok ? '✅' : '❌'} chrome reset + fullscreen hover lift translateY(-1px):`, JSON.stringify(results),
92
+ '\n resetClass:', rc,
93
+ '\n fsClass: ', fc);
94
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,100 @@
1
+ /* Round 376 verification: nodeSize + zoom segmented control wrappers
2
+ * rounded-md → rounded-lg (6 → 8 px). Sibling polish to R375 Layout-
3
+ * toggle wrapper — three chrome-strip segmented controls now all
4
+ * share rounded-lg at the wrapper tier:
5
+ * R375 Layout-toggle wrapper rounded-lg 8 px
6
+ * R376 nodeSize wrapper rounded-lg 8 px (this round)
7
+ * R376 zoom wrapper rounded-lg 8 px (this round)
8
+ *
9
+ * Individual atomic chrome buttons (reset, fullscreen) keep rounded-
10
+ * md (6 px) as their own atomic-button tier — chrome strip now
11
+ * expresses a two-tier hierarchy: 'segmented control container'
12
+ * (rounded-lg) vs 'standalone button' (rounded-md).
13
+ *
14
+ * Contract:
15
+ * - [data-topo-chrome-nodesize-radius] computed border-radius '8px'.
16
+ * - data-topo-chrome-nodesize-radius === 'rounded-lg'.
17
+ * - [data-topo-chrome-zoom-wrapper-radius] computed border-radius '8px'.
18
+ * - data-topo-chrome-zoom-wrapper-radius === 'rounded-lg'.
19
+ * - Pre-R376 invariants: border 1px + flex display + overflow:hidden
20
+ * + R263 background-color/border-color transition preserved on
21
+ * each wrapper.
22
+ * - Atomic reset + fullscreen buttons still have border-radius 6px
23
+ * (rounded-md, untouched).
24
+ */
25
+ import { chromium } from 'playwright';
26
+ import { readFileSync } from 'node:fs';
27
+
28
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
29
+ const browser = await chromium.launch({ headless: true });
30
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
31
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
32
+ await ctx.addInitScript(() => {
33
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
34
+ });
35
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
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) => ({
41
+ alias, status: 'working', 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: [ mk('a'), mk('b'), mk('c') ] } });
46
+ });
47
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
48
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
49
+
50
+ const page = await ctx.newPage();
51
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
52
+ await page.waitForSelector('[data-topo-chrome-nodesize-radius]', { timeout: 15000 });
53
+ await page.waitForSelector('[data-topo-chrome-zoom-wrapper-radius]', { timeout: 5000 });
54
+ await page.waitForTimeout(300);
55
+
56
+ const probe = await page.evaluate(() => {
57
+ const nodesize = document.querySelector('[data-topo-chrome-nodesize-radius]');
58
+ const zoom = document.querySelector('[data-topo-chrome-zoom-wrapper-radius]');
59
+ const reset = document.querySelector('[data-topo-chrome-reset]');
60
+ const fs = document.querySelector('[data-topo-chrome-fullscreen]');
61
+ const ncs = nodesize ? getComputedStyle(nodesize) : null;
62
+ const zcs = zoom ? getComputedStyle(zoom) : null;
63
+ const rcs = reset ? getComputedStyle(reset) : null;
64
+ const fcs = fs ? getComputedStyle(fs) : null;
65
+ return {
66
+ nodesizeRadius: ncs?.borderRadius ?? null,
67
+ nodesizeData: nodesize?.getAttribute('data-topo-chrome-nodesize-radius') ?? null,
68
+ nodesizeBorderW: ncs?.borderWidth ?? null,
69
+ nodesizeTrans: ncs?.transition ?? null,
70
+ zoomRadius: zcs?.borderRadius ?? null,
71
+ zoomData: zoom?.getAttribute('data-topo-chrome-zoom-wrapper-radius') ?? null,
72
+ zoomBorderW: zcs?.borderWidth ?? null,
73
+ zoomTrans: zcs?.transition ?? null,
74
+ resetRadius: rcs?.borderRadius ?? null,
75
+ fullscreenRadius: fcs?.borderRadius ?? null,
76
+ };
77
+ });
78
+
79
+ await browser.close();
80
+
81
+ const hasTrans = (s, prop) =>
82
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
83
+
84
+ const results = {
85
+ nodesize_radius_8px: probe.nodesizeRadius === '8px', // rounded-lg
86
+ nodesize_data_lg: probe.nodesizeData === 'rounded-lg',
87
+ nodesize_border_1px: probe.nodesizeBorderW === '1px',
88
+ nodesize_trans_border: hasTrans(probe.nodesizeTrans, 'border-color'), // R263 / R268
89
+ zoom_radius_8px: probe.zoomRadius === '8px',
90
+ zoom_data_lg: probe.zoomData === 'rounded-lg',
91
+ zoom_border_1px: probe.zoomBorderW === '1px',
92
+ zoom_trans_border: hasTrans(probe.zoomTrans, 'border-color'),
93
+ // Atomic chrome buttons stay rounded-md (6 px) — invariant.
94
+ reset_still_md: probe.resetRadius === '6px',
95
+ fullscreen_still_md: probe.fullscreenRadius === '6px',
96
+ };
97
+ const ok = Object.values(results).every(Boolean);
98
+ console.log(`${ok ? '✅' : '❌'} chrome segmented wrappers rounded-md → rounded-lg:`, JSON.stringify(results),
99
+ '\n probe:', probe);
100
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,99 @@
1
+ /* Round 403 verification: click-ripple SMIL initial opacity 0.7 →
2
+ * 0.8. Theme-consistency / canvas-presence polish family (8th
3
+ * anchor) — click-feedback now starts at the same 0.8 alpha as
4
+ * R370 hub hover-ring and R391 hub-spoke active. Three canvas
5
+ * state-feedback indicators share a uniform 0.8 start alpha.
6
+ *
7
+ * Contract:
8
+ * - Click the hub to fire the ripple (R52 fitView click path)
9
+ * - data-click-ripple-start-opacity === '0.8' on the opacity
10
+ * <animate> element
11
+ * - The opacity animation `values` starts with '0.8'
12
+ * - Pre-R403 invariants preserved:
13
+ * * dur='0.5s'
14
+ * * R227 calcMode='spline'
15
+ * * R227 keySplines='0.25 0.1 0.25 1'
16
+ * * fill='freeze'
17
+ *
18
+ * Source-file verification fallback: the SMIL element appears
19
+ * only during the 500ms ripple window, so the test reads source
20
+ * if the runtime probe doesn't catch the element in flight.
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
27
+
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
+ await ctx.route('**/api/hub/status*', async (route) => {
35
+ const r = await route.fetch();
36
+ const b = await r.json();
37
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
38
+ const mk = (alias) => ({
39
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
40
+ network_id: nid, project_dir: null,
41
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
42
+ });
43
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('alpha'), mk('beta') ] } });
44
+ });
45
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
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-topo-hub]', { timeout: 15000 });
51
+ await page.waitForTimeout(400);
52
+
53
+ // Click hub to fire ripple
54
+ await page.click('[data-topo-hub]');
55
+ await page.waitForTimeout(100); // ripple lives 500ms, probe early
56
+
57
+ const probe = await page.evaluate(() => {
58
+ const ripple = document.querySelector('[data-click-ripple]');
59
+ if (!ripple) return null;
60
+ const opacityAnim = ripple.querySelector('[data-click-ripple-start-opacity]');
61
+ return {
62
+ rippleMounted: true,
63
+ strokeWidth: ripple.getAttribute('stroke-width'),
64
+ fillAttr: ripple.getAttribute('fill'),
65
+ startOpacity: opacityAnim?.getAttribute('data-click-ripple-start-opacity') ?? null,
66
+ valuesAttr: opacityAnim?.getAttribute('values') ?? null,
67
+ durAttr: opacityAnim?.getAttribute('dur') ?? null,
68
+ calcMode: opacityAnim?.getAttribute('calcMode') ?? null,
69
+ keySplines: opacityAnim?.getAttribute('keySplines') ?? null,
70
+ fillFreeze: opacityAnim?.getAttribute('fill') ?? null,
71
+ };
72
+ });
73
+
74
+ // Source-file fallback in case the SMIL element is not caught
75
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
76
+ const sourceHasStartOpacity = fileText.includes('data-click-ripple-start-opacity="0.8"');
77
+ const sourceHasValues08 = fileText.includes('values="0.8;0"');
78
+
79
+ await browser.close();
80
+
81
+ const results = {
82
+ // Source-file invariants (deterministic from disk)
83
+ source_start_opacity_attr: sourceHasStartOpacity,
84
+ source_values_0_8_to_0: sourceHasValues08,
85
+ // Runtime probe (best-effort during 500ms ripple window)
86
+ ripple_mounted: !!probe,
87
+ start_opacity_0_8: probe?.startOpacity === '0.8',
88
+ values_0_8_to_0: probe?.valuesAttr === '0.8;0',
89
+ dur_0_5s: probe?.durAttr === '0.5s',
90
+ calcMode_spline: probe?.calcMode === 'spline',
91
+ keySplines_easeout: probe?.keySplines === '0.25 0.1 0.25 1',
92
+ // Static ring invariants
93
+ strokeWidth_2: probe?.strokeWidth === '2',
94
+ fill_none: probe?.fillAttr === 'none',
95
+ };
96
+ const ok = Object.values(results).every(Boolean);
97
+ console.log(`${ok ? '✅' : '❌'} click-ripple SMIL opacity 0.7 → 0.8:`, JSON.stringify(results),
98
+ '\n probe:', JSON.stringify(probe, null, 2));
99
+ process.exit(ok ? 0 : 1);
@@ -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);