@sleep2agi/agent-network-dashboard 0.5.3-preview.12 → 0.5.3-preview.121

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 (272) 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/05-~ezd16iskp.css +2 -0
  145. package/.next/static/chunks/0_i2rayo5ufic.js +4 -0
  146. package/.next/static/chunks/{0f9~w1b6yn1dj.js → 0c7o0bzcd~3vc.js} +1 -1
  147. package/.next/static/chunks/{03a4--7ncekmk.js → 0v4-5tng.uh.7.js} +2 -2
  148. package/.next/static/chunks/14lqafir29vbd.js +1 -0
  149. package/.next/static/chunks/150r~1iole-z1.js +1 -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 +3237 -138
  154. package/app/globals.css +58 -7
  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-drop-shadow-test.mjs +86 -0
  161. package/scripts/topo-avatar-fallback-hover-test.mjs +104 -0
  162. package/scripts/topo-avatar-fallback-rotate-test.mjs +92 -0
  163. package/scripts/topo-avatar-rotate-test.mjs +85 -0
  164. package/scripts/topo-avatar-scale-test.mjs +89 -0
  165. package/scripts/topo-brand-drop-shadow-test.mjs +71 -0
  166. package/scripts/topo-brand-logo-breath-test.mjs +102 -0
  167. package/scripts/topo-brand-logo-hover-brightness-test.mjs +105 -0
  168. package/scripts/topo-brand-logo-hover-rotate-test.mjs +93 -0
  169. package/scripts/topo-brand-logo-hover-test.mjs +85 -0
  170. package/scripts/topo-chip-row-digit-ls-test.mjs +135 -0
  171. package/scripts/topo-chip-row-member-alias-lit-test.mjs +154 -0
  172. package/scripts/topo-chip-row-tier-glow-brightness-test.mjs +99 -0
  173. package/scripts/topo-chip-row-unit-hover-tracking-test.mjs +124 -0
  174. package/scripts/topo-cluster-count-attr-test.mjs +80 -0
  175. package/scripts/topo-crescent-breath-test.mjs +104 -0
  176. package/scripts/topo-crescent-recede-test.mjs +111 -0
  177. package/scripts/topo-edge-badge-circle-brightness-test.mjs +82 -0
  178. package/scripts/topo-edge-badge-hover-glow-test.mjs +90 -0
  179. package/scripts/topo-edge-badge-text-brightness-test.mjs +83 -0
  180. package/scripts/topo-edge-particle-brightness-test.mjs +82 -0
  181. package/scripts/topo-edge-pill-glow-test.mjs +67 -0
  182. package/scripts/topo-edge-visible-brightness-test.mjs +84 -0
  183. package/scripts/topo-endpoint-ring-brightness-test.mjs +83 -0
  184. package/scripts/topo-filter-pill-glow-test.mjs +90 -0
  185. package/scripts/topo-fleet-density-tier-test.mjs +84 -0
  186. package/scripts/topo-flow-rail-brightness-test.mjs +80 -0
  187. package/scripts/topo-freshness-chip-fade-test.mjs +105 -0
  188. package/scripts/topo-fullscreen-attr-test.mjs +73 -0
  189. package/scripts/topo-fullscreen-brightness-test.mjs +84 -0
  190. package/scripts/topo-fullscreen-icon-rotate-test.mjs +93 -0
  191. package/scripts/topo-grid-content-bottom-attr-test.mjs +72 -0
  192. package/scripts/topo-group-box-brightness-test.mjs +84 -0
  193. package/scripts/topo-group-label-brightness-test.mjs +84 -0
  194. package/scripts/topo-group-label-hover-glow-test.mjs +86 -0
  195. package/scripts/topo-group-label-member-alias-hover-test.mjs +125 -0
  196. package/scripts/topo-group-pill-glow-test.mjs +76 -0
  197. package/scripts/topo-hub-digit-brightness-test.mjs +79 -0
  198. package/scripts/topo-hub-digit-ls-test.mjs +119 -0
  199. package/scripts/topo-hub-halo-brightness-test.mjs +80 -0
  200. package/scripts/topo-hub-halo-glow-test.mjs +96 -0
  201. package/scripts/topo-hub-highlight-amplify-test.mjs +139 -0
  202. package/scripts/topo-hub-highlight-brightness-test.mjs +84 -0
  203. package/scripts/topo-hub-highlight-fill-transition-test.mjs +84 -0
  204. package/scripts/topo-hub-highlight-glow-test.mjs +99 -0
  205. package/scripts/topo-hub-highlight-r-test.mjs +112 -0
  206. package/scripts/topo-hub-highlight-recede-test.mjs +144 -0
  207. package/scripts/topo-hub-highlight-theme-fill-test.mjs +83 -0
  208. package/scripts/topo-hub-hover-ring-brightness-test.mjs +79 -0
  209. package/scripts/topo-hub-hover-ring-glow-test.mjs +97 -0
  210. package/scripts/topo-hub-idle-breath-test.mjs +7 -2
  211. package/scripts/topo-hub-recede-test.mjs +124 -0
  212. package/scripts/topo-hub-spoke-brightness-test.mjs +77 -0
  213. package/scripts/topo-hub-spoke-glow-test.mjs +112 -0
  214. package/scripts/topo-layout-hover-fw-test.mjs +98 -0
  215. package/scripts/topo-layout-toggle-brightness-test.mjs +94 -0
  216. package/scripts/topo-legend-count-brightness-test.mjs +80 -0
  217. package/scripts/topo-legend-count-letter-spacing-test.mjs +108 -0
  218. package/scripts/topo-legend-label-fw-test.mjs +107 -0
  219. package/scripts/topo-legend-pin-ring-brightness-test.mjs +82 -0
  220. package/scripts/topo-legend-row-count-brightness-test.mjs +85 -0
  221. package/scripts/topo-legend-row-label-glow-test.mjs +102 -0
  222. package/scripts/topo-legend-swatch-glow-test.mjs +109 -0
  223. package/scripts/topo-legend-swatch-member-alias-match-test.mjs +139 -0
  224. package/scripts/topo-minimap-hover-glow-test.mjs +109 -0
  225. package/scripts/topo-more-footer-brightness-test.mjs +94 -0
  226. package/scripts/topo-node-alias-brightness-test.mjs +84 -0
  227. package/scripts/topo-node-sub-text-brightness-test.mjs +88 -0
  228. package/scripts/topo-nodesize-brightness-test.mjs +82 -0
  229. package/scripts/topo-nodesize-hover-fw-test.mjs +99 -0
  230. package/scripts/topo-orphan-box-dash-test.mjs +89 -0
  231. package/scripts/topo-orphan-fill-opacity-test.mjs +91 -0
  232. package/scripts/topo-orphan-label-opacity-test.mjs +98 -0
  233. package/scripts/topo-panel-count-hover-ls-test.mjs +87 -0
  234. package/scripts/topo-panel-row-brightness-test.mjs +116 -0
  235. package/scripts/topo-panel-title-brightness-test.mjs +98 -0
  236. package/scripts/topo-panel-title-glow-test.mjs +111 -0
  237. package/scripts/topo-pill-x-rotate-test.mjs +96 -0
  238. package/scripts/topo-pinned-aspect-test.mjs +89 -0
  239. package/scripts/topo-pip-brightness-test.mjs +85 -0
  240. package/scripts/topo-pressure-seg-glow-test.mjs +92 -0
  241. package/scripts/topo-pressure-seg-member-alias-match-test.mjs +133 -0
  242. package/scripts/topo-pressure-seg-motion-test.mjs +101 -0
  243. package/scripts/topo-recent-count-brightness-test.mjs +84 -0
  244. package/scripts/topo-recent-more-fw-test.mjs +126 -0
  245. package/scripts/topo-recent-row-fw-test.mjs +115 -0
  246. package/scripts/topo-recent-row-text-glow-test.mjs +86 -0
  247. package/scripts/topo-recent-ts-brightness-test.mjs +86 -0
  248. package/scripts/topo-reduced-motion-attr-test.mjs +69 -0
  249. package/scripts/topo-reset-brightness-test.mjs +83 -0
  250. package/scripts/topo-reset-icon-hover-scale-test.mjs +102 -0
  251. package/scripts/topo-runtime-badge-brightness-test.mjs +78 -0
  252. package/scripts/topo-runtime-badge-glow-test.mjs +108 -0
  253. package/scripts/topo-runtime-badge-rotate-test.mjs +85 -0
  254. package/scripts/topo-starfield-hue-test.mjs +109 -0
  255. package/scripts/topo-status-ring-brightness-test.mjs +84 -0
  256. package/scripts/topo-titleblock-h2-hover-fw-test.mjs +109 -0
  257. package/scripts/topo-titleblock-h2-hover-tracking-test.mjs +128 -0
  258. package/scripts/topo-titleblock-kicker-hover-test.mjs +134 -0
  259. package/scripts/topo-vendor-chip-glow-test.mjs +97 -0
  260. package/scripts/topo-vendor-pill-glow-test.mjs +98 -0
  261. package/scripts/topo-watermark-breath-test.mjs +100 -0
  262. package/scripts/topo-watermark-recede-test.mjs +114 -0
  263. package/scripts/topo-zoom-buttons-brightness-test.mjs +94 -0
  264. package/scripts/topo-zoom-level-brightness-test.mjs +83 -0
  265. package/scripts/topo-zoom-level-color-test.mjs +105 -0
  266. package/.next/static/chunks/0.yvha74oimu5.js +0 -1
  267. package/.next/static/chunks/0brgpp3pfwrqa.js +0 -4
  268. package/.next/static/chunks/0m.1mvl~t.avc.css +0 -2
  269. package/.next/static/chunks/10x1zyfvy0f.8.js +0 -1
  270. /package/.next/static/{U1wWdzjMSApioHvZY8ULS → hgZRsyyYc0vdfgiTQsHgJ}/_buildManifest.js +0 -0
  271. /package/.next/static/{U1wWdzjMSApioHvZY8ULS → hgZRsyyYc0vdfgiTQsHgJ}/_clientMiddlewareManifest.js +0 -0
  272. /package/.next/static/{U1wWdzjMSApioHvZY8ULS → hgZRsyyYc0vdfgiTQsHgJ}/_ssgManifest.js +0 -0
@@ -0,0 +1,85 @@
1
+ /* Round 599 verification: runtime badge outer <g> gains
2
+ * hover-rotate-3 via CSS individual `rotate` property. 5th
3
+ * anchor in hover-rotate idiom (R350/R547/R549/R576/R599).
4
+ * Extends runtime badge hover signature to 6 axes.
5
+ *
6
+ * Test phases:
7
+ * 1. mock 2 idle nodes → runtime badges render
8
+ * 2. rest: rotate='0deg' (or 'none'), rotate-attr='0'
9
+ * 3. computed transition-property contains 'rotate'
10
+ * 4. transform-origin set to badge center coords
11
+ * 5. source: rotate conditional + data-attr + transition extension
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-rotate]', { timeout: 15000, state: 'attached' });
45
+ await page.waitForTimeout(500);
46
+
47
+ const rest = await page.evaluate(() => {
48
+ const el = document.querySelector('[data-runtime-badge-rotate]');
49
+ if (!el) return null;
50
+ const cs = getComputedStyle(el);
51
+ return {
52
+ rotate: cs.rotate,
53
+ transformOrigin: cs.transformOrigin,
54
+ transitionProperty: cs.transitionProperty,
55
+ rotateAttr: el.getAttribute('data-runtime-badge-rotate'),
56
+ glowAttr: el.getAttribute('data-runtime-badge-glow'),
57
+ };
58
+ });
59
+
60
+ await browser.close();
61
+
62
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
63
+ const sourceRotate = /rotate: isNodeActive \? '3deg' : '0deg'/.test(src);
64
+ const sourceAttr = /data-runtime-badge-rotate=\{isNodeActive \? '3' : '0'\}/.test(src);
65
+ const sourceTransformOrigin = /transformOrigin: `\$\{bx\}px \$\{by\}px`/.test(src);
66
+ const sourceTransition = /transition: 'filter 150ms ease-out, rotate 150ms ease-out'/.test(src);
67
+
68
+ const results = {
69
+ badge_present: !!rest,
70
+ // CSS rotate at rest = 'none' or '0deg' depending on browser
71
+ rest_rotate_zero: rest?.rotate === 'none' || rest?.rotate === '0deg',
72
+ rest_rotate_attr: rest?.rotateAttr === '0',
73
+ rest_glow_false: rest?.glowAttr === 'false',
74
+ has_transform_origin: !!rest?.transformOrigin && rest.transformOrigin !== '',
75
+ transition_has_rotate: /rotate/.test(rest?.transitionProperty || ''),
76
+ source_rotate: sourceRotate,
77
+ source_attr: sourceAttr,
78
+ source_transform_origin: sourceTransformOrigin,
79
+ source_transition: sourceTransition,
80
+ };
81
+ const ok = Object.values(results).every(Boolean);
82
+ console.log(`${ok ? '✅' : '❌'} R599 runtime-badge rotate (5th hover-rotate anchor, badge 6-axis):`,
83
+ JSON.stringify(results, null, 2),
84
+ `\n rest: ${JSON.stringify(rest)}`);
85
+ 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,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);
@@ -0,0 +1,128 @@
1
+ /* Round 554 verification: title-block H2 "Command mesh" gains
2
+ * group-hover:tracking-tighter via the wrapper's `group` flag.
3
+ * Hovering the brand logo OR the title text should tighten the
4
+ * H2's kerning from -0.025em → -0.05em.
5
+ *
6
+ * Tailwind tracking utilities map to letter-spacing CSS:
7
+ * tracking-tight -0.025em
8
+ * tracking-tighter -0.05em
9
+ *
10
+ * At text-lg (18px font-size):
11
+ * tracking-tight = -0.45px
12
+ * tracking-tighter = -0.9px
13
+ *
14
+ * Test phases:
15
+ * 1. rest: H2 letter-spacing ≈ -0.45px (tracking-tight)
16
+ * 2. hover brand logo: H2 letter-spacing ≈ -0.9px (tracking-tighter)
17
+ * Verifies group-hover propagation from sibling element.
18
+ * 3. hover H2 itself: letter-spacing ≈ -0.9px (same)
19
+ * 4. source: wrapper has `group`, H2 has group-hover:tracking-tighter
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: 1200 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try {
32
+ localStorage.setItem('anet-theme', 'cyber');
33
+ localStorage.setItem('anet-topo-layout', 'ring');
34
+ sessionStorage.setItem('anet_v3_auth', '1');
35
+ } catch {}
36
+ });
37
+ await ctx.route('**/api/hub/status*', async (route) => {
38
+ const r = await route.fetch();
39
+ const b = await r.json();
40
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
41
+ const mk = (alias) => ({
42
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
43
+ network_id: nid, project_dir: null,
44
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
45
+ });
46
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('a·1')] } });
47
+ });
48
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
49
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
50
+ const page = await ctx.newPage();
51
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
52
+ await page.waitForSelector('[data-topo-section-title]', { timeout: 15000 });
53
+ await page.waitForTimeout(500);
54
+
55
+ const h2Sel = '[data-topo-section-title]';
56
+ const logoSel = '[data-topo-brand-logo]';
57
+
58
+ const parsePx = (s) => parseFloat((s || '').replace(/px$/, ''));
59
+
60
+ // Phase 1: rest
61
+ const rest = await page.evaluate((s) => {
62
+ const el = document.querySelector(s);
63
+ if (!el) return null;
64
+ const cs = getComputedStyle(el);
65
+ return {
66
+ letterSpacing: cs.letterSpacing,
67
+ transitionProperty: cs.transitionProperty,
68
+ transitionDuration: cs.transitionDuration,
69
+ fontSize: cs.fontSize,
70
+ hoverAttr: el.getAttribute('data-topo-section-title-hover-tracking'),
71
+ };
72
+ }, h2Sel);
73
+
74
+ // Phase 2: hover brand logo (different element, but same group → H2 should tighten)
75
+ await page.hover(logoSel);
76
+ await page.waitForTimeout(400);
77
+ const hoverLogo = await page.evaluate((s) => {
78
+ const el = document.querySelector(s);
79
+ if (!el) return null;
80
+ const cs = getComputedStyle(el);
81
+ return { letterSpacing: cs.letterSpacing };
82
+ }, h2Sel);
83
+
84
+ // Phase 3: hover H2 itself (cross-confirm)
85
+ await page.mouse.move(0, 0);
86
+ await page.waitForTimeout(300);
87
+ await page.hover(h2Sel);
88
+ await page.waitForTimeout(400);
89
+ const hoverH2 = await page.evaluate((s) => {
90
+ const el = document.querySelector(s);
91
+ if (!el) return null;
92
+ const cs = getComputedStyle(el);
93
+ return { letterSpacing: cs.letterSpacing };
94
+ }, h2Sel);
95
+
96
+ await browser.close();
97
+
98
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
99
+ const sourceGroupWrapper = /<div className="group flex items-center gap-2\.5" data-topo-section-titleblock-group>/.test(src);
100
+ const sourceH2HoverTracking = /group-hover:tracking-tighter/.test(src);
101
+ const sourceH2HoverAttr = /data-topo-section-title-hover-tracking="tracking-tighter"/.test(src);
102
+
103
+ // Rest ≈ -0.45px (-0.025em × 18px), hover ≈ -0.9px (-0.05em × 18px)
104
+ const restPx = parsePx(rest?.letterSpacing);
105
+ const hoverLogoPx = parsePx(hoverLogo?.letterSpacing);
106
+ const hoverH2Px = parsePx(hoverH2?.letterSpacing);
107
+
108
+ const results = {
109
+ rest_attr_present: rest?.hoverAttr === 'tracking-tighter',
110
+ rest_tracking_tight: Math.abs(restPx - (-0.45)) < 0.05,
111
+ hover_via_logo_tightens: Math.abs(hoverLogoPx - (-0.9)) < 0.05,
112
+ hover_via_h2_tightens: Math.abs(hoverH2Px - (-0.9)) < 0.05,
113
+ hover_tighter_than_rest: hoverLogoPx < restPx - 0.3,
114
+ transition_has_letter_spacing: /letter-spacing/.test(rest?.transitionProperty || ''),
115
+ transition_duration_200ms: rest?.transitionDuration === '0.2s',
116
+ source_group_wrapper: sourceGroupWrapper,
117
+ source_h2_group_hover: sourceH2HoverTracking,
118
+ source_h2_attr: sourceH2HoverAttr,
119
+ };
120
+ const ok = Object.values(results).every(Boolean);
121
+ console.log(`${ok ? '✅' : '❌'} R554 title-block H2 group-hover:tracking-tighter:`,
122
+ JSON.stringify(results, null, 2),
123
+ `\n rest letter-spacing: ${rest?.letterSpacing} (expect ≈ -0.45px)`,
124
+ `\n hover-via-logo: ${hoverLogo?.letterSpacing} (expect ≈ -0.9px)`,
125
+ `\n hover-via-h2: ${hoverH2?.letterSpacing} (expect ≈ -0.9px)`,
126
+ `\n font-size: ${rest?.fontSize}`,
127
+ `\n transition-property: ${rest?.transitionProperty}`);
128
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,134 @@
1
+ /* Round 555 verification: title-block kicker "Network Topology"
2
+ * gains group-hover affordance via the R554 wrapper's `group` flag.
3
+ * Closes the title-block cluster's hover coverage at 3 surfaces
4
+ * (brand logo + H2 + kicker).
5
+ *
6
+ * Picks up small-label SPREAD direction (R554 idiom) — tracking-
7
+ * widest (0.1em) → 0.13em on hover, plus color lift text-gray-500
8
+ * #6b7280 → text-gray-400 #9ca3af.
9
+ *
10
+ * At text-xs (12px):
11
+ * tracking-widest = 1.2px per gap (rest)
12
+ * 0.13em = 1.56px per gap (hover)
13
+ *
14
+ * Test phases:
15
+ * 1. rest: letter-spacing ≈ 1.2px, color = gray-500
16
+ * 2. hover brand logo (sibling): kicker letter-spacing ≈ 1.56px,
17
+ * color = gray-400 (group-hover propagation)
18
+ * 3. source: kicker className contains group-hover utilities
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
25
+
26
+ const browser = await chromium.launch({ headless: true });
27
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
28
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
29
+ await ctx.addInitScript(() => {
30
+ try {
31
+ localStorage.setItem('anet-theme', 'cyber');
32
+ localStorage.setItem('anet-topo-layout', 'ring');
33
+ sessionStorage.setItem('anet_v3_auth', '1');
34
+ } 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) => ({
41
+ alias, status: 'idle', 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·1')] } });
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('[data-topo-section-kicker]', { timeout: 15000 });
52
+ await page.waitForTimeout(500);
53
+
54
+ const kickerSel = '[data-topo-section-kicker]';
55
+ const logoSel = '[data-topo-brand-logo]';
56
+
57
+ const parsePx = (s) => parseFloat((s || '').replace(/px$/, ''));
58
+ const parseLab = (s) => {
59
+ const m = (s || '').match(/lab\(([0-9.]+)\s/) || (s || '').match(/oklab\(([0-9.]+)\s/);
60
+ return m ? parseFloat(m[1]) : NaN;
61
+ };
62
+
63
+ // Phase 1: rest
64
+ const rest = await page.evaluate((s) => {
65
+ const el = document.querySelector(s);
66
+ if (!el) return null;
67
+ const cs = getComputedStyle(el);
68
+ return {
69
+ letterSpacing: cs.letterSpacing,
70
+ color: cs.color,
71
+ transitionProperty: cs.transitionProperty,
72
+ transitionDuration: cs.transitionDuration,
73
+ fontSize: cs.fontSize,
74
+ trackingAttr: el.getAttribute('data-topo-section-kicker-hover-tracking'),
75
+ colorAttr: el.getAttribute('data-topo-section-kicker-hover-color'),
76
+ };
77
+ }, kickerSel);
78
+
79
+ // Phase 2: hover brand logo (sibling element) → group-hover propagation
80
+ await page.hover(logoSel);
81
+ await page.waitForTimeout(400);
82
+ const hoverLogo = await page.evaluate((s) => {
83
+ const el = document.querySelector(s);
84
+ if (!el) return null;
85
+ const cs = getComputedStyle(el);
86
+ return { letterSpacing: cs.letterSpacing, color: cs.color };
87
+ }, kickerSel);
88
+
89
+ await browser.close();
90
+
91
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
92
+ const sourceKickerWired = /group-hover:text-gray-400/.test(src) &&
93
+ /group-hover:tracking-\[0\.13em\]/.test(src);
94
+ const sourceTransition = /transition-\[letter-spacing,color\] duration-200 ease-out/.test(src);
95
+ const sourceTrackingAttr = /data-topo-section-kicker-hover-tracking="0\.13em"/.test(src);
96
+ const sourceColorAttr = /data-topo-section-kicker-hover-color="text-gray-400"/.test(src);
97
+
98
+ // Rest: tracking-widest 0.1em × 12px = 1.2px
99
+ // Hover: 0.13em × 12px = 1.56px
100
+ const restPx = parsePx(rest?.letterSpacing);
101
+ const hoverPx = parsePx(hoverLogo?.letterSpacing);
102
+
103
+ // Color: rest gray-500 ≈ #6b7280; hover gray-400 ≈ #9ca3af (lighter).
104
+ // Tailwind v4 may emit in lab/oklab; check that hover L > rest L.
105
+ const restL = parseLab(rest?.color);
106
+ const hoverL = parseLab(hoverLogo?.color);
107
+ // Also handle the legacy rgb() case in case it's emitted that way
108
+ const colorVaries = rest?.color !== hoverLogo?.color;
109
+
110
+ const results = {
111
+ rest_tracking_widest_1_2px: Math.abs(restPx - 1.2) < 0.1,
112
+ hover_tracking_0_13em: Math.abs(hoverPx - 1.56) < 0.1,
113
+ hover_spreads_vs_rest: hoverPx > restPx + 0.2,
114
+ color_varies_on_hover: colorVaries,
115
+ hover_color_lighter: !Number.isNaN(restL) && !Number.isNaN(hoverL) ? (hoverL > restL + 5) : true,
116
+ rest_tracking_attr: rest?.trackingAttr === '0.13em',
117
+ rest_color_attr: rest?.colorAttr === 'text-gray-400',
118
+ transition_has_ls_color: /letter-spacing/.test(rest?.transitionProperty || '') && /color/.test(rest?.transitionProperty || ''),
119
+ transition_duration_200ms: rest?.transitionDuration === '0.2s',
120
+ source_kicker_wired: sourceKickerWired,
121
+ source_transition: sourceTransition,
122
+ source_tracking_attr: sourceTrackingAttr,
123
+ source_color_attr: sourceColorAttr,
124
+ };
125
+ const ok = Object.values(results).every(Boolean);
126
+ console.log(`${ok ? '✅' : '❌'} R555 title-block kicker group-hover (spread + color lift):`,
127
+ JSON.stringify(results, null, 2),
128
+ `\n rest letter-spacing: ${rest?.letterSpacing} (expect ≈ 1.2px)`,
129
+ `\n hover letter-spacing: ${hoverLogo?.letterSpacing} (expect ≈ 1.56px)`,
130
+ `\n rest color: ${rest?.color}`,
131
+ `\n hover color: ${hoverLogo?.color}`,
132
+ `\n font-size: ${rest?.fontSize}`,
133
+ `\n transition-property: ${rest?.transitionProperty}`);
134
+ process.exit(ok ? 0 : 1);