@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,80 @@
1
+ /* Round 382 verification: hub-spoke path strokeLinecap='round'.
2
+ * Sibling SVG stroke-softening polish to R378 flow-rail dashes +
3
+ * R380 group box dashes — three dashed-stroke surfaces now share
4
+ * 'round' linecap.
5
+ *
6
+ * Dashed-stroke linecap family snapshot post-R382:
7
+ * R378 flow-rail '2 12' -> soft 3-px pills
8
+ * R380 group box '6 6' -> soft 7.5-px pills
9
+ * R382 hub spoke '6 14' -> soft 7-px pills (this round)
10
+ *
11
+ * Contract:
12
+ * - Every [data-topo-hub-spoke-active] path has stroke-linecap='round'.
13
+ * - data-topo-hub-spoke-linecap === 'round'.
14
+ * - Idle spokes still have strokeDasharray='6 14' (R382 doesn't touch
15
+ * the dasharray); active spokes still have 'none'.
16
+ * - Pre-R382 invariants preserved on each spoke: strokeWidth (1 idle /
17
+ * 2 active), R241 stroke + stroke-width + opacity 250ms transition.
18
+ */
19
+ import { chromium } from 'playwright';
20
+ import { readFileSync } from 'node:fs';
21
+
22
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
23
+ const browser = await chromium.launch({ headless: true });
24
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript(() => {
27
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
28
+ });
29
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
30
+ await ctx.route('**/api/hub/status*', async (route) => {
31
+ const r = await route.fetch();
32
+ const b = await r.json();
33
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
34
+ const mk = (alias) => ({
35
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
36
+ network_id: nid, project_dir: null,
37
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
38
+ });
39
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b'), mk('c') ] } });
40
+ });
41
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
42
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
43
+
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
46
+ await page.waitForSelector('[data-topo-hub-spoke-active]', { timeout: 15000 });
47
+ await page.waitForTimeout(300);
48
+
49
+ const probe = await page.evaluate(() => {
50
+ const spokes = Array.from(document.querySelectorAll('[data-topo-hub-spoke-active]'));
51
+ return spokes.map(el => ({
52
+ activeAttr: el.getAttribute('data-topo-hub-spoke-active'),
53
+ linecapAttr: el.getAttribute('stroke-linecap'),
54
+ linecapData: el.getAttribute('data-topo-hub-spoke-linecap'),
55
+ strokeWidthAttr: el.getAttribute('stroke-width'),
56
+ dasharrayAttr: el.getAttribute('stroke-dasharray'),
57
+ }));
58
+ });
59
+
60
+ await browser.close();
61
+
62
+ const allOk = probe.length >= 1 && probe.every(s =>
63
+ s.linecapAttr === 'round'
64
+ && s.linecapData === 'round'
65
+ && (s.activeAttr === 'true'
66
+ ? (s.strokeWidthAttr === '2' && s.dasharrayAttr === 'none')
67
+ : (s.strokeWidthAttr === '1' && s.dasharrayAttr === '6 14'))
68
+ );
69
+
70
+ const results = {
71
+ spokes_rendered: probe.length >= 1,
72
+ all_linecap_round: probe.every(s => s.linecapAttr === 'round'),
73
+ all_data_linecap: probe.every(s => s.linecapData === 'round'),
74
+ per_state_invariants_intact: allOk,
75
+ };
76
+ const ok = Object.values(results).every(Boolean);
77
+ console.log(`${ok ? '✅' : '❌'} hub-spoke strokeLinecap='round':`, JSON.stringify(results),
78
+ '\n count:', probe.length,
79
+ '\n sample:', probe.slice(0, 3));
80
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,99 @@
1
+ /* Round 429 verification: node label-card body opacity 0.94 → 1.0 on
2
+ * hover (cyber theme). Sibling to R348 panel rect opacity lift at
3
+ * panel scope. Adds a solidity axis to node hover signature alongside
4
+ * R217 stroke tint + R142 drop-shadow + R26 lift + R427/R428 ls.
5
+ *
6
+ * Contract:
7
+ * - rest (cyber): every label-card reports opacity '0.94'
8
+ * - rest elevation attr 'idle' on all cards
9
+ * - hover one node: that card opacity '1' + elevation 'hover'
10
+ * - siblings stay at 0.94 / 'idle'
11
+ * - source-file probe confirms conditional + R246 transition intact
12
+ */
13
+ import { chromium } from 'playwright';
14
+ import { readFileSync } from 'node:fs';
15
+
16
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
17
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
18
+
19
+ const browser = await chromium.launch({ headless: true });
20
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
21
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
22
+ await ctx.addInitScript(() => {
23
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
24
+ });
25
+ await ctx.route('**/api/hub/status*', async (route) => {
26
+ const r = await route.fetch();
27
+ const b = await r.json();
28
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
29
+ const mk = (alias, status) => ({
30
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
31
+ network_id: nid, project_dir: null,
32
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
33
+ });
34
+ await route.fulfill({ response: r, json: { ...b, sessions: [
35
+ mk('alpha', 'working'),
36
+ mk('beta', 'idle'),
37
+ mk('gamma', 'working'),
38
+ ] } });
39
+ });
40
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
41
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
42
+
43
+ const page = await ctx.newPage();
44
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
45
+ await page.waitForSelector('[data-node-label-card]', { timeout: 15000 });
46
+ await page.waitForTimeout(400);
47
+
48
+ const readAll = () => page.evaluate(() => {
49
+ const rects = [...document.querySelectorAll('[data-node-label-card]')];
50
+ return rects.map(t => ({
51
+ alias: t.getAttribute('data-node-label-card'),
52
+ opacity: t.getAttribute('opacity'),
53
+ elevation: t.getAttribute('data-node-label-card-elevation'),
54
+ }));
55
+ });
56
+
57
+ const rest = await readAll();
58
+ const firstAlias = rest[0]?.alias;
59
+ let hover = null;
60
+ if (firstAlias) {
61
+ const box = await page.evaluate((alias) => {
62
+ const t = document.querySelector(`[data-node-label-card="${alias}"]`);
63
+ if (!t) return null;
64
+ const node = t.closest('[data-node]');
65
+ const target = node || t;
66
+ const b = target.getBoundingClientRect();
67
+ return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
68
+ }, firstAlias);
69
+ if (box) {
70
+ await page.mouse.move(box.x, box.y);
71
+ await page.waitForTimeout(300);
72
+ hover = await readAll();
73
+ await page.mouse.move(0, 0);
74
+ }
75
+ }
76
+
77
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
78
+ const sourceWired = /opacity=\{\s*\n?\s*!reducedMotion && hoveredAlias === session\.alias\s*\n?\s*\?\s*1\s*\n?\s*:\s*\(isLight \? 1 : 0\.94\)\s*\n?\s*\}/.test(fileText);
79
+ const sourceTransition = /transition: 'filter 220ms ease-out, stroke 220ms ease-out, fill 220ms ease-out, opacity 220ms ease-out'/.test(fileText);
80
+
81
+ await browser.close();
82
+
83
+ const hoveredEntry = hover?.find(r => r.alias === firstAlias);
84
+ const othersRest = hover ? hover.filter(r => r.alias !== firstAlias).every(r => r.opacity === '0.94' && r.elevation === 'idle') : false;
85
+
86
+ const results = {
87
+ rest_card_count_gte_3: rest.length >= 3,
88
+ rest_all_opacity_0_94: rest.every(r => r.opacity === '0.94'),
89
+ rest_all_elevation_idle: rest.every(r => r.elevation === 'idle'),
90
+ hover_target_opacity_1: hoveredEntry?.opacity === '1',
91
+ hover_target_elevation: hoveredEntry?.elevation === 'hover',
92
+ hover_others_stay_idle: othersRest,
93
+ source_conditional_wired: sourceWired,
94
+ source_transition_intact: sourceTransition,
95
+ };
96
+ const ok = Object.values(results).every(Boolean);
97
+ console.log(`${ok ? '✅' : '❌'} label card opacity hover:`, JSON.stringify(results),
98
+ '\n hover target:', JSON.stringify(hoveredEntry));
99
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,109 @@
1
+ /* Round 351 verification: Layout-toggle Ring|Grid buttons gain
2
+ * hover-state letter-spacing (Tailwind hover:tracking-wide =
3
+ * 0.025em). Extends the R344 (footer) / R345 (panel titles) /
4
+ * R347 (zoom-level readout) hover-letter-spacing family to a
5
+ * 4th surface — the chrome-strip Ring/Grid layout-toggle pair.
6
+ *
7
+ * Pure-CSS approach: no new React state. transition-colors
8
+ * className dropped in favour of an inline transition spec
9
+ * that bundles bg/color (150ms ease) + letter-spacing (200ms
10
+ * ease-out) so the tracking-wide hover doesn't snap. Grid
11
+ * button additionally keeps border-color in the transition
12
+ * list (R268 theme-ease).
13
+ *
14
+ * Contract (cyber):
15
+ * - Rest: both buttons computed letter-spacing === 'normal' (0).
16
+ * - Hover Ring: computed letter-spacing === '0.4px' (0.025em ×
17
+ * ~14-16 base font; we'll accept any value > 0.1px).
18
+ * - Hover Grid: same.
19
+ * - Inline transition on both contains 'letter-spacing'.
20
+ * - Pre-R351 invariants: data-topo-chrome-layout attrs intact,
21
+ * R268 border-color transition still present on Grid.
22
+ */
23
+ import { chromium } from 'playwright';
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
27
+ const browser = await chromium.launch({ headless: true });
28
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
32
+ });
33
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
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: 'working', 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('a'), mk('b'), mk('c') ] } });
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-layout="ring"]', { timeout: 15000 });
51
+ await page.waitForSelector('[data-topo-chrome-layout="grid"]', { timeout: 5000 });
52
+ await page.waitForTimeout(300);
53
+
54
+ const restProbe = await page.evaluate(() => {
55
+ const r = document.querySelector('[data-topo-chrome-layout="ring"]');
56
+ const g = document.querySelector('[data-topo-chrome-layout="grid"]');
57
+ const rcs = r ? getComputedStyle(r) : null;
58
+ const gcs = g ? getComputedStyle(g) : null;
59
+ return {
60
+ ringLs: rcs?.letterSpacing ?? null,
61
+ ringTrans: rcs?.transition ?? null,
62
+ gridLs: gcs?.letterSpacing ?? null,
63
+ gridTrans: gcs?.transition ?? null,
64
+ };
65
+ });
66
+
67
+ // Hover Ring button.
68
+ await page.hover('[data-topo-chrome-layout="ring"]');
69
+ await page.waitForTimeout(300);
70
+ const ringHover = await page.evaluate(() => {
71
+ const r = document.querySelector('[data-topo-chrome-layout="ring"]');
72
+ return { ls: r ? getComputedStyle(r).letterSpacing : null };
73
+ });
74
+
75
+ // Move to Grid button.
76
+ await page.hover('[data-topo-chrome-layout="grid"]');
77
+ await page.waitForTimeout(300);
78
+ const gridHover = await page.evaluate(() => {
79
+ const g = document.querySelector('[data-topo-chrome-layout="grid"]');
80
+ return { ls: g ? getComputedStyle(g).letterSpacing : null };
81
+ });
82
+
83
+ await browser.close();
84
+
85
+ const isRestLs = (v) => v === 'normal' || v === '0px' || v === '0' || v === null;
86
+ const isHoverLs = (v) => {
87
+ // tracking-wide = 0.025em. At base 14-16px font, that's ~0.35-0.4px.
88
+ if (!v || v === 'normal') return false;
89
+ const n = parseFloat(v);
90
+ return Number.isFinite(n) && n > 0.2 && n < 1.0;
91
+ };
92
+ const hasTrans = (s, prop) =>
93
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
94
+
95
+ const results = {
96
+ rest_ring_ls_zero: isRestLs(restProbe.ringLs),
97
+ rest_grid_ls_zero: isRestLs(restProbe.gridLs),
98
+ ring_trans_has_ls: hasTrans(restProbe.ringTrans, 'letter-spacing'),
99
+ grid_trans_has_ls: hasTrans(restProbe.gridTrans, 'letter-spacing'),
100
+ grid_trans_has_bcol: hasTrans(restProbe.gridTrans, 'border-color'), // R268
101
+ hover_ring_tracked: isHoverLs(ringHover.ls),
102
+ hover_grid_tracked: isHoverLs(gridHover.ls),
103
+ };
104
+ const ok = Object.values(results).every(Boolean);
105
+ console.log(`${ok ? '✅' : '❌'} layout-toggle hover tracking-wide:`, JSON.stringify(results),
106
+ '\n rest: ', restProbe,
107
+ '\n hover ring: ', ringHover,
108
+ '\n hover grid: ', gridHover);
109
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,87 @@
1
+ /* Round 375 verification: Layout-toggle wrapper rounded-md → rounded-
2
+ * lg (6 → 8 px). Extends the R330/R331/R332 corner-radius cascade to
3
+ * the chrome-strip layout-toggle wrapper. Pre-R375 the wrapper at
4
+ * rounded-md (6 px) was the only chrome-strip container still using
5
+ * the smaller corner radius — both R330 outer wrapper and R332
6
+ * minimap sit at ≥ 8 px; R375 brings the toggle into the rounded-lg
7
+ * tier where minimap already lives.
8
+ *
9
+ * Contract:
10
+ * - data-topo-chrome-layout-trailer element computed border-radius
11
+ * === '8px' (rounded-lg).
12
+ * - data-topo-chrome-layout-radius === 'rounded-lg'.
13
+ * - Pre-R375 invariants:
14
+ * * border still present (1 px default Tailwind)
15
+ * * R268 inline borderColor + 200ms border-color transition
16
+ * * R329 mr-0.5 spacing preserved
17
+ * * inline-flex + overflow-hidden preserved
18
+ * * Inner buttons (Ring + Grid) still mounted as flex children
19
+ */
20
+ import { chromium } from 'playwright';
21
+ import { readFileSync } from 'node:fs';
22
+
23
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
24
+ const browser = await chromium.launch({ headless: true });
25
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
26
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
27
+ await ctx.addInitScript(() => {
28
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
29
+ });
30
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
31
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ const mk = (alias) => ({
36
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
37
+ network_id: nid, project_dir: null,
38
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
39
+ });
40
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b'), mk('c') ] } });
41
+ });
42
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
44
+
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForSelector('[data-topo-chrome-layout-trailer]', { timeout: 15000 });
48
+ await page.waitForTimeout(300);
49
+
50
+ const probe = await page.evaluate(() => {
51
+ const w = document.querySelector('[data-topo-chrome-layout-trailer]');
52
+ const cs = w ? getComputedStyle(w) : null;
53
+ const ring = w?.querySelector('[data-topo-chrome-layout="ring"]');
54
+ const grid = w?.querySelector('[data-topo-chrome-layout="grid"]');
55
+ return {
56
+ borderRadius: cs?.borderRadius ?? null,
57
+ radiusData: w?.getAttribute('data-topo-chrome-layout-radius') ?? null,
58
+ borderWidth: cs?.borderWidth ?? null,
59
+ display: cs?.display ?? null,
60
+ overflow: cs?.overflow ?? null,
61
+ transition: cs?.transition ?? null,
62
+ cls: w?.getAttribute('class') ?? null,
63
+ ringPresent: !!ring,
64
+ gridPresent: !!grid,
65
+ };
66
+ });
67
+
68
+ await browser.close();
69
+
70
+ const hasTrans = (s, prop) =>
71
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
72
+
73
+ const results = {
74
+ border_radius_8px: probe.borderRadius === '8px', // rounded-lg
75
+ data_radius_lg: probe.radiusData === 'rounded-lg',
76
+ border_width_1px: probe.borderWidth === '1px',
77
+ display_inline_flex: probe.display === 'inline-flex' || probe.display === 'flex',
78
+ overflow_hidden: probe.overflow === 'hidden',
79
+ trans_has_border_color: hasTrans(probe.transition, 'border-color'), // R268
80
+ cls_has_mr_0_5: /mr-0\.5/.test(probe.cls || ''), // R329
81
+ ring_button_present: probe.ringPresent,
82
+ grid_button_present: probe.gridPresent,
83
+ };
84
+ const ok = Object.values(results).every(Boolean);
85
+ console.log(`${ok ? '✅' : '❌'} Layout-toggle wrapper rounded-md → rounded-lg:`, JSON.stringify(results),
86
+ '\n probe:', probe);
87
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,94 @@
1
+ /* Round 364 verification: legend-row label fontWeight 400 → 500.
2
+ * Sibling typography lift to R363 recent-row text fw 400 → 500.
3
+ * Both surfaces render small monospace text against panel chrome
4
+ * at fontSize 9-11 where SVG-default fw 400 sits at the legibility
5
+ * floor.
6
+ *
7
+ * Family snapshot post-R364:
8
+ * legend label fw 500 (R364, this round)
9
+ * legend count fw 600 (R309)
10
+ * recent alias fw 500 (R363)
11
+ * recent count fw 600/700 (R320)
12
+ *
13
+ * Contract (cyber, fixture with active rows):
14
+ * - Each rendered [data-legend-row-label] has font-weight === '500'.
15
+ * - Each has data-legend-row-label-font-weight === '500'.
16
+ * - Pre-R364 invariants:
17
+ * * fontSize=11 + fontFamily=monospace
18
+ * * R219 letter-spacing 0 → 0.5px on pin (style.transition still
19
+ * lists letter-spacing)
20
+ * * R55 fill transition retained
21
+ * * data-legend-row-label key surfaces (working / idle / offline)
22
+ */
23
+ import { chromium } from 'playwright';
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
27
+ const browser = await chromium.launch({ headless: true });
28
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
32
+ });
33
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
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: 'working', 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('a'), mk('b'), mk('c') ] } });
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-legend-row-label]', { timeout: 15000 });
51
+ await page.waitForTimeout(300);
52
+
53
+ const probe = await page.evaluate(() => {
54
+ const labels = Array.from(document.querySelectorAll('[data-legend-row-label]'));
55
+ return labels.map(el => {
56
+ const cs = getComputedStyle(el);
57
+ return {
58
+ key: el.getAttribute('data-legend-row-label'),
59
+ fontWeightAttr: el.getAttribute('font-weight'),
60
+ fontWeightData: el.getAttribute('data-legend-row-label-font-weight'),
61
+ fontSizeAttr: el.getAttribute('font-size'),
62
+ fontFamily: el.getAttribute('font-family'),
63
+ transition: cs.transition,
64
+ };
65
+ });
66
+ });
67
+
68
+ await browser.close();
69
+
70
+ const hasTrans = (s, prop) =>
71
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
72
+
73
+ const allRowsOk = probe.length > 0 && probe.every(r =>
74
+ r.fontWeightAttr === '500'
75
+ && r.fontWeightData === '500'
76
+ && r.fontSizeAttr === '11'
77
+ && /monospace/i.test(r.fontFamily || '')
78
+ && hasTrans(r.transition, 'letter-spacing')
79
+ && hasTrans(r.transition, 'fill')
80
+ );
81
+
82
+ const results = {
83
+ rows_rendered: probe.length >= 1,
84
+ all_rows_fw_500: probe.every(r => r.fontWeightAttr === '500'),
85
+ all_rows_data_500: probe.every(r => r.fontWeightData === '500'),
86
+ all_rows_fontsize_11: probe.every(r => r.fontSizeAttr === '11'),
87
+ all_rows_monospace: probe.every(r => /monospace/i.test(r.fontFamily || '')),
88
+ all_rows_have_ls_trans: probe.every(r => hasTrans(r.transition, 'letter-spacing')),
89
+ all_rows_have_fill_trans: probe.every(r => hasTrans(r.transition, 'fill')),
90
+ };
91
+ const ok = allRowsOk && Object.values(results).every(Boolean);
92
+ console.log(`${ok ? '✅' : '❌'} legend-row label fw 400 → 500:`, JSON.stringify(results),
93
+ '\n rows: ', probe.map(r => ({ key: r.key, fw: r.fontWeightAttr })));
94
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,101 @@
1
+ /* Round 402 verification: legend pin-ring strokeWidth 1.5 → 1.75.
2
+ * Sibling visual-weight bump (12th anchor) to R385 hub hover-ring
3
+ * — both are stroke-only pin/hover indicators painted as r=8 / r=14
4
+ * circles outside their target. R51 sentinel concern: 1.5 reserved
5
+ * for offline-node detection, but selector gated to g[data-node]
6
+ * ancestors so this legend-internal circle is invisible to probe.
7
+ *
8
+ * Contract:
9
+ * - data-legend-pin-ring-stroke-width === '1.75' (idle state too,
10
+ * since the attr surfaces the resolved value regardless of pin)
11
+ * - <circle> stroke-width attr === '1.75'
12
+ * - Pre-R402 invariants preserved:
13
+ * * r='8', fill='none', cx='16'
14
+ * * opacity reflects isPinned state (0 idle, 1 pinned)
15
+ * * data-legend-pin-ring-pinned attr present
16
+ * - Idle ring: opacity='0' (R181 always-mount + transition)
17
+ * - Pinned ring (after click): opacity='1'
18
+ */
19
+ import { chromium } from 'playwright';
20
+ import { readFileSync } from 'node:fs';
21
+
22
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
23
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
24
+
25
+ const browser = await chromium.launch({ headless: true });
26
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
27
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
28
+ await ctx.addInitScript(() => {
29
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
30
+ });
31
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ const mk = (alias, status) => ({
36
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
37
+ network_id: nid, project_dir: null,
38
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
39
+ });
40
+ await route.fulfill({ response: r, json: { ...b, sessions: [
41
+ mk('alpha', 'working'),
42
+ mk('beta', 'idle'),
43
+ ] } });
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-legend-pin-ring]', { timeout: 15000 });
51
+ await page.waitForTimeout(400);
52
+
53
+ const restProbe = await page.evaluate(() => {
54
+ const ring = document.querySelector('[data-legend-pin-ring]');
55
+ return ring ? {
56
+ strokeWidthAttr: ring.getAttribute('stroke-width'),
57
+ strokeWidthData: ring.getAttribute('data-legend-pin-ring-stroke-width'),
58
+ rAttr: ring.getAttribute('r'),
59
+ fillAttr: ring.getAttribute('fill'),
60
+ cxAttr: ring.getAttribute('cx'),
61
+ opacityAttr: ring.getAttribute('opacity'),
62
+ pinned: ring.getAttribute('data-legend-pin-ring-pinned'),
63
+ } : null;
64
+ });
65
+
66
+ // Click a legend row to pin it
67
+ await page.click('[data-legend-status="working"]');
68
+ await page.waitForTimeout(300);
69
+
70
+ const pinnedProbe = await page.evaluate(() => {
71
+ const rings = Array.from(document.querySelectorAll('[data-legend-pin-ring]'));
72
+ const pinned = rings.find((r) => r.getAttribute('data-legend-pin-ring-pinned') === 'true');
73
+ return pinned ? {
74
+ strokeWidthAttr: pinned.getAttribute('stroke-width'),
75
+ opacityAttr: pinned.getAttribute('opacity'),
76
+ rAttr: pinned.getAttribute('r'),
77
+ } : null;
78
+ });
79
+
80
+ await browser.close();
81
+
82
+ const results = {
83
+ // R402 wire: stroke-width attr + data both 1.75
84
+ rest_strokeWidth_1_75: restProbe?.strokeWidthAttr === '1.75',
85
+ rest_data_1_75: restProbe?.strokeWidthData === '1.75',
86
+ // Pre-R402 invariants (R181 + base geometry)
87
+ rest_r_8: restProbe?.rAttr === '8',
88
+ rest_fill_none: restProbe?.fillAttr === 'none',
89
+ rest_cx_16: restProbe?.cxAttr === '16',
90
+ rest_opacity_0: restProbe?.opacityAttr === '0', // R181 idle invariant
91
+ rest_pinned_false: restProbe?.pinned === 'false',
92
+ // Pinned state preserves the bumped stroke
93
+ pinned_strokeWidth_1_75: pinnedProbe?.strokeWidthAttr === '1.75',
94
+ pinned_opacity_1: pinnedProbe?.opacityAttr === '1', // R181 pinned alpha
95
+ pinned_r_8: pinnedProbe?.rAttr === '8', // geometry preserved
96
+ };
97
+ const ok = Object.values(results).every(Boolean);
98
+ console.log(`${ok ? '✅' : '❌'} legend pin-ring strokeWidth 1.5 → 1.75:`, JSON.stringify(results),
99
+ '\n rest: ', restProbe,
100
+ '\n pinned: ', pinnedProbe);
101
+ process.exit(ok ? 0 : 1);