@sleep2agi/agent-network-dashboard 0.5.3-preview.1 → 0.5.3-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 (261) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/diagnostics/route-bundle-stats.json +32 -32
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/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 +12 -12
  15. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  16. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  17. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  18. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  19. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  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 +14 -14
  24. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  25. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  26. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  27. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  28. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  29. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  30. package/.next/server/app/index.html +2 -2
  31. package/.next/server/app/index.rsc +14 -14
  32. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  33. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  34. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  35. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  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 +14 -14
  40. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  41. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  42. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  43. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  44. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  45. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  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 +14 -14
  49. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  50. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  51. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  52. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  53. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  54. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  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 +14 -14
  58. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  59. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  60. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  61. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  62. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  63. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  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 +14 -14
  67. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  68. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  69. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  70. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  71. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  72. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  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 +14 -14
  76. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  77. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  78. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  79. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  80. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  81. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  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 +14 -14
  86. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  87. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  88. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  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 +4 -4
  91. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  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 +14 -14
  95. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  96. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  97. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  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 +4 -4
  100. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  101. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  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 +14 -14
  106. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  107. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  108. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  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 +4 -4
  111. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  112. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  113. package/.next/server/app/settings.html +2 -2
  114. package/.next/server/app/settings.rsc +14 -14
  115. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  116. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  117. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  118. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  119. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  120. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  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 +14 -14
  125. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  126. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  127. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  128. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  130. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  131. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js +1 -1
  132. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +1 -1
  133. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -4
  134. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  135. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  136. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  137. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  138. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  139. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  140. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.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/static/chunks/0-c2b_r~s.a00.js +1 -0
  145. package/.next/static/chunks/{0ce~u5hn~wege.js → 0nvxj45vzw-1t.js} +1 -1
  146. package/.next/static/chunks/{03a4--7ncekmk.js → 0v4-5tng.uh.7.js} +2 -2
  147. package/.next/static/chunks/0v9i~z.k.2uji.js +1 -0
  148. package/.next/static/chunks/0w3dd-f9bl6xs.js +4 -0
  149. package/.next/static/chunks/13~aih56vx-cf.css +2 -0
  150. package/.next/trace +2 -2
  151. package/.next/trace-build +1 -1
  152. package/app/components/ServersDrawer.tsx +16 -3
  153. package/app/components/TopoGraph.tsx +2747 -140
  154. package/app/globals.css +142 -6
  155. package/package.json +4 -4
  156. package/scripts/p157-servers-copy-test.mjs +95 -0
  157. package/scripts/topo-active-chrome-hover-text-test.mjs +107 -0
  158. package/scripts/topo-alias-glow-test.mjs +121 -0
  159. package/scripts/topo-avatar-brightness-test.mjs +116 -0
  160. package/scripts/topo-avatar-fallback-hover-test.mjs +104 -0
  161. package/scripts/topo-brand-logo-breath-test.mjs +102 -0
  162. package/scripts/topo-brand-logo-hover-brightness-test.mjs +105 -0
  163. package/scripts/topo-brand-logo-hover-rotate-test.mjs +93 -0
  164. package/scripts/topo-brand-logo-hover-test.mjs +85 -0
  165. package/scripts/topo-chip-row-digit-ls-test.mjs +135 -0
  166. package/scripts/topo-chip-row-member-alias-lit-test.mjs +154 -0
  167. package/scripts/topo-chip-row-press-test.mjs +93 -0
  168. package/scripts/topo-chip-row-tier-glow-brightness-test.mjs +99 -0
  169. package/scripts/topo-chip-row-unit-hover-tracking-test.mjs +124 -0
  170. package/scripts/topo-chrome-press-fullstrip-test.mjs +105 -0
  171. package/scripts/topo-chrome-press-scale-test.mjs +100 -0
  172. package/scripts/topo-cluster-count-attr-test.mjs +80 -0
  173. package/scripts/topo-crescent-breath-test.mjs +104 -0
  174. package/scripts/topo-crescent-recede-test.mjs +111 -0
  175. package/scripts/topo-edge-badge-hover-glow-test.mjs +90 -0
  176. package/scripts/topo-edge-badge-text-brightness-test.mjs +83 -0
  177. package/scripts/topo-edge-particle-brightness-test.mjs +82 -0
  178. package/scripts/topo-edge-pill-glow-test.mjs +67 -0
  179. package/scripts/topo-edge-visible-brightness-test.mjs +84 -0
  180. package/scripts/topo-endpoint-ring-brightness-test.mjs +83 -0
  181. package/scripts/topo-filter-pill-glow-test.mjs +90 -0
  182. package/scripts/topo-filter-pills-press-test.mjs +96 -0
  183. package/scripts/topo-fleet-density-tier-test.mjs +84 -0
  184. package/scripts/topo-flow-rail-brightness-test.mjs +80 -0
  185. package/scripts/topo-focus-outline-transition-test.mjs +107 -0
  186. package/scripts/topo-freshness-chip-fade-test.mjs +105 -0
  187. package/scripts/topo-fullscreen-attr-test.mjs +73 -0
  188. package/scripts/topo-fullscreen-icon-rotate-test.mjs +93 -0
  189. package/scripts/topo-grid-content-bottom-attr-test.mjs +72 -0
  190. package/scripts/topo-group-label-brightness-test.mjs +84 -0
  191. package/scripts/topo-group-label-hover-glow-test.mjs +86 -0
  192. package/scripts/topo-group-label-member-alias-hover-test.mjs +125 -0
  193. package/scripts/topo-group-pill-glow-test.mjs +76 -0
  194. package/scripts/topo-hover-ring-duration-test.mjs +87 -0
  195. package/scripts/topo-hub-digit-brightness-test.mjs +79 -0
  196. package/scripts/topo-hub-digit-ls-test.mjs +119 -0
  197. package/scripts/topo-hub-halo-brightness-test.mjs +80 -0
  198. package/scripts/topo-hub-halo-glow-test.mjs +96 -0
  199. package/scripts/topo-hub-highlight-amplify-test.mjs +139 -0
  200. package/scripts/topo-hub-highlight-brightness-test.mjs +84 -0
  201. package/scripts/topo-hub-highlight-fill-transition-test.mjs +84 -0
  202. package/scripts/topo-hub-highlight-glow-test.mjs +99 -0
  203. package/scripts/topo-hub-highlight-r-test.mjs +112 -0
  204. package/scripts/topo-hub-highlight-recede-test.mjs +144 -0
  205. package/scripts/topo-hub-highlight-theme-fill-test.mjs +83 -0
  206. package/scripts/topo-hub-hover-ring-brightness-test.mjs +79 -0
  207. package/scripts/topo-hub-hover-ring-glow-test.mjs +97 -0
  208. package/scripts/topo-hub-idle-breath-test.mjs +109 -0
  209. package/scripts/topo-hub-recede-test.mjs +124 -0
  210. package/scripts/topo-hub-spoke-brightness-test.mjs +77 -0
  211. package/scripts/topo-hub-spoke-glow-test.mjs +112 -0
  212. package/scripts/topo-layout-hover-fw-test.mjs +98 -0
  213. package/scripts/topo-legend-count-letter-spacing-test.mjs +108 -0
  214. package/scripts/topo-legend-label-fw-test.mjs +107 -0
  215. package/scripts/topo-legend-row-label-glow-test.mjs +102 -0
  216. package/scripts/topo-legend-swatch-glow-test.mjs +109 -0
  217. package/scripts/topo-legend-swatch-member-alias-match-test.mjs +139 -0
  218. package/scripts/topo-minimap-hover-glow-test.mjs +109 -0
  219. package/scripts/topo-node-alias-brightness-test.mjs +84 -0
  220. package/scripts/topo-node-sub-text-brightness-test.mjs +88 -0
  221. package/scripts/topo-nodesize-hover-fw-test.mjs +99 -0
  222. package/scripts/topo-orphan-box-dash-test.mjs +89 -0
  223. package/scripts/topo-orphan-fill-opacity-test.mjs +91 -0
  224. package/scripts/topo-orphan-label-italic-test.mjs +90 -0
  225. package/scripts/topo-orphan-label-opacity-test.mjs +98 -0
  226. package/scripts/topo-panel-count-hover-ls-test.mjs +87 -0
  227. package/scripts/topo-panel-row-brightness-test.mjs +116 -0
  228. package/scripts/topo-panel-title-brightness-test.mjs +98 -0
  229. package/scripts/topo-panel-title-glow-test.mjs +111 -0
  230. package/scripts/topo-pill-x-rotate-test.mjs +96 -0
  231. package/scripts/topo-pinned-aspect-test.mjs +89 -0
  232. package/scripts/topo-pressure-seg-glow-test.mjs +92 -0
  233. package/scripts/topo-pressure-seg-member-alias-match-test.mjs +133 -0
  234. package/scripts/topo-pressure-seg-motion-test.mjs +101 -0
  235. package/scripts/topo-recent-hot-pulse-test.mjs +102 -0
  236. package/scripts/topo-recent-more-fw-test.mjs +126 -0
  237. package/scripts/topo-recent-row-fw-test.mjs +115 -0
  238. package/scripts/topo-recent-row-text-glow-test.mjs +86 -0
  239. package/scripts/topo-reduced-motion-attr-test.mjs +69 -0
  240. package/scripts/topo-reset-icon-hover-scale-test.mjs +102 -0
  241. package/scripts/topo-runtime-badge-brightness-test.mjs +78 -0
  242. package/scripts/topo-runtime-badge-glow-test.mjs +108 -0
  243. package/scripts/topo-starfield-hue-test.mjs +109 -0
  244. package/scripts/topo-status-ring-brightness-test.mjs +84 -0
  245. package/scripts/topo-svg-focus-transition-test.mjs +105 -0
  246. package/scripts/topo-titleblock-h2-hover-fw-test.mjs +109 -0
  247. package/scripts/topo-titleblock-h2-hover-tracking-test.mjs +128 -0
  248. package/scripts/topo-titleblock-kicker-hover-test.mjs +134 -0
  249. package/scripts/topo-vendor-activelinks-press-test.mjs +100 -0
  250. package/scripts/topo-vendor-chip-glow-test.mjs +97 -0
  251. package/scripts/topo-vendor-pill-glow-test.mjs +98 -0
  252. package/scripts/topo-watermark-breath-test.mjs +100 -0
  253. package/scripts/topo-watermark-recede-test.mjs +114 -0
  254. package/scripts/topo-zoom-level-color-test.mjs +105 -0
  255. package/.next/static/chunks/0a4hmfvj-81x5.css +0 -2
  256. package/.next/static/chunks/14m8prv3qgm45.js +0 -4
  257. package/.next/static/chunks/17fq.aa.hsdd..js +0 -1
  258. package/.next/static/chunks/17~las5t-t.kj.js +0 -1
  259. /package/.next/static/{cBZM18sgaGtUo-xccD_a3 → oBpu6kOuBIOwABE5kiB8V}/_buildManifest.js +0 -0
  260. /package/.next/static/{cBZM18sgaGtUo-xccD_a3 → oBpu6kOuBIOwABE5kiB8V}/_clientMiddlewareManifest.js +0 -0
  261. /package/.next/static/{cBZM18sgaGtUo-xccD_a3 → oBpu6kOuBIOwABE5kiB8V}/_ssgManifest.js +0 -0
@@ -0,0 +1,78 @@
1
+ /* Round 586 verification: runtime badge outer <g> stacks
2
+ * brightness(1.15) onto the R559 drop-shadow on node hover.
3
+ * 25th anchor in per-element brightness family, 18th in
4
+ * stacked-filter sub-pattern.
5
+ *
6
+ * Test phases:
7
+ * 1. mock 2 idle nodes → runtime badges render
8
+ * 2. rest: filter='none' on outer <g> (no hover), brightness-attr='1'
9
+ * 3. transition-property contains 'filter'
10
+ * 4. source: filter string contains 'brightness(1.15)' stacked
11
+ * onto drop-shadow + data-attr
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: 1200 } });
21
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
22
+ await ctx.addInitScript(() => {
23
+ try {
24
+ localStorage.setItem('anet-theme', 'cyber');
25
+ localStorage.setItem('anet-topo-layout', 'ring');
26
+ sessionStorage.setItem('anet_v3_auth', '1');
27
+ } 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('a·1'), mk('a·2')] } });
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
+ const page = await ctx.newPage();
43
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
44
+ await page.waitForSelector('[data-runtime-badge-brightness]', { timeout: 15000, state: 'attached' });
45
+ await page.waitForTimeout(500);
46
+
47
+ const rest = await page.evaluate(() => {
48
+ const el = document.querySelector('[data-runtime-badge-brightness]');
49
+ if (!el) return null;
50
+ const cs = getComputedStyle(el);
51
+ return {
52
+ filter: cs.filter,
53
+ transitionProperty: cs.transitionProperty,
54
+ brightnessAttr: el.getAttribute('data-runtime-badge-brightness'),
55
+ glowAttr: el.getAttribute('data-runtime-badge-glow'),
56
+ };
57
+ });
58
+
59
+ await browser.close();
60
+
61
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
62
+ const sourceFilter = /filter: isNodeActive\s*\?\s*`drop-shadow\(0 0 2px \$\{rt\.color\}99\) brightness\(1\.15\)`\s*:\s*undefined/.test(src);
63
+ const sourceAttr = /data-runtime-badge-brightness=\{isNodeActive \? '1\.15' : '1'\}/.test(src);
64
+
65
+ const results = {
66
+ badge_present: !!rest,
67
+ rest_filter_none: rest?.filter === 'none',
68
+ rest_brightness_1: rest?.brightnessAttr === '1',
69
+ rest_glow_false: rest?.glowAttr === 'false',
70
+ transition_has_filter: /filter/.test(rest?.transitionProperty || ''),
71
+ source_filter: sourceFilter,
72
+ source_attr: sourceAttr,
73
+ };
74
+ const ok = Object.values(results).every(Boolean);
75
+ console.log(`${ok ? '✅' : '❌'} R586 runtime-badge brightness (25th anchor, badge 4-axis closure):`,
76
+ JSON.stringify(results, null, 2),
77
+ `\n rest: ${JSON.stringify(rest)}`);
78
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,108 @@
1
+ /* Round 559 verification: runtime badge outer <g> gains drop-shadow
2
+ * glow using rt.color on node hover. 16th anchor in drop-shadow
3
+ * family. Pairs with R208 (ring r+sw) + R443 (icon sw) for 3-axis
4
+ * hover signature.
5
+ *
6
+ * runtimeIdentity('claude-code-cli').color is one of:
7
+ * '#a78bfa', '#38bdf8', '#34d399', '#fbbf24'
8
+ *
9
+ * Test phases:
10
+ * 1. mock sessions with runtime → badge renders
11
+ * 2. rest: outer <g> filter='none', glow attr='false'
12
+ * 3. hover the parent node group → filter contains 'drop-shadow',
13
+ * glow attr='true', and the existing R208 ring r/sw still lift
14
+ * 4. source: drop-shadow with rt.color hex+99 alpha + transition
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
21
+
22
+ const browser = await chromium.launch({ headless: true });
23
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
24
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
25
+ await ctx.addInitScript(() => {
26
+ try {
27
+ localStorage.setItem('anet-theme', 'cyber');
28
+ localStorage.setItem('anet-topo-layout', 'ring');
29
+ sessionStorage.setItem('anet_v3_auth', '1');
30
+ } catch {}
31
+ });
32
+ // Use claude-code-cli runtime so the runtime badge renders.
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('a·1'), mk('a·2')] } });
43
+ });
44
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
45
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
46
+ const page = await ctx.newPage();
47
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
48
+ await page.waitForSelector('[data-runtime-badge="a·1"]', { timeout: 15000 });
49
+ await page.waitForTimeout(500);
50
+
51
+ // The outer <g> wrapping the runtime badge carries the glow filter.
52
+ // We can locate it via the parent of the [data-runtime-badge] circle.
53
+ const probe = async (state) => {
54
+ return page.evaluate(() => {
55
+ const circle = document.querySelector('[data-runtime-badge="a·1"]');
56
+ if (!circle) return null;
57
+ const outerG = circle.parentElement;
58
+ if (!outerG) return null;
59
+ const cs = getComputedStyle(outerG);
60
+ const ringCs = getComputedStyle(circle);
61
+ return {
62
+ outerFilter: cs.filter,
63
+ outerTransitionProperty: cs.transitionProperty,
64
+ outerTransitionDuration: cs.transitionDuration,
65
+ glowAttr: outerG.getAttribute('data-runtime-badge-glow'),
66
+ ringR: ringCs.r,
67
+ ringSw: ringCs.strokeWidth,
68
+ activeAttr: circle.getAttribute('data-runtime-badge-active'),
69
+ };
70
+ });
71
+ };
72
+
73
+ const rest = await probe();
74
+
75
+ await page.hover('g[data-node="a·1"]');
76
+ await page.waitForTimeout(400);
77
+ const hover = await probe();
78
+
79
+ await browser.close();
80
+
81
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
82
+ const sourceFilter = /filter: isNodeActive\s*\?\s*`drop-shadow\(0 0 2px \$\{rt\.color\}99\)`\s*:\s*undefined,/.test(src);
83
+ const sourceTransition = /transition: 'filter 150ms ease-out',/.test(src);
84
+ const sourceGlowAttr = /data-runtime-badge-glow=\{isNodeActive \? 'true' : 'false'\}/.test(src);
85
+
86
+ const results = {
87
+ rest_filter_none: rest?.outerFilter === 'none',
88
+ rest_glow_false: rest?.glowAttr === 'false',
89
+ rest_ring_r_7: rest?.ringR === '7px',
90
+ rest_ring_sw_1_5: rest?.ringSw === '1.5px',
91
+ rest_active_false: rest?.activeAttr === 'false',
92
+ hover_filter_dropshadow: /drop-shadow\(/.test(hover?.outerFilter || ''),
93
+ hover_glow_true: hover?.glowAttr === 'true',
94
+ hover_ring_r_8: hover?.ringR === '8px', // R208 still lifts
95
+ hover_ring_sw_2: hover?.ringSw === '2px', // R208 still lifts
96
+ hover_active_true: hover?.activeAttr === 'true',
97
+ transition_has_filter: /filter/.test(rest?.outerTransitionProperty || ''),
98
+ transition_duration: rest?.outerTransitionDuration === '0.15s',
99
+ source_filter: sourceFilter,
100
+ source_transition: sourceTransition,
101
+ source_glow_attr: sourceGlowAttr,
102
+ };
103
+ const ok = Object.values(results).every(Boolean);
104
+ console.log(`${ok ? '✅' : '❌'} R559 runtime badge drop-shadow glow (16th anchor):`,
105
+ JSON.stringify(results, null, 2),
106
+ `\n rest: ${JSON.stringify(rest)}`,
107
+ `\n hover: ${JSON.stringify(hover)}`);
108
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,109 @@
1
+ /* Round 523 verification: starfield gains 3-hue deterministic color
2
+ * rotation (indigo / cyan / slate) instead of flat #a5b4fc. 配色 family
3
+ * 3rd anchor (cyber theme; light theme unaffected per starfield gate).
4
+ *
5
+ * Test phases:
6
+ * 1. cyber: 14 dots present, each carries data-topo-starfield-dot-hue
7
+ * with one of: indigo / cyan / slate
8
+ * 2. distribution: dots at i % 3 === 0 → indigo (#a5b4fc),
9
+ * i % 3 === 1 → cyan (#67e8f9), i % 3 === 2 → slate (#cbd5e1)
10
+ * → 14 dots → 5 indigo + 5 cyan + 4 slate (indices 0..13)
11
+ * 3. all three hues present
12
+ * 4. light theme: starfield not rendered at all (existing gate)
13
+ * 5. source-side regex confirms hues array + dot-hue attr wired
14
+ */
15
+ import { chromium } from 'playwright';
16
+ import { readFileSync } from 'node:fs';
17
+
18
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
19
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
20
+
21
+ async function probe(theme) {
22
+ const browser = await chromium.launch({ headless: true });
23
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
24
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
25
+ await ctx.addInitScript((t) => {
26
+ try {
27
+ localStorage.setItem('anet-theme', t);
28
+ localStorage.setItem('anet-topo-layout', 'ring');
29
+ sessionStorage.setItem('anet_v3_auth', '1');
30
+ } catch {}
31
+ }, theme);
32
+ await ctx.route('**/api/hub/status*', async (route) => {
33
+ const r = await route.fetch();
34
+ const b = await r.json();
35
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
36
+ const mk = (alias) => ({
37
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
38
+ network_id: nid, project_dir: null,
39
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
40
+ });
41
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('a·1')] } });
42
+ });
43
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
44
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
47
+ await page.waitForSelector('svg[data-topo-layout]', { timeout: 15000 });
48
+ await page.waitForTimeout(800);
49
+ const data = await page.evaluate(() => {
50
+ const dots = Array.from(document.querySelectorAll('[data-topo-starfield-dot]'));
51
+ return dots.map((el) => ({
52
+ idx: el.getAttribute('data-topo-starfield-dot'),
53
+ hue: el.getAttribute('data-topo-starfield-dot-hue'),
54
+ fill: el.getAttribute('fill'),
55
+ }));
56
+ });
57
+ await browser.close();
58
+ return data;
59
+ }
60
+
61
+ const cyber = await probe('cyber');
62
+ const light = await probe('light');
63
+
64
+ // Source regex
65
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
66
+ const sourceHuesArr =
67
+ /const hues = \['#a5b4fc', '#67e8f9', '#cbd5e1'\] as const;/.test(src);
68
+ const sourceNamesArr =
69
+ /const hueNames = \['indigo', 'cyan', 'slate'\] as const;/.test(src);
70
+ const sourceAttrWired =
71
+ /data-topo-starfield-dot-hue=\{hueNames\[hueIdx\]\}/.test(src);
72
+
73
+ // Cyber: 14 dots, distribution by i % 3
74
+ const cyberHueCounts = { indigo: 0, cyan: 0, slate: 0 };
75
+ for (const d of cyber) {
76
+ if (d.hue && cyberHueCounts[d.hue] !== undefined) cyberHueCounts[d.hue]++;
77
+ }
78
+
79
+ // Verify each idx has the right hue per i % 3
80
+ const cyberIdxMatch = cyber.every((d) => {
81
+ const i = parseInt(d.idx, 10);
82
+ const expected = ['indigo', 'cyan', 'slate'][i % 3];
83
+ return d.hue === expected;
84
+ });
85
+
86
+ // Verify fill matches the hue
87
+ const cyberFillMatch = cyber.every((d) => {
88
+ const expectedFill = { indigo: '#a5b4fc', cyan: '#67e8f9', slate: '#cbd5e1' }[d.hue];
89
+ return d.fill === expectedFill;
90
+ });
91
+
92
+ const results = {
93
+ cyber_dot_count_14: cyber.length === 14,
94
+ cyber_indigo_count_5: cyberHueCounts.indigo === 5, // i=0,3,6,9,12 → 5
95
+ cyber_cyan_count_5: cyberHueCounts.cyan === 5, // i=1,4,7,10,13 → 5
96
+ cyber_slate_count_4: cyberHueCounts.slate === 4, // i=2,5,8,11 → 4
97
+ cyber_idx_to_hue_match: cyberIdxMatch,
98
+ cyber_hue_to_fill_match: cyberFillMatch,
99
+ light_starfield_empty: light.length === 0,
100
+ source_hues_array: sourceHuesArr,
101
+ source_names_array: sourceNamesArr,
102
+ source_attr_wired: sourceAttrWired,
103
+ };
104
+ const ok = Object.values(results).every(Boolean);
105
+ console.log(`${ok ? '✅' : '❌'} R523 starfield 3-hue rotation:`,
106
+ JSON.stringify(results, null, 2),
107
+ '\n cyber distribution:', JSON.stringify(cyberHueCounts),
108
+ '\n light count:', light.length);
109
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,84 @@
1
+ /* Round 584 verification: node status ring gains filter
2
+ * brightness(1.15) on hover. 23rd anchor in per-element
3
+ * brightness family. Stacks with url(#topo-glow) on
4
+ * cyber+online; plain brightness on light or cyber+offline.
5
+ *
6
+ * Test phases:
7
+ * 1. mock 2 idle nodes → status rings render
8
+ * 2. rest (cyber+online): filter='url("#topo-glow")'
9
+ * (no brightness stacked, no hover state)
10
+ * 3. brightness-attr='1' at rest
11
+ * 4. transition-property contains 'filter'
12
+ * 5. source: filter conditional (three-way tree) + data-attr
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
19
+
20
+ const browser = await chromium.launch({ headless: true });
21
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
22
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
23
+ await ctx.addInitScript(() => {
24
+ try {
25
+ localStorage.setItem('anet-theme', 'cyber');
26
+ localStorage.setItem('anet-topo-layout', 'ring');
27
+ sessionStorage.setItem('anet_v3_auth', '1');
28
+ } catch {}
29
+ });
30
+ await ctx.route('**/api/hub/status*', async (route) => {
31
+ const r = await route.fetch();
32
+ const b = await r.json();
33
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
34
+ const mk = (alias) => ({
35
+ alias, status: 'idle', 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·1'), mk('a·2')] } });
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
+ const page = await ctx.newPage();
44
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
45
+ await page.waitForSelector('[data-node-status-ring]', { timeout: 15000, state: 'attached' });
46
+ await page.waitForTimeout(500);
47
+
48
+ const rest = await page.evaluate(() => {
49
+ const el = document.querySelector('[data-node-status-ring]');
50
+ if (!el) return null;
51
+ const cs = getComputedStyle(el);
52
+ return {
53
+ filter: cs.filter,
54
+ transitionProperty: cs.transitionProperty,
55
+ brightnessAttr: el.getAttribute('data-node-status-ring-brightness'),
56
+ hoveredAttr: el.getAttribute('data-node-status-ring-hovered'),
57
+ label: el.getAttribute('data-node-status-ring'),
58
+ };
59
+ });
60
+
61
+ await browser.close();
62
+
63
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
64
+ const sourceFilter = /filter: isRingHovered\s*\?\s*\(isLight\s*\?\s*'brightness\(1\.15\)'\s*:\s*\(isOnline\s*\?\s*'url\(#topo-glow\) brightness\(1\.15\)'\s*:\s*'brightness\(1\.15\)'\)\)\s*:\s*undefined/.test(src);
65
+ const sourceAttr = /data-node-status-ring-brightness=\{isRingHovered \? '1\.15' : '1'\}/.test(src);
66
+ const sourceTransition = /transition: 'fill 300ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, filter 300ms ease-out'/.test(src);
67
+
68
+ const results = {
69
+ ring_present: !!rest,
70
+ // On cyber+online (mock default), SVG attribute filter applies at rest
71
+ rest_filter_has_url: /url\(/.test(rest?.filter || ''),
72
+ rest_filter_no_brightness: !/brightness\(/.test(rest?.filter || ''),
73
+ rest_brightness_1: rest?.brightnessAttr === '1',
74
+ rest_hovered_false: rest?.hoveredAttr === 'false',
75
+ transition_has_filter: /filter/.test(rest?.transitionProperty || ''),
76
+ source_filter: sourceFilter,
77
+ source_attr: sourceAttr,
78
+ source_transition: sourceTransition,
79
+ };
80
+ const ok = Object.values(results).every(Boolean);
81
+ console.log(`${ok ? '✅' : '❌'} R584 status-ring brightness (23rd anchor):`,
82
+ JSON.stringify(results, null, 2),
83
+ `\n rest: ${JSON.stringify(rest)}`);
84
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,105 @@
1
+ /* Round 491 verification: SVG-side focus outline on `.anet-topo-svg-focus`
2
+ * (R156 class, applied to SVG g elements for recent-signal rows, legend
3
+ * rows, group labels, edge badges, nodes, "+N more" footer) now eases
4
+ * outline-color through 200ms ease-out instead of hard-cutting.
5
+ *
6
+ * Sibling polish to R490 (HTML chip-focus). Together: every keyboard-
7
+ * focus surface on the TopoGraph canvas — HTML chips + SVG g — fades
8
+ * smoothly.
9
+ *
10
+ * Verifies:
11
+ * 1. baseline outline: 2px solid transparent on every .anet-topo-svg-focus
12
+ * 2. outline-color in computed transition-property
13
+ * 3. transition-duration === 200ms
14
+ * 4. source CSS rule wired
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
21
+
22
+ const browser = await chromium.launch({ headless: true });
23
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
24
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
25
+ await ctx.addInitScript(() => {
26
+ try {
27
+ localStorage.setItem('anet-theme', 'cyber');
28
+ localStorage.setItem('anet-topo-layout', 'ring');
29
+ sessionStorage.setItem('anet_v3_auth', '1');
30
+ } catch {}
31
+ });
32
+ await ctx.route('**/api/hub/status*', async (route) => {
33
+ const r = await route.fetch();
34
+ const b = await r.json();
35
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
36
+ const mk = (alias, status) => ({
37
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
38
+ network_id: nid, project_dir: null,
39
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
40
+ });
41
+ await route.fulfill({ response: r, json: { ...b, sessions: [
42
+ mk('alpha·a1', 'working'),
43
+ mk('alpha·a2', 'idle'),
44
+ mk('beta·b1', 'working'),
45
+ ] } });
46
+ });
47
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
48
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
51
+ await page.waitForSelector('.anet-topo-svg-focus', { timeout: 15000 });
52
+ await page.waitForTimeout(1500);
53
+
54
+ // Scan ALL .anet-topo-svg-focus elements
55
+ const baseline = await page.evaluate(() => {
56
+ const els = Array.from(document.querySelectorAll('.anet-topo-svg-focus'));
57
+ if (!els.length) return null;
58
+ let allOutlineBaseline = true;
59
+ let allOutlineInTransition = true;
60
+ let allDuration200 = true;
61
+ const samples = [];
62
+ els.slice(0, 10).forEach((el) => {
63
+ const cs = window.getComputedStyle(el);
64
+ const transp = /rgba\(0,\s*0,\s*0,\s*0\)/.test(cs.outlineColor) || cs.outlineColor === 'transparent';
65
+ if (!(cs.outlineWidth.startsWith('2') && cs.outlineStyle === 'solid' && transp)) {
66
+ allOutlineBaseline = false;
67
+ }
68
+ if (!/outline/i.test(cs.transitionProperty || '')) {
69
+ allOutlineInTransition = false;
70
+ }
71
+ if (!/\b0\.2s\b/.test(cs.transitionDuration || '')) {
72
+ allDuration200 = false;
73
+ }
74
+ samples.push({
75
+ tag: el.tagName.toLowerCase(),
76
+ role: el.getAttribute('role') || '-',
77
+ ow: cs.outlineWidth, os: cs.outlineStyle, oc: cs.outlineColor,
78
+ tp: cs.transitionProperty, td: cs.transitionDuration,
79
+ });
80
+ });
81
+ return { allOutlineBaseline, allOutlineInTransition, allDuration200, count: els.length, samples };
82
+ });
83
+
84
+ await browser.close();
85
+
86
+ const css = readFileSync('/home/vansin/agent-network-dashboard/app/globals.css', 'utf8');
87
+ const baselineWired = /\.anet-topo-svg-focus\s*\{[^}]*outline:\s*2px solid transparent/.test(css);
88
+ const transitionWired = /\.anet-topo-svg-focus\s*\{[^}]*transition-property:\s*outline-color\s*!important/.test(css);
89
+ const focusWired = /\.anet-topo-svg-focus:focus-visible\s*\{[^}]*outline-color:\s*#67e8f9/.test(css);
90
+
91
+ const results = {
92
+ baseline_resolved: baseline !== null,
93
+ any_elements_found: !!(baseline && baseline.count > 0),
94
+ all_outline_baseline: !!(baseline && baseline.allOutlineBaseline),
95
+ all_outline_in_transition:!!(baseline && baseline.allOutlineInTransition),
96
+ all_duration_200ms: !!(baseline && baseline.allDuration200),
97
+ source_baseline_wired: baselineWired,
98
+ source_transition_wired: transitionWired,
99
+ source_focus_wired: focusWired,
100
+ };
101
+ const ok = Object.values(results).every(Boolean);
102
+ console.log(`${ok ? '✅' : '❌'} svg-focus outline-color transition (R491):`, JSON.stringify(results),
103
+ '\n count:', baseline && baseline.count,
104
+ '\n sample[0]:', baseline && JSON.stringify(baseline.samples?.[0]));
105
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,109 @@
1
+ /* Round 556 verification: title-block H2 "Command mesh" gains
2
+ * a 2nd editorial-emphasis axis — group-hover:font-bold paired
3
+ * with R554's group-hover:tracking-tighter.
4
+ *
5
+ * Test phases:
6
+ * 1. rest: fontWeight = 600 (semibold), letter-spacing ≈ -0.45px
7
+ * 2. hover brand logo (sibling, propagates via group-hover):
8
+ * H2 fontWeight = 700 (bold), letter-spacing ≈ -0.9px
9
+ * 3. transition-property contains BOTH letter-spacing AND font-weight
10
+ * 4. source-side regex confirms group-hover:font-bold wired
11
+ */
12
+ import { chromium } from 'playwright';
13
+ import { readFileSync } from 'node:fs';
14
+
15
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
16
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
17
+
18
+ const browser = await chromium.launch({ headless: true });
19
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
20
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
21
+ await ctx.addInitScript(() => {
22
+ try {
23
+ localStorage.setItem('anet-theme', 'cyber');
24
+ localStorage.setItem('anet-topo-layout', 'ring');
25
+ sessionStorage.setItem('anet_v3_auth', '1');
26
+ } catch {}
27
+ });
28
+ await ctx.route('**/api/hub/status*', async (route) => {
29
+ const r = await route.fetch();
30
+ const b = await r.json();
31
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
32
+ const mk = (alias) => ({
33
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
34
+ network_id: nid, project_dir: null,
35
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
36
+ });
37
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('a·1')] } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
40
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
41
+ const page = await ctx.newPage();
42
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
43
+ await page.waitForSelector('[data-topo-section-title]', { timeout: 15000 });
44
+ await page.waitForTimeout(500);
45
+
46
+ const h2Sel = '[data-topo-section-title]';
47
+ const logoSel = '[data-topo-brand-logo]';
48
+
49
+ const parsePx = (s) => parseFloat((s || '').replace(/px$/, ''));
50
+
51
+ // Phase 1: rest
52
+ const rest = await page.evaluate((s) => {
53
+ const el = document.querySelector(s);
54
+ if (!el) return null;
55
+ const cs = getComputedStyle(el);
56
+ return {
57
+ fontWeight: cs.fontWeight,
58
+ letterSpacing: cs.letterSpacing,
59
+ transitionProperty: cs.transitionProperty,
60
+ transitionDuration: cs.transitionDuration,
61
+ hoverFwAttr: el.getAttribute('data-topo-section-title-hover-fw'),
62
+ hoverTrackingAttr: el.getAttribute('data-topo-section-title-hover-tracking'),
63
+ };
64
+ }, h2Sel);
65
+
66
+ // Phase 2: hover brand logo (sibling) → group-hover propagates
67
+ await page.hover(logoSel);
68
+ await page.waitForTimeout(400);
69
+ const hover = await page.evaluate((s) => {
70
+ const el = document.querySelector(s);
71
+ if (!el) return null;
72
+ const cs = getComputedStyle(el);
73
+ return { fontWeight: cs.fontWeight, letterSpacing: cs.letterSpacing };
74
+ }, h2Sel);
75
+
76
+ await browser.close();
77
+
78
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
79
+ const sourceFwWired = /group-hover:font-bold/.test(src);
80
+ const sourceTransition = /transition-\[letter-spacing,font-weight\] duration-200 ease-out/.test(src);
81
+ const sourceFwAttr = /data-topo-section-title-hover-fw="700"/.test(src);
82
+
83
+ const restPx = parsePx(rest?.letterSpacing);
84
+ const hoverPx = parsePx(hover?.letterSpacing);
85
+
86
+ const results = {
87
+ rest_fw_semibold: rest?.fontWeight === '600',
88
+ rest_tracking_tight: Math.abs(restPx - (-0.45)) < 0.05,
89
+ hover_fw_bold: hover?.fontWeight === '700',
90
+ hover_tracking_tighter: Math.abs(hoverPx - (-0.9)) < 0.05,
91
+ // Both axes intensify together — confirm by directionality
92
+ fw_intensifies: parseInt(hover?.fontWeight || '0') > parseInt(rest?.fontWeight || '0'),
93
+ tracking_intensifies: hoverPx < restPx - 0.3, // more negative = tighter
94
+ transition_has_ls: /letter-spacing/.test(rest?.transitionProperty || ''),
95
+ transition_has_fw: /font-weight/.test(rest?.transitionProperty || ''),
96
+ transition_duration: rest?.transitionDuration === '0.2s',
97
+ rest_hover_fw_attr: rest?.hoverFwAttr === '700',
98
+ rest_hover_tracking_attr: rest?.hoverTrackingAttr === 'tracking-tighter',
99
+ source_fw_wired: sourceFwWired,
100
+ source_transition: sourceTransition,
101
+ source_fw_attr: sourceFwAttr,
102
+ };
103
+ const ok = Object.values(results).every(Boolean);
104
+ console.log(`${ok ? '✅' : '❌'} R556 title-block H2 group-hover:font-bold (2-axis editorial emphasis):`,
105
+ JSON.stringify(results, null, 2),
106
+ `\n rest: fw=${rest?.fontWeight}, ls=${rest?.letterSpacing}`,
107
+ `\n hover: fw=${hover?.fontWeight}, ls=${hover?.letterSpacing}`,
108
+ `\n transition: ${rest?.transitionProperty}`);
109
+ process.exit(ok ? 0 : 1);