@sleep2agi/agent-network-dashboard 0.5.3-preview.9 → 0.5.3-preview.91

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 (244) 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/00spyt85k-ze4.js +1 -0
  145. package/.next/static/chunks/06rtmmx.6i6n1.js +4 -0
  146. package/.next/static/chunks/{00d~vl~k~7f75.js → 0gqcmj8vinghl.js} +1 -1
  147. package/.next/static/chunks/{03a4--7ncekmk.js → 0v4-5tng.uh.7.js} +2 -2
  148. package/.next/static/chunks/0v~hxo-3kh3jo.js +1 -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 +2460 -121
  154. package/app/globals.css +81 -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-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-unit-hover-tracking-test.mjs +124 -0
  168. package/scripts/topo-cluster-count-attr-test.mjs +80 -0
  169. package/scripts/topo-crescent-breath-test.mjs +104 -0
  170. package/scripts/topo-crescent-recede-test.mjs +111 -0
  171. package/scripts/topo-edge-badge-hover-glow-test.mjs +90 -0
  172. package/scripts/topo-edge-badge-text-brightness-test.mjs +83 -0
  173. package/scripts/topo-edge-pill-glow-test.mjs +67 -0
  174. package/scripts/topo-filter-pill-glow-test.mjs +90 -0
  175. package/scripts/topo-fleet-density-tier-test.mjs +84 -0
  176. package/scripts/topo-freshness-chip-fade-test.mjs +105 -0
  177. package/scripts/topo-fullscreen-attr-test.mjs +73 -0
  178. package/scripts/topo-fullscreen-icon-rotate-test.mjs +93 -0
  179. package/scripts/topo-grid-content-bottom-attr-test.mjs +72 -0
  180. package/scripts/topo-group-label-brightness-test.mjs +84 -0
  181. package/scripts/topo-group-label-hover-glow-test.mjs +86 -0
  182. package/scripts/topo-group-label-member-alias-hover-test.mjs +125 -0
  183. package/scripts/topo-group-pill-glow-test.mjs +76 -0
  184. package/scripts/topo-hub-digit-brightness-test.mjs +79 -0
  185. package/scripts/topo-hub-digit-ls-test.mjs +119 -0
  186. package/scripts/topo-hub-halo-brightness-test.mjs +80 -0
  187. package/scripts/topo-hub-halo-glow-test.mjs +96 -0
  188. package/scripts/topo-hub-highlight-amplify-test.mjs +139 -0
  189. package/scripts/topo-hub-highlight-brightness-test.mjs +84 -0
  190. package/scripts/topo-hub-highlight-fill-transition-test.mjs +84 -0
  191. package/scripts/topo-hub-highlight-glow-test.mjs +99 -0
  192. package/scripts/topo-hub-highlight-r-test.mjs +112 -0
  193. package/scripts/topo-hub-highlight-recede-test.mjs +144 -0
  194. package/scripts/topo-hub-highlight-theme-fill-test.mjs +83 -0
  195. package/scripts/topo-hub-hover-ring-glow-test.mjs +97 -0
  196. package/scripts/topo-hub-idle-breath-test.mjs +109 -0
  197. package/scripts/topo-hub-recede-test.mjs +124 -0
  198. package/scripts/topo-hub-spoke-glow-test.mjs +112 -0
  199. package/scripts/topo-layout-hover-fw-test.mjs +98 -0
  200. package/scripts/topo-legend-count-letter-spacing-test.mjs +108 -0
  201. package/scripts/topo-legend-label-fw-test.mjs +107 -0
  202. package/scripts/topo-legend-row-label-glow-test.mjs +102 -0
  203. package/scripts/topo-legend-swatch-glow-test.mjs +109 -0
  204. package/scripts/topo-legend-swatch-member-alias-match-test.mjs +139 -0
  205. package/scripts/topo-minimap-hover-glow-test.mjs +109 -0
  206. package/scripts/topo-node-alias-brightness-test.mjs +84 -0
  207. package/scripts/topo-node-sub-text-brightness-test.mjs +88 -0
  208. package/scripts/topo-nodesize-hover-fw-test.mjs +99 -0
  209. package/scripts/topo-orphan-box-dash-test.mjs +89 -0
  210. package/scripts/topo-orphan-fill-opacity-test.mjs +91 -0
  211. package/scripts/topo-orphan-label-italic-test.mjs +90 -0
  212. package/scripts/topo-orphan-label-opacity-test.mjs +98 -0
  213. package/scripts/topo-panel-count-hover-ls-test.mjs +87 -0
  214. package/scripts/topo-panel-row-brightness-test.mjs +116 -0
  215. package/scripts/topo-panel-title-brightness-test.mjs +98 -0
  216. package/scripts/topo-panel-title-glow-test.mjs +111 -0
  217. package/scripts/topo-pill-x-rotate-test.mjs +96 -0
  218. package/scripts/topo-pinned-aspect-test.mjs +89 -0
  219. package/scripts/topo-pressure-seg-glow-test.mjs +92 -0
  220. package/scripts/topo-pressure-seg-member-alias-match-test.mjs +133 -0
  221. package/scripts/topo-pressure-seg-motion-test.mjs +101 -0
  222. package/scripts/topo-recent-hot-pulse-test.mjs +102 -0
  223. package/scripts/topo-recent-more-fw-test.mjs +126 -0
  224. package/scripts/topo-recent-row-fw-test.mjs +115 -0
  225. package/scripts/topo-recent-row-text-glow-test.mjs +86 -0
  226. package/scripts/topo-reduced-motion-attr-test.mjs +69 -0
  227. package/scripts/topo-reset-icon-hover-scale-test.mjs +102 -0
  228. package/scripts/topo-runtime-badge-glow-test.mjs +108 -0
  229. package/scripts/topo-starfield-hue-test.mjs +109 -0
  230. package/scripts/topo-titleblock-h2-hover-fw-test.mjs +109 -0
  231. package/scripts/topo-titleblock-h2-hover-tracking-test.mjs +128 -0
  232. package/scripts/topo-titleblock-kicker-hover-test.mjs +134 -0
  233. package/scripts/topo-vendor-chip-glow-test.mjs +97 -0
  234. package/scripts/topo-vendor-pill-glow-test.mjs +98 -0
  235. package/scripts/topo-watermark-breath-test.mjs +100 -0
  236. package/scripts/topo-watermark-recede-test.mjs +114 -0
  237. package/scripts/topo-zoom-level-color-test.mjs +105 -0
  238. package/.next/static/chunks/0hndl9yzpqajt.css +0 -2
  239. package/.next/static/chunks/0jn4lbsj97vfl.js +0 -1
  240. package/.next/static/chunks/0rz3-6.xs78~d.js +0 -1
  241. package/.next/static/chunks/0~~xbw42y4m3v.js +0 -4
  242. /package/.next/static/{nbpD7Lhttq59fvvIw9gTX → hCtjbS0oHsw_JU2q5cp-3}/_buildManifest.js +0 -0
  243. /package/.next/static/{nbpD7Lhttq59fvvIw9gTX → hCtjbS0oHsw_JU2q5cp-3}/_clientMiddlewareManifest.js +0 -0
  244. /package/.next/static/{nbpD7Lhttq59fvvIw9gTX → hCtjbS0oHsw_JU2q5cp-3}/_ssgManifest.js +0 -0
@@ -0,0 +1,104 @@
1
+ /* Round 558 verification: per-node avatar hover-brightness family
2
+ * closes at 3 anchors. R501 already covers the vendor.logo image
3
+ * branch; R558 adds the monogram + prefix-group fallback branches.
4
+ *
5
+ * Mock with claude-code-cli runtime and no recognized vendor model
6
+ * → triggers the prefix-group fallback branch (hue-hashed initial).
7
+ *
8
+ * Test phases:
9
+ * 1. wait for [data-node-avatar-fallback] to render
10
+ * 2. rest filter = 'none', hovered attr = 'false'
11
+ * 3. hover the node → filter contains 'brightness(1.15)',
12
+ * hovered attr = 'true'
13
+ * 4. source-side regex confirms both fallback branches 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
+ const browser = await chromium.launch({ headless: true });
22
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
23
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
24
+ await ctx.addInitScript(() => {
25
+ try {
26
+ localStorage.setItem('anet-theme', 'cyber');
27
+ localStorage.setItem('anet-topo-layout', 'ring');
28
+ sessionStorage.setItem('anet_v3_auth', '1');
29
+ } catch {}
30
+ });
31
+ // model=null → vendor=unknown → prefix-group fallback branch.
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: null, runtime: null,
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('foo·1'), mk('foo·2')] } });
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('[data-node-avatar-fallback="foo·1"]', { timeout: 15000 });
48
+ await page.waitForTimeout(500);
49
+
50
+ const avatarSel = '[data-node-avatar-fallback="foo·1"]';
51
+ const nodeSel = 'g[data-node="foo·1"]';
52
+
53
+ const rest = await page.evaluate((s) => {
54
+ const el = document.querySelector(s);
55
+ if (!el) return null;
56
+ const cs = getComputedStyle(el);
57
+ return {
58
+ filter: cs.filter,
59
+ transitionProperty: cs.transitionProperty,
60
+ transitionDuration: cs.transitionDuration,
61
+ hoveredAttr: el.getAttribute('data-node-avatar-fallback-hovered'),
62
+ };
63
+ }, avatarSel);
64
+
65
+ // Hover the parent node group (the avatar is pointerEvents:none inside,
66
+ // but the node group handles hover; node-hovered state is what we test).
67
+ await page.hover(nodeSel);
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 {
74
+ filter: cs.filter,
75
+ hoveredAttr: el.getAttribute('data-node-avatar-fallback-hovered'),
76
+ };
77
+ }, avatarSel);
78
+
79
+ await browser.close();
80
+
81
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
82
+ const sourceMonogramG = /data-node-avatar-monogram-hovered=\{isAvatarFallbackHovered/.test(src);
83
+ const sourceFallbackG = /data-node-avatar-fallback-hovered=\{isAvatarFallbackHovered/.test(src);
84
+ const sourceBrightnessExpr = /filter: isAvatarFallbackHovered \? 'brightness\(1\.15\)' : undefined/.test(src);
85
+ const sourceTransitionExpr = /transition: 'filter 200ms ease-out'/.test(src);
86
+
87
+ const results = {
88
+ rest_filter_none: rest?.filter === 'none',
89
+ rest_hovered_false: rest?.hoveredAttr === 'false',
90
+ hover_filter_brightness: /brightness\(1\.15\)/.test(hover?.filter || ''),
91
+ hover_hovered_true: hover?.hoveredAttr === 'true',
92
+ transition_filter: /filter/.test(rest?.transitionProperty || ''),
93
+ transition_duration: rest?.transitionDuration === '0.2s',
94
+ source_monogram_group: sourceMonogramG,
95
+ source_fallback_group: sourceFallbackG,
96
+ source_brightness_expr: sourceBrightnessExpr,
97
+ source_transition_expr: sourceTransitionExpr,
98
+ };
99
+ const ok = Object.values(results).every(Boolean);
100
+ console.log(`${ok ? '✅' : '❌'} R558 per-node avatar fallback hover-brightness (closes 3-anchor family):`,
101
+ JSON.stringify(results, null, 2),
102
+ `\n rest: ${JSON.stringify(rest)}`,
103
+ `\n hover: ${JSON.stringify(hover)}`);
104
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,102 @@
1
+ /* Round 553 verification: title-block brand logo gains subtle idle
2
+ * opacity breath (0.92 ↔ 1, 5s cycle). 5th anchor in the 呼吸感
3
+ * breath family.
4
+ *
5
+ * Test phases:
6
+ * 1. brand-logo className contains 'anet-topo-brand-logo-breath'
7
+ * 2. data-topo-brand-logo-breath="true" attr present
8
+ * 3. computed animation-name === 'anet-topo-brand-logo-breath-kf'
9
+ * (paused or running)
10
+ * 4. computed animation-duration === '5s'
11
+ * 5. source-side regex confirms keyframe + class + componentJSX
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')] } });
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-topo-brand-logo]', { timeout: 15000 });
45
+ await page.waitForTimeout(500);
46
+
47
+ const sel = '[data-topo-brand-logo]';
48
+ const probe = await page.evaluate((s) => {
49
+ const el = document.querySelector(s);
50
+ if (!el) return null;
51
+ const cs = getComputedStyle(el);
52
+ return {
53
+ breathAttr: el.getAttribute('data-topo-brand-logo-breath'),
54
+ hasBreathClass: el.classList.contains('anet-topo-brand-logo-breath'),
55
+ animationName: cs.animationName,
56
+ animationDuration: cs.animationDuration,
57
+ animationIterationCount: cs.animationIterationCount,
58
+ animationTimingFunction: cs.animationTimingFunction,
59
+ // Sample opacity twice across ~1.5s to confirm it's actually changing
60
+ opacityNow: parseFloat(cs.opacity),
61
+ };
62
+ }, sel);
63
+
64
+ // Sample opacity at 3 timepoints across the 5s breath cycle
65
+ await page.waitForTimeout(500);
66
+ const opacity_t1 = await page.evaluate((s) => parseFloat(getComputedStyle(document.querySelector(s)).opacity), sel);
67
+ await page.waitForTimeout(900);
68
+ const opacity_t2 = await page.evaluate((s) => parseFloat(getComputedStyle(document.querySelector(s)).opacity), sel);
69
+ await page.waitForTimeout(900);
70
+ const opacity_t3 = await page.evaluate((s) => parseFloat(getComputedStyle(document.querySelector(s)).opacity), sel);
71
+
72
+ await browser.close();
73
+
74
+ const css = readFileSync('/home/vansin/agent-network-dashboard/app/globals.css', 'utf8');
75
+ const tsx = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
76
+ const sourceKeyframe = /@keyframes anet-topo-brand-logo-breath-kf/.test(css);
77
+ const sourceClass = /\.anet-topo-brand-logo-breath\s*\{[^}]*animation: anet-topo-brand-logo-breath-kf 5s ease-in-out infinite/.test(css);
78
+ const sourceComponent = /\!reducedMotion \? ' anet-topo-brand-logo-breath' : ''/.test(tsx);
79
+ const sourceAttr = /data-topo-brand-logo-breath=\{!reducedMotion \? 'true' : 'false'\}/.test(tsx);
80
+
81
+ // Opacity should oscillate; not all 3 samples identical
82
+ const opacityVaries = new Set([opacity_t1, opacity_t2, opacity_t3].map(o => o.toFixed(3))).size > 1;
83
+
84
+ const results = {
85
+ breath_attr: probe?.breathAttr === 'true',
86
+ has_breath_class: probe?.hasBreathClass === true,
87
+ animation_name_match: probe?.animationName === 'anet-topo-brand-logo-breath-kf',
88
+ animation_duration_5s: probe?.animationDuration === '5s',
89
+ animation_iteration_infinite: probe?.animationIterationCount === 'infinite',
90
+ animation_timing_ease_in_out: probe?.animationTimingFunction === 'ease-in-out',
91
+ opacity_varies_over_time: opacityVaries,
92
+ source_keyframe: sourceKeyframe,
93
+ source_class: sourceClass,
94
+ source_component: sourceComponent,
95
+ source_attr: sourceAttr,
96
+ };
97
+ const ok = Object.values(results).every(Boolean);
98
+ console.log(`${ok ? '✅' : '❌'} R553 brand-logo idle breath (5th anchor in 呼吸感 family):`,
99
+ JSON.stringify(results, null, 2),
100
+ '\n probe:', JSON.stringify(probe),
101
+ `\n opacity samples: ${opacity_t1.toFixed(3)} → ${opacity_t2.toFixed(3)} → ${opacity_t3.toFixed(3)}`);
102
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,105 @@
1
+ /* Round 557 verification: title-block brand logo gains 4th hover
2
+ * axis — hover:brightness-110 (filter). Composes with R548 scale +
3
+ * R549 rotate + R553 idle breath.
4
+ *
5
+ * Test phases:
6
+ * 1. rest: filter = 'none' (no brightness on idle, breath uses opacity)
7
+ * 2. hover: filter contains 'brightness(1.1)' (or similar)
8
+ * 3. data-attr present
9
+ * 4. source: className contains hover:brightness-110 and
10
+ * transition-[transform,filter]
11
+ *
12
+ * R553 breath uses opacity, NOT filter — so filter stays clean at
13
+ * rest. On hover, Tailwind adds filter: brightness(1.1).
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
+ const browser = await chromium.launch({ headless: true });
22
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
23
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
24
+ await ctx.addInitScript(() => {
25
+ try {
26
+ localStorage.setItem('anet-theme', 'cyber');
27
+ localStorage.setItem('anet-topo-layout', 'ring');
28
+ sessionStorage.setItem('anet_v3_auth', '1');
29
+ } catch {}
30
+ });
31
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ const mk = (alias) => ({
36
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
37
+ network_id: nid, project_dir: null,
38
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
39
+ });
40
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('a·1')] } });
41
+ });
42
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
46
+ await page.waitForSelector('[data-topo-brand-logo]', { timeout: 15000 });
47
+ await page.waitForTimeout(500);
48
+
49
+ const sel = '[data-topo-brand-logo]';
50
+ const rest = await page.evaluate((s) => {
51
+ const el = document.querySelector(s);
52
+ if (!el) return null;
53
+ const cs = getComputedStyle(el);
54
+ return {
55
+ filter: cs.filter,
56
+ transitionProperty: cs.transitionProperty,
57
+ transitionDuration: cs.transitionDuration,
58
+ hoverBrightnessAttr: el.getAttribute('data-topo-brand-logo-hover-brightness'),
59
+ };
60
+ }, sel);
61
+
62
+ await page.hover(sel);
63
+ await page.waitForTimeout(400);
64
+ const hover = await page.evaluate((s) => {
65
+ const el = document.querySelector(s);
66
+ if (!el) return null;
67
+ const cs = getComputedStyle(el);
68
+ return {
69
+ filter: cs.filter,
70
+ scale: cs.scale,
71
+ rotate: cs.rotate,
72
+ };
73
+ }, sel);
74
+
75
+ await browser.close();
76
+
77
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
78
+ const sourceClassname = /hover:brightness-110/.test(src);
79
+ const sourceTransition = /transition-\[transform,filter\] duration-200 ease-out hover:scale-105 hover:rotate-6 hover:brightness-110/.test(src);
80
+ const sourceAttr = /data-topo-brand-logo-hover-brightness="1\.1"/.test(src);
81
+
82
+ const results = {
83
+ rest_filter_none: rest?.filter === 'none',
84
+ rest_brightness_attr: rest?.hoverBrightnessAttr === '1.1',
85
+ // Computed filter on hover should contain brightness(1.1) — handle
86
+ // both raw and serialized forms.
87
+ hover_filter_brightness: /brightness\(1\.1\)/.test(hover?.filter || ''),
88
+ hover_scale_still_105: hover?.scale === '1.05',
89
+ hover_rotate_still_6: hover?.rotate === '6deg',
90
+ transition_has_transform: /transform/.test(rest?.transitionProperty || ''),
91
+ transition_has_filter: /filter/.test(rest?.transitionProperty || ''),
92
+ // 3 axes (color + transform + filter) → '0.2s, 0.2s, 0.2s'
93
+ transition_duration: /^0\.2s(,\s*0\.2s)*$/.test(rest?.transitionDuration || ''),
94
+ source_classname: sourceClassname,
95
+ source_transition_list: sourceTransition,
96
+ source_attr: sourceAttr,
97
+ };
98
+ const ok = Object.values(results).every(Boolean);
99
+ console.log(`${ok ? '✅' : '❌'} R557 brand-logo hover:brightness-110 (4th axis):`,
100
+ JSON.stringify(results, null, 2),
101
+ `\n rest filter: ${rest?.filter}`,
102
+ `\n hover filter: ${hover?.filter}`,
103
+ `\n hover scale: ${hover?.scale}, rotate: ${hover?.rotate}`,
104
+ `\n transition: ${rest?.transitionProperty}`);
105
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,93 @@
1
+ /* Round 549 verification: title-block brand logo gains hover:rotate-6
2
+ * paired with R548's hover:scale-105. Banked R547 Tailwind 4 pattern —
3
+ * probe BOTH cs.scale AND cs.rotate (individual CSS properties, not
4
+ * combined transform).
5
+ *
6
+ * Test phases:
7
+ * 1. rest: scale='none' (or '1'), rotate='none' (or '0deg')
8
+ * 2. hover: scale='1.05', rotate='6deg'
9
+ * 3. source-side regex confirms className + data-attr wired
10
+ */
11
+ import { chromium } from 'playwright';
12
+ import { readFileSync } from 'node:fs';
13
+
14
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
15
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
16
+
17
+ const browser = await chromium.launch({ headless: true });
18
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try {
22
+ localStorage.setItem('anet-theme', 'cyber');
23
+ localStorage.setItem('anet-topo-layout', 'ring');
24
+ sessionStorage.setItem('anet_v3_auth', '1');
25
+ } catch {}
26
+ });
27
+ await ctx.route('**/api/hub/status*', async (route) => {
28
+ const r = await route.fetch();
29
+ const b = await r.json();
30
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
31
+ const mk = (alias) => ({
32
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
33
+ network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ });
36
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('a·1')] } });
37
+ });
38
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
39
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
40
+ const page = await ctx.newPage();
41
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
42
+ await page.waitForSelector('[data-topo-brand-logo]', { timeout: 15000 });
43
+ await page.waitForTimeout(500);
44
+
45
+ const sel = '[data-topo-brand-logo]';
46
+ const rest = await page.evaluate((s) => {
47
+ const el = document.querySelector(s);
48
+ if (!el) return null;
49
+ const cs = getComputedStyle(el);
50
+ return {
51
+ attrScale: el.getAttribute('data-topo-brand-logo-hover-scale'),
52
+ attrRotate: el.getAttribute('data-topo-brand-logo-hover-rotate'),
53
+ scale: cs.scale,
54
+ rotate: cs.rotate,
55
+ };
56
+ }, sel);
57
+
58
+ await page.hover(sel);
59
+ await page.waitForTimeout(400);
60
+ const hover = await page.evaluate((s) => {
61
+ const el = document.querySelector(s);
62
+ if (!el) return null;
63
+ const cs = getComputedStyle(el);
64
+ return { scale: cs.scale, rotate: cs.rotate };
65
+ }, sel);
66
+
67
+ await browser.close();
68
+
69
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
70
+ const sourceClassNameWired =
71
+ /className="shrink-0 transition-transform duration-200 ease-out hover:scale-105 hover:rotate-6 transform-gpu"/.test(src);
72
+ const sourceScaleAttrWired =
73
+ /data-topo-brand-logo-hover-scale="1\.05"/.test(src);
74
+ const sourceRotateAttrWired =
75
+ /data-topo-brand-logo-hover-rotate="6deg"/.test(src);
76
+
77
+ const results = {
78
+ rest_scale_idle: rest?.scale === 'none' || rest?.scale === '1',
79
+ rest_rotate_idle: rest?.rotate === 'none' || rest?.rotate === '0deg',
80
+ rest_attr_scale_105: rest?.attrScale === '1.05',
81
+ rest_attr_rotate_6deg: rest?.attrRotate === '6deg',
82
+ hover_scale_105: hover?.scale === '1.05',
83
+ hover_rotate_6deg: hover?.rotate === '6deg',
84
+ source_classname: sourceClassNameWired,
85
+ source_scale_attr: sourceScaleAttrWired,
86
+ source_rotate_attr: sourceRotateAttrWired,
87
+ };
88
+ const ok = Object.values(results).every(Boolean);
89
+ console.log(`${ok ? '✅' : '❌'} R549 brand-logo hover-rotate-6 (paired with R548 scale-105):`,
90
+ JSON.stringify(results, null, 2),
91
+ '\n rest:', JSON.stringify(rest),
92
+ '\n hover:', JSON.stringify(hover));
93
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,85 @@
1
+ /* Round 548 verification: title-block brand logo gains hover:scale-105
2
+ * delight gesture. Banked R547 Tailwind 4 pattern — probe cs.scale not
3
+ * cs.transform.
4
+ *
5
+ * Test phases:
6
+ * 1. rest: scale='none'
7
+ * 2. hover: scale='1.05'
8
+ * 3. source-side regex confirms className + data-attr wired
9
+ */
10
+ import { chromium } from 'playwright';
11
+ import { readFileSync } from 'node:fs';
12
+
13
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
14
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
15
+
16
+ const browser = await chromium.launch({ headless: true });
17
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
18
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
19
+ await ctx.addInitScript(() => {
20
+ try {
21
+ localStorage.setItem('anet-theme', 'cyber');
22
+ localStorage.setItem('anet-topo-layout', 'ring');
23
+ sessionStorage.setItem('anet_v3_auth', '1');
24
+ } catch {}
25
+ });
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ const r = await route.fetch();
28
+ const b = await r.json();
29
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
30
+ const mk = (alias) => ({
31
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
32
+ network_id: nid, project_dir: null,
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
34
+ });
35
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('a·1')] } });
36
+ });
37
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
38
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
39
+ const page = await ctx.newPage();
40
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
41
+ await page.waitForSelector('[data-topo-brand-logo]', { timeout: 15000 });
42
+ await page.waitForTimeout(500);
43
+
44
+ const sel = '[data-topo-brand-logo]';
45
+ const rest = await page.evaluate((s) => {
46
+ const el = document.querySelector(s);
47
+ if (!el) return null;
48
+ const cs = getComputedStyle(el);
49
+ return {
50
+ attrScale: el.getAttribute('data-topo-brand-logo-hover-scale'),
51
+ scale: cs.scale,
52
+ rotate: cs.rotate,
53
+ };
54
+ }, sel);
55
+
56
+ await page.hover(sel);
57
+ await page.waitForTimeout(400);
58
+ const hover = await page.evaluate((s) => {
59
+ const el = document.querySelector(s);
60
+ if (!el) return null;
61
+ const cs = getComputedStyle(el);
62
+ return { scale: cs.scale };
63
+ }, sel);
64
+
65
+ await browser.close();
66
+
67
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
68
+ const sourceClassNameWired =
69
+ /className="shrink-0 transition-transform duration-200 ease-out hover:scale-105 transform-gpu"/.test(src);
70
+ const sourceAttrWired =
71
+ /data-topo-brand-logo-hover-scale="1\.05"/.test(src);
72
+
73
+ const results = {
74
+ rest_scale_none: rest?.scale === 'none' || rest?.scale === '1',
75
+ rest_attr_scale_105: rest?.attrScale === '1.05',
76
+ hover_scale_105: hover?.scale === '1.05',
77
+ source_classname: sourceClassNameWired,
78
+ source_attr: sourceAttrWired,
79
+ };
80
+ const ok = Object.values(results).every(Boolean);
81
+ console.log(`${ok ? '✅' : '❌'} R548 brand-logo hover-scale:`,
82
+ JSON.stringify(results, null, 2),
83
+ '\n rest:', JSON.stringify(rest),
84
+ '\n hover:', JSON.stringify(hover));
85
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,135 @@
1
+ /* Round 539 verification: chip-row digit (working/online/active-links)
2
+ * gains group-hover:tracking-wide alongside existing R362 group-hover:
3
+ * font-bold. Hover-letter-spacing family 12th anchor across 3 siblings.
4
+ *
5
+ * Test phases:
6
+ * 1. rest each chip-digit: computed letterSpacing='normal' (=0)
7
+ * 2. hover the chip's parent (has `group` class — sets group-hover):
8
+ * digit's letterSpacing = ~0.025em
9
+ * 3. source-side regex confirms all 3 digits have the new class
10
+ */
11
+ import { chromium } from 'playwright';
12
+ import { readFileSync } from 'node:fs';
13
+
14
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
15
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
16
+
17
+ const browser = await chromium.launch({ headless: true });
18
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try {
22
+ localStorage.setItem('anet-theme', 'cyber');
23
+ localStorage.setItem('anet-topo-layout', 'ring');
24
+ sessionStorage.setItem('anet_v3_auth', '1');
25
+ } catch {}
26
+ });
27
+ await ctx.route('**/api/hub/status*', async (route) => {
28
+ const r = await route.fetch();
29
+ const b = await r.json();
30
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
31
+ const mk = (alias, status) => ({
32
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
33
+ network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ });
36
+ await route.fulfill({ response: r, json: { ...b, sessions: [
37
+ mk('a·1', 'working'), mk('a·2', 'idle'),
38
+ ] } });
39
+ });
40
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: {
41
+ messages: [{ id: 'm1', from_alias: 'a·1', to_alias: 'a·2', content: 't', created_at: fresh }]
42
+ } }));
43
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
46
+ await page.waitForSelector('[data-working-chip-digit]', { timeout: 15000 });
47
+ await page.waitForTimeout(800);
48
+
49
+ async function probe(digitSel, chipParentSel) {
50
+ const rest = await page.evaluate((s) => {
51
+ const el = document.querySelector(s);
52
+ if (!el) return null;
53
+ return { ls: getComputedStyle(el).letterSpacing };
54
+ }, digitSel);
55
+ await page.hover(chipParentSel);
56
+ await page.waitForTimeout(350);
57
+ const hover = await page.evaluate((s) => {
58
+ const el = document.querySelector(s);
59
+ if (!el) return null;
60
+ return { ls: getComputedStyle(el).letterSpacing };
61
+ }, digitSel);
62
+ // move pointer away for next probe
63
+ await page.mouse.move(50, 50);
64
+ await page.waitForTimeout(200);
65
+ return { rest, hover };
66
+ }
67
+
68
+ // The chip-row chip <span class="group ...">; each has parent with `group`.
69
+ // Hover the chip's parent span — its child digit gets group-hover effects.
70
+ // The chip parent is the closest ancestor with `group` class. Easier: find
71
+ // the digit and walk up to nearest `.group` element via JS.
72
+ async function hoverChipDigit(sel) {
73
+ const handle = await page.evaluateHandle((s) => {
74
+ const el = document.querySelector(s);
75
+ if (!el) return null;
76
+ let p = el.parentElement;
77
+ while (p && !p.classList.contains('group')) p = p.parentElement;
78
+ return p;
79
+ }, sel);
80
+ return handle;
81
+ }
82
+
83
+ async function probeChip(digitSel) {
84
+ const rest = await page.evaluate((s) => {
85
+ const el = document.querySelector(s);
86
+ if (!el) return null;
87
+ return { ls: getComputedStyle(el).letterSpacing };
88
+ }, digitSel);
89
+ const groupHandle = await hoverChipDigit(digitSel);
90
+ await groupHandle.asElement()?.hover();
91
+ await page.waitForTimeout(350);
92
+ const hover = await page.evaluate((s) => {
93
+ const el = document.querySelector(s);
94
+ if (!el) return null;
95
+ return { ls: getComputedStyle(el).letterSpacing };
96
+ }, digitSel);
97
+ await page.mouse.move(50, 50);
98
+ await page.waitForTimeout(200);
99
+ return { rest, hover };
100
+ }
101
+
102
+ const working = await probeChip('[data-working-chip-digit]');
103
+ const online = await probeChip('[data-online-chip-digit]');
104
+ const activeLinks = await probeChip('[data-active-links-chip-digit]');
105
+
106
+ await browser.close();
107
+
108
+ // Source regex — all 3 chip digits should carry the new class string
109
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
110
+ const sourceWorking = /class[Nn]ame="font-semibold transition-\[font-weight,letter-spacing\] duration-200 group-hover:font-bold group-hover:tracking-wide" data-working-chip-digit/.test(src);
111
+ const sourceOnline = /class[Nn]ame="font-semibold transition-\[font-weight,letter-spacing\] duration-200 group-hover:font-bold group-hover:tracking-wide" data-online-chip-digit/.test(src);
112
+ const sourceAL = /class[Nn]ame="font-semibold transition-\[font-weight,letter-spacing\] duration-200 group-hover:font-bold group-hover:tracking-wide" data-active-links-chip-digit/.test(src);
113
+
114
+ const isRestLS = (r) => r?.ls === 'normal' || r?.ls === '0px';
115
+ // tracking-wide = 0.025em; on text-xs (12px) digit ≈ 0.3px (varies with computed font-size)
116
+ const isHoverLS = (r) => r?.ls && r.ls !== 'normal' && r.ls !== '0px' && parseFloat(r.ls) > 0.1;
117
+
118
+ const results = {
119
+ working_rest_normal: isRestLS(working?.rest),
120
+ working_hover_wide: isHoverLS(working?.hover),
121
+ online_rest_normal: isRestLS(online?.rest),
122
+ online_hover_wide: isHoverLS(online?.hover),
123
+ active_rest_normal: isRestLS(activeLinks?.rest),
124
+ active_hover_wide: isHoverLS(activeLinks?.hover),
125
+ source_working: sourceWorking,
126
+ source_online: sourceOnline,
127
+ source_active_links: sourceAL,
128
+ };
129
+ const ok = Object.values(results).every(Boolean);
130
+ console.log(`${ok ? '✅' : '❌'} R539 chip-row digit hover-tracking:`,
131
+ JSON.stringify(results, null, 2),
132
+ '\n working:', JSON.stringify(working),
133
+ '\n online:', JSON.stringify(online),
134
+ '\n active-links:', JSON.stringify(activeLinks));
135
+ process.exit(ok ? 0 : 1);