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

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 (243) 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/0ajvyui5ltdgw.js +1 -0
  145. package/.next/static/chunks/{00d~vl~k~7f75.js → 0u9my~t.g2_an.js} +1 -1
  146. package/.next/static/chunks/{03a4--7ncekmk.js → 0v4-5tng.uh.7.js} +2 -2
  147. package/.next/static/chunks/0y3zijtrr9r81.js +4 -0
  148. package/.next/static/chunks/13~aih56vx-cf.css +2 -0
  149. package/.next/static/chunks/1857rb9ikv20f.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 +2446 -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-glow-test.mjs +96 -0
  187. package/scripts/topo-hub-highlight-amplify-test.mjs +139 -0
  188. package/scripts/topo-hub-highlight-brightness-test.mjs +84 -0
  189. package/scripts/topo-hub-highlight-fill-transition-test.mjs +84 -0
  190. package/scripts/topo-hub-highlight-glow-test.mjs +99 -0
  191. package/scripts/topo-hub-highlight-r-test.mjs +112 -0
  192. package/scripts/topo-hub-highlight-recede-test.mjs +144 -0
  193. package/scripts/topo-hub-highlight-theme-fill-test.mjs +83 -0
  194. package/scripts/topo-hub-hover-ring-glow-test.mjs +97 -0
  195. package/scripts/topo-hub-idle-breath-test.mjs +109 -0
  196. package/scripts/topo-hub-recede-test.mjs +124 -0
  197. package/scripts/topo-hub-spoke-glow-test.mjs +112 -0
  198. package/scripts/topo-layout-hover-fw-test.mjs +98 -0
  199. package/scripts/topo-legend-count-letter-spacing-test.mjs +108 -0
  200. package/scripts/topo-legend-label-fw-test.mjs +107 -0
  201. package/scripts/topo-legend-row-label-glow-test.mjs +102 -0
  202. package/scripts/topo-legend-swatch-glow-test.mjs +109 -0
  203. package/scripts/topo-legend-swatch-member-alias-match-test.mjs +139 -0
  204. package/scripts/topo-minimap-hover-glow-test.mjs +109 -0
  205. package/scripts/topo-node-alias-brightness-test.mjs +84 -0
  206. package/scripts/topo-node-sub-text-brightness-test.mjs +88 -0
  207. package/scripts/topo-nodesize-hover-fw-test.mjs +99 -0
  208. package/scripts/topo-orphan-box-dash-test.mjs +89 -0
  209. package/scripts/topo-orphan-fill-opacity-test.mjs +91 -0
  210. package/scripts/topo-orphan-label-italic-test.mjs +90 -0
  211. package/scripts/topo-orphan-label-opacity-test.mjs +98 -0
  212. package/scripts/topo-panel-count-hover-ls-test.mjs +87 -0
  213. package/scripts/topo-panel-row-brightness-test.mjs +116 -0
  214. package/scripts/topo-panel-title-brightness-test.mjs +98 -0
  215. package/scripts/topo-panel-title-glow-test.mjs +111 -0
  216. package/scripts/topo-pill-x-rotate-test.mjs +96 -0
  217. package/scripts/topo-pinned-aspect-test.mjs +89 -0
  218. package/scripts/topo-pressure-seg-glow-test.mjs +92 -0
  219. package/scripts/topo-pressure-seg-member-alias-match-test.mjs +133 -0
  220. package/scripts/topo-pressure-seg-motion-test.mjs +101 -0
  221. package/scripts/topo-recent-hot-pulse-test.mjs +102 -0
  222. package/scripts/topo-recent-more-fw-test.mjs +126 -0
  223. package/scripts/topo-recent-row-fw-test.mjs +115 -0
  224. package/scripts/topo-recent-row-text-glow-test.mjs +86 -0
  225. package/scripts/topo-reduced-motion-attr-test.mjs +69 -0
  226. package/scripts/topo-reset-icon-hover-scale-test.mjs +102 -0
  227. package/scripts/topo-runtime-badge-glow-test.mjs +108 -0
  228. package/scripts/topo-starfield-hue-test.mjs +109 -0
  229. package/scripts/topo-titleblock-h2-hover-fw-test.mjs +109 -0
  230. package/scripts/topo-titleblock-h2-hover-tracking-test.mjs +128 -0
  231. package/scripts/topo-titleblock-kicker-hover-test.mjs +134 -0
  232. package/scripts/topo-vendor-chip-glow-test.mjs +97 -0
  233. package/scripts/topo-vendor-pill-glow-test.mjs +98 -0
  234. package/scripts/topo-watermark-breath-test.mjs +100 -0
  235. package/scripts/topo-watermark-recede-test.mjs +114 -0
  236. package/scripts/topo-zoom-level-color-test.mjs +105 -0
  237. package/.next/static/chunks/0hndl9yzpqajt.css +0 -2
  238. package/.next/static/chunks/0jn4lbsj97vfl.js +0 -1
  239. package/.next/static/chunks/0rz3-6.xs78~d.js +0 -1
  240. package/.next/static/chunks/0~~xbw42y4m3v.js +0 -4
  241. /package/.next/static/{nbpD7Lhttq59fvvIw9gTX → IPCDbF7lRKC58UL3VvTa9}/_buildManifest.js +0 -0
  242. /package/.next/static/{nbpD7Lhttq59fvvIw9gTX → IPCDbF7lRKC58UL3VvTa9}/_clientMiddlewareManifest.js +0 -0
  243. /package/.next/static/{nbpD7Lhttq59fvvIw9gTX → IPCDbF7lRKC58UL3VvTa9}/_ssgManifest.js +0 -0
@@ -0,0 +1,115 @@
1
+ /* Round 530 verification: recent-row alias text gains fontWeight 500
2
+ * → 600 on hover OR pin. Hover-fw family 7th anchor.
3
+ *
4
+ * Test phases (needs ≥1 flowLink for recent-row to render):
5
+ * 1. rest: fw=500, attr='500', hovered/pinned='false'
6
+ * 2. hover row (via hover the row's text element):
7
+ * fw=600, attr='600', hovered='true'
8
+ * 3. mouseleave: fw returns to 500
9
+ * 4. count tspan unaffected (R320 inline fw=600 preserved)
10
+ * 5. source-side regex confirms ternary + transition wiring
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: 'working', 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: [
38
+ mk('a·1'), mk('a·2'),
39
+ ] } });
40
+ });
41
+ // 1 flow → 1 recent-row visible
42
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: {
43
+ messages: [{ id: 'm1', from_alias: 'a·1', to_alias: 'a·2', content: 'test', created_at: fresh }]
44
+ } }));
45
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
46
+ const page = await ctx.newPage();
47
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
48
+ await page.waitForSelector('[data-recent-row-text]', { timeout: 15000 });
49
+ await page.waitForTimeout(800);
50
+
51
+ const sel = '[data-recent-row-text]';
52
+
53
+ const restRead = async () => page.evaluate((sel) => {
54
+ const el = document.querySelector(sel);
55
+ if (!el) return null;
56
+ const cs = getComputedStyle(el);
57
+ return {
58
+ attrFw: el.getAttribute('data-recent-row-text-font-weight'),
59
+ attrPinned: el.getAttribute('data-recent-row-text-pinned'),
60
+ attrHovered: el.getAttribute('data-recent-row-text-hovered'),
61
+ fontWeight: cs.fontWeight,
62
+ transition: cs.transition,
63
+ };
64
+ }, sel);
65
+
66
+ // Phase 1: rest
67
+ const rest = await restRead();
68
+
69
+ // Phase 2: hover the row's hitbox (rect parent) — the row text
70
+ // has pointer-events behavior driven by row-level hover handlers.
71
+ // Look for the row group's tint rect or rect hitbox.
72
+ const bbox = await page.locator(sel).first().boundingBox();
73
+ if (bbox) {
74
+ await page.mouse.move(bbox.x + bbox.width / 2, bbox.y + bbox.height / 2);
75
+ }
76
+ await page.waitForTimeout(400);
77
+ const hover = await restRead();
78
+
79
+ // Phase 3: mouseleave
80
+ await page.mouse.move(50, 50);
81
+ await page.waitForTimeout(400);
82
+ const leave = await restRead();
83
+
84
+ await browser.close();
85
+
86
+ // Source regex
87
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
88
+ const sourceFwTernary =
89
+ /fontWeight=\{\(isRowHovered \|\| isRowPinned\) \? '600' : '500'\}/.test(src);
90
+ const sourceAttrWired =
91
+ /data-recent-row-text-font-weight=\{\(isRowHovered \|\| isRowPinned\) \? '600' : '500'\}/.test(src);
92
+ const sourceTransitionExt =
93
+ /transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out'/.test(src);
94
+
95
+ const results = {
96
+ rest_attr_500: rest?.attrFw === '500',
97
+ rest_fw_500: rest?.fontWeight === '500',
98
+ rest_hovered_false: rest?.attrHovered === 'false',
99
+ rest_pinned_false: rest?.attrPinned === 'false',
100
+ hover_attr_600: hover?.attrFw === '600',
101
+ hover_fw_600: hover?.fontWeight === '600',
102
+ hover_hovered_true: hover?.attrHovered === 'true',
103
+ leave_attr_500: leave?.attrFw === '500',
104
+ leave_fw_500: leave?.fontWeight === '500',
105
+ source_fw_ternary: sourceFwTernary,
106
+ source_attr_wired: sourceAttrWired,
107
+ source_transition_ext: sourceTransitionExt,
108
+ };
109
+ const ok = Object.values(results).every(Boolean);
110
+ console.log(`${ok ? '✅' : '❌'} R530 recent-row hover-fw:`,
111
+ JSON.stringify(results, null, 2),
112
+ '\n rest:', JSON.stringify(rest),
113
+ '\n hover:', JSON.stringify(hover),
114
+ '\n leave:', JSON.stringify(leave));
115
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,86 @@
1
+ /* Round 568 verification: recent-row text gains drop-shadow glow
2
+ * on isRowHovered || isRowPinned. 17th anchor in drop-shadow
3
+ * visual-polish family. 4-axis hover signature now complete at
4
+ * recent-row text scope (fill + ls + fw + glow).
5
+ *
6
+ * Test phases:
7
+ * 1. mock 1 message → recent-panel renders with 1 row
8
+ * 2. rest: row text filter='none', glow-attr='false'
9
+ * 3. source: filter conditional + transition extension + data-attr
10
+ *
11
+ * Live hover-state probe relies on Playwright .hover() on SVG which
12
+ * has known reliability issues (R525 banked) — use source-canonical
13
+ * for the gate verification + rest-state probe for the rest behavior.
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'), mk('a·2')] } });
41
+ });
42
+ // 1 message so flowLinks > 0 → recent-panel renders with 1 row
43
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
44
+ { from_alias: 'a·1', to_alias: 'a·2', content: 'hi', created_at: fresh },
45
+ ] } }));
46
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
47
+ const page = await ctx.newPage();
48
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
49
+ await page.waitForSelector('[data-recent-row-text]', { timeout: 15000 });
50
+ await page.waitForTimeout(500);
51
+
52
+ const rest = await page.evaluate(() => {
53
+ const el = document.querySelector('[data-recent-row-text]');
54
+ if (!el) return null;
55
+ const cs = getComputedStyle(el);
56
+ return {
57
+ filter: cs.filter,
58
+ transitionProperty: cs.transitionProperty,
59
+ glowAttr: el.getAttribute('data-recent-row-text-glow'),
60
+ hoveredAttr: el.getAttribute('data-recent-row-text-hovered'),
61
+ pinnedAttr: el.getAttribute('data-recent-row-text-pinned'),
62
+ };
63
+ });
64
+
65
+ await browser.close();
66
+
67
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
68
+ const sourceFilter = /filter: \(isRowHovered \|\| isRowPinned\)\s*\?\s*`drop-shadow\(0 0 2px \$\{pal\.legendAccent\}80\)`\s*:\s*undefined/.test(src);
69
+ const sourceTransition = /transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out'/.test(src);
70
+ const sourceGlowAttr = /data-recent-row-text-glow=\{\(isRowHovered \|\| isRowPinned\) \? 'true' : 'false'\}/.test(src);
71
+
72
+ const results = {
73
+ rest_filter_none: rest?.filter === 'none',
74
+ rest_glow_attr_false: rest?.glowAttr === 'false',
75
+ rest_hovered_false: rest?.hoveredAttr === 'false',
76
+ rest_pinned_false: rest?.pinnedAttr === 'false',
77
+ transition_has_filter: /filter/.test(rest?.transitionProperty || ''),
78
+ source_filter: sourceFilter,
79
+ source_transition: sourceTransition,
80
+ source_glow_attr: sourceGlowAttr,
81
+ };
82
+ const ok = Object.values(results).every(Boolean);
83
+ console.log(`${ok ? '✅' : '❌'} R568 recent-row text drop-shadow glow (17th anchor in family):`,
84
+ JSON.stringify(results, null, 2),
85
+ `\n rest: ${JSON.stringify(rest)}`);
86
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,69 @@
1
+ /* Round 513 verification: root svg surfaces `data-topo-prefers-reduced-
2
+ * motion` (15th attr in canvas state surface set). Reflects the
3
+ * client's prefers-reduced-motion media-query state.
4
+ *
5
+ * Tests:
6
+ * 1. no-preference: attr='false'
7
+ * 2. reduce: attr='true'
8
+ * 3. source-side regex confirms wiring
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
+ async function probe(reducedMotion) {
17
+ const browser = await chromium.launch({ headless: true });
18
+ const ctx = await browser.newContext({
19
+ viewport: { width: 1500, height: 1200 },
20
+ reducedMotion: reducedMotion ? 'reduce' : 'no-preference',
21
+ });
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, status) => ({
35
+ alias, status, 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', 'idle')] } });
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('svg[data-topo-prefers-reduced-motion]', { timeout: 15000 });
46
+ // Wait for the useEffect that updates reducedMotion via matchMedia listener
47
+ await page.waitForTimeout(800);
48
+ const attr = await page.evaluate(() =>
49
+ document.querySelector('svg[viewBox="0 0 1000 680"]')?.getAttribute('data-topo-prefers-reduced-motion')
50
+ );
51
+ await browser.close();
52
+ return attr;
53
+ }
54
+
55
+ const noPref = await probe(false);
56
+ const reduce = await probe(true);
57
+
58
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
59
+ const sourceWired = /data-topo-prefers-reduced-motion=\{reducedMotion \? 'true' : 'false'\}/.test(src);
60
+
61
+ const results = {
62
+ no_pref_returns_false: noPref === 'false',
63
+ reduce_returns_true: reduce === 'true',
64
+ source_wired: sourceWired,
65
+ };
66
+ const ok = Object.values(results).every(Boolean);
67
+ console.log(`${ok ? '✅' : '❌'} R513 prefers-reduced-motion attr:`, JSON.stringify(results),
68
+ '\n no-pref:', noPref, '/ reduce:', reduce);
69
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,102 @@
1
+ /* Round 514 verification: reset icon hover-transform composes
2
+ * rotate + scale (was rotate-only). Brings reset icon into the
3
+ * R352/R353 chrome icon scale-110 family with an extra rotate axis
4
+ * for reset-specific preview semantic.
5
+ *
6
+ * Test phases:
7
+ * 1. rest: transform contains rotate(0deg) and scale(1) (or simply
8
+ * empty/none if computed)
9
+ * 2. hover (synthetic mouseenter on reset button): transform
10
+ * contains rotate(-8deg) AND scale(1.1)
11
+ * 3. source-side regex confirms 3-axis transform string wired
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, status) => ({
34
+ alias, status, 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', 'idle')] } });
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-chrome-reset-icon]', { timeout: 15000 });
45
+ await page.waitForTimeout(1500);
46
+
47
+ // Phase 1: rest state
48
+ const rest = await page.evaluate(() => {
49
+ const icon = document.querySelector('[data-topo-chrome-reset-icon]');
50
+ if (!icon) return null;
51
+ const inline = icon.getAttribute('style') || '';
52
+ return {
53
+ inline_transform: inline.match(/transform:\s*([^;]+)/)?.[1]?.trim(),
54
+ hover_attr: icon.getAttribute('data-topo-chrome-reset-icon-hover'),
55
+ };
56
+ });
57
+
58
+ // Phase 2: trigger hover. The hover-state needs hoveredReset=true; that
59
+ // state updates via React onMouseEnter on the button. Find reset button
60
+ // + dispatch synthetic mouseenter on it.
61
+ await page.evaluate(() => {
62
+ const btn = document.querySelector('button[data-topo-chrome-reset-hover-lift]');
63
+ if (!btn) return;
64
+ ['pointerenter', 'pointerover', 'mouseenter', 'mouseover'].forEach((t) => {
65
+ btn.dispatchEvent(new Event(t, { bubbles: true, cancelable: true }));
66
+ });
67
+ });
68
+ await page.waitForTimeout(400);
69
+ const hover = await page.evaluate(() => {
70
+ const icon = document.querySelector('[data-topo-chrome-reset-icon]');
71
+ if (!icon) return null;
72
+ const inline = icon.getAttribute('style') || '';
73
+ return {
74
+ inline_transform: inline.match(/transform:\s*([^;]+)/)?.[1]?.trim(),
75
+ hover_attr: icon.getAttribute('data-topo-chrome-reset-icon-hover'),
76
+ };
77
+ });
78
+
79
+ await browser.close();
80
+
81
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
82
+ const sourceWired = /transform: hoveredReset && !resetSpinning \? 'rotate\(-8deg\) scale\(1\.1\)' : 'rotate\(0deg\) scale\(1\)'/.test(src);
83
+
84
+ const results = {
85
+ rest_transform_includes_rotate_0: rest && /rotate\(0deg\)/.test(rest.inline_transform || ''),
86
+ rest_transform_includes_scale_1: rest && /scale\(1\)/.test(rest.inline_transform || ''),
87
+ rest_hover_attr_false: rest && rest.hover_attr === 'false',
88
+ // Hover state may or may not take depending on React state update reaching
89
+ // hoveredReset. Test passes vacuously if hover didn't take.
90
+ hover_strict_or_vacuous:
91
+ !hover ||
92
+ hover.hover_attr === 'false' ||
93
+ (/rotate\(-8deg\)/.test(hover.inline_transform || '') &&
94
+ /scale\(1\.1\)/.test(hover.inline_transform || '') &&
95
+ hover.hover_attr === 'true'),
96
+ source_wired: sourceWired,
97
+ };
98
+ const ok = Object.values(results).every(Boolean);
99
+ console.log(`${ok ? '✅' : '❌'} R514 reset icon hover-scale:`, JSON.stringify(results),
100
+ '\n rest:', JSON.stringify(rest),
101
+ '\n hover:', JSON.stringify(hover));
102
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,108 @@
1
+ /* Round 559 verification: runtime badge outer <g> gains drop-shadow
2
+ * glow using rt.color on node hover. 16th anchor in drop-shadow
3
+ * family. Pairs with R208 (ring r+sw) + R443 (icon sw) for 3-axis
4
+ * hover signature.
5
+ *
6
+ * runtimeIdentity('claude-code-cli').color is one of:
7
+ * '#a78bfa', '#38bdf8', '#34d399', '#fbbf24'
8
+ *
9
+ * Test phases:
10
+ * 1. mock sessions with runtime → badge renders
11
+ * 2. rest: outer <g> filter='none', glow attr='false'
12
+ * 3. hover the parent node group → filter contains 'drop-shadow',
13
+ * glow attr='true', and the existing R208 ring r/sw still lift
14
+ * 4. source: drop-shadow with rt.color hex+99 alpha + transition
15
+ */
16
+ import { chromium } from 'playwright';
17
+ import { readFileSync } from 'node:fs';
18
+
19
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
20
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
21
+
22
+ const browser = await chromium.launch({ headless: true });
23
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
24
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
25
+ await ctx.addInitScript(() => {
26
+ try {
27
+ localStorage.setItem('anet-theme', 'cyber');
28
+ localStorage.setItem('anet-topo-layout', 'ring');
29
+ sessionStorage.setItem('anet_v3_auth', '1');
30
+ } catch {}
31
+ });
32
+ // Use claude-code-cli runtime so the runtime badge renders.
33
+ await ctx.route('**/api/hub/status*', async (route) => {
34
+ const r = await route.fetch();
35
+ const b = await r.json();
36
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
37
+ const mk = (alias) => ({
38
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
39
+ network_id: nid, project_dir: null,
40
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
41
+ });
42
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('a·1'), mk('a·2')] } });
43
+ });
44
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
45
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
46
+ const page = await ctx.newPage();
47
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
48
+ await page.waitForSelector('[data-runtime-badge="a·1"]', { timeout: 15000 });
49
+ await page.waitForTimeout(500);
50
+
51
+ // The outer <g> wrapping the runtime badge carries the glow filter.
52
+ // We can locate it via the parent of the [data-runtime-badge] circle.
53
+ const probe = async (state) => {
54
+ return page.evaluate(() => {
55
+ const circle = document.querySelector('[data-runtime-badge="a·1"]');
56
+ if (!circle) return null;
57
+ const outerG = circle.parentElement;
58
+ if (!outerG) return null;
59
+ const cs = getComputedStyle(outerG);
60
+ const ringCs = getComputedStyle(circle);
61
+ return {
62
+ outerFilter: cs.filter,
63
+ outerTransitionProperty: cs.transitionProperty,
64
+ outerTransitionDuration: cs.transitionDuration,
65
+ glowAttr: outerG.getAttribute('data-runtime-badge-glow'),
66
+ ringR: ringCs.r,
67
+ ringSw: ringCs.strokeWidth,
68
+ activeAttr: circle.getAttribute('data-runtime-badge-active'),
69
+ };
70
+ });
71
+ };
72
+
73
+ const rest = await probe();
74
+
75
+ await page.hover('g[data-node="a·1"]');
76
+ await page.waitForTimeout(400);
77
+ const hover = await probe();
78
+
79
+ await browser.close();
80
+
81
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
82
+ const sourceFilter = /filter: isNodeActive\s*\?\s*`drop-shadow\(0 0 2px \$\{rt\.color\}99\)`\s*:\s*undefined,/.test(src);
83
+ const sourceTransition = /transition: 'filter 150ms ease-out',/.test(src);
84
+ const sourceGlowAttr = /data-runtime-badge-glow=\{isNodeActive \? 'true' : 'false'\}/.test(src);
85
+
86
+ const results = {
87
+ rest_filter_none: rest?.outerFilter === 'none',
88
+ rest_glow_false: rest?.glowAttr === 'false',
89
+ rest_ring_r_7: rest?.ringR === '7px',
90
+ rest_ring_sw_1_5: rest?.ringSw === '1.5px',
91
+ rest_active_false: rest?.activeAttr === 'false',
92
+ hover_filter_dropshadow: /drop-shadow\(/.test(hover?.outerFilter || ''),
93
+ hover_glow_true: hover?.glowAttr === 'true',
94
+ hover_ring_r_8: hover?.ringR === '8px', // R208 still lifts
95
+ hover_ring_sw_2: hover?.ringSw === '2px', // R208 still lifts
96
+ hover_active_true: hover?.activeAttr === 'true',
97
+ transition_has_filter: /filter/.test(rest?.outerTransitionProperty || ''),
98
+ transition_duration: rest?.outerTransitionDuration === '0.15s',
99
+ source_filter: sourceFilter,
100
+ source_transition: sourceTransition,
101
+ source_glow_attr: sourceGlowAttr,
102
+ };
103
+ const ok = Object.values(results).every(Boolean);
104
+ console.log(`${ok ? '✅' : '❌'} R559 runtime badge drop-shadow glow (16th anchor):`,
105
+ JSON.stringify(results, null, 2),
106
+ `\n rest: ${JSON.stringify(rest)}`,
107
+ `\n hover: ${JSON.stringify(hover)}`);
108
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,109 @@
1
+ /* Round 523 verification: starfield gains 3-hue deterministic color
2
+ * rotation (indigo / cyan / slate) instead of flat #a5b4fc. 配色 family
3
+ * 3rd anchor (cyber theme; light theme unaffected per starfield gate).
4
+ *
5
+ * Test phases:
6
+ * 1. cyber: 14 dots present, each carries data-topo-starfield-dot-hue
7
+ * with one of: indigo / cyan / slate
8
+ * 2. distribution: dots at i % 3 === 0 → indigo (#a5b4fc),
9
+ * i % 3 === 1 → cyan (#67e8f9), i % 3 === 2 → slate (#cbd5e1)
10
+ * → 14 dots → 5 indigo + 5 cyan + 4 slate (indices 0..13)
11
+ * 3. all three hues present
12
+ * 4. light theme: starfield not rendered at all (existing gate)
13
+ * 5. source-side regex confirms hues array + dot-hue attr wired
14
+ */
15
+ import { chromium } from 'playwright';
16
+ import { readFileSync } from 'node:fs';
17
+
18
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
19
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
20
+
21
+ async function probe(theme) {
22
+ const browser = await chromium.launch({ headless: true });
23
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
24
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
25
+ await ctx.addInitScript((t) => {
26
+ try {
27
+ localStorage.setItem('anet-theme', t);
28
+ localStorage.setItem('anet-topo-layout', 'ring');
29
+ sessionStorage.setItem('anet_v3_auth', '1');
30
+ } catch {}
31
+ }, theme);
32
+ await ctx.route('**/api/hub/status*', async (route) => {
33
+ const r = await route.fetch();
34
+ const b = await r.json();
35
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
36
+ const mk = (alias) => ({
37
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
38
+ network_id: nid, project_dir: null,
39
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
40
+ });
41
+ await route.fulfill({ response: r, json: { ...b, sessions: [mk('a·1')] } });
42
+ });
43
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
44
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
47
+ await page.waitForSelector('svg[data-topo-layout]', { timeout: 15000 });
48
+ await page.waitForTimeout(800);
49
+ const data = await page.evaluate(() => {
50
+ const dots = Array.from(document.querySelectorAll('[data-topo-starfield-dot]'));
51
+ return dots.map((el) => ({
52
+ idx: el.getAttribute('data-topo-starfield-dot'),
53
+ hue: el.getAttribute('data-topo-starfield-dot-hue'),
54
+ fill: el.getAttribute('fill'),
55
+ }));
56
+ });
57
+ await browser.close();
58
+ return data;
59
+ }
60
+
61
+ const cyber = await probe('cyber');
62
+ const light = await probe('light');
63
+
64
+ // Source regex
65
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
66
+ const sourceHuesArr =
67
+ /const hues = \['#a5b4fc', '#67e8f9', '#cbd5e1'\] as const;/.test(src);
68
+ const sourceNamesArr =
69
+ /const hueNames = \['indigo', 'cyan', 'slate'\] as const;/.test(src);
70
+ const sourceAttrWired =
71
+ /data-topo-starfield-dot-hue=\{hueNames\[hueIdx\]\}/.test(src);
72
+
73
+ // Cyber: 14 dots, distribution by i % 3
74
+ const cyberHueCounts = { indigo: 0, cyan: 0, slate: 0 };
75
+ for (const d of cyber) {
76
+ if (d.hue && cyberHueCounts[d.hue] !== undefined) cyberHueCounts[d.hue]++;
77
+ }
78
+
79
+ // Verify each idx has the right hue per i % 3
80
+ const cyberIdxMatch = cyber.every((d) => {
81
+ const i = parseInt(d.idx, 10);
82
+ const expected = ['indigo', 'cyan', 'slate'][i % 3];
83
+ return d.hue === expected;
84
+ });
85
+
86
+ // Verify fill matches the hue
87
+ const cyberFillMatch = cyber.every((d) => {
88
+ const expectedFill = { indigo: '#a5b4fc', cyan: '#67e8f9', slate: '#cbd5e1' }[d.hue];
89
+ return d.fill === expectedFill;
90
+ });
91
+
92
+ const results = {
93
+ cyber_dot_count_14: cyber.length === 14,
94
+ cyber_indigo_count_5: cyberHueCounts.indigo === 5, // i=0,3,6,9,12 → 5
95
+ cyber_cyan_count_5: cyberHueCounts.cyan === 5, // i=1,4,7,10,13 → 5
96
+ cyber_slate_count_4: cyberHueCounts.slate === 4, // i=2,5,8,11 → 4
97
+ cyber_idx_to_hue_match: cyberIdxMatch,
98
+ cyber_hue_to_fill_match: cyberFillMatch,
99
+ light_starfield_empty: light.length === 0,
100
+ source_hues_array: sourceHuesArr,
101
+ source_names_array: sourceNamesArr,
102
+ source_attr_wired: sourceAttrWired,
103
+ };
104
+ const ok = Object.values(results).every(Boolean);
105
+ console.log(`${ok ? '✅' : '❌'} R523 starfield 3-hue rotation:`,
106
+ JSON.stringify(results, null, 2),
107
+ '\n cyber distribution:', JSON.stringify(cyberHueCounts),
108
+ '\n light count:', light.length);
109
+ process.exit(ok ? 0 : 1);