@sleep2agi/agent-network-dashboard 0.5.1-preview.111 → 0.5.1-preview.113

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 (141) 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 +4 -4
  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.html +1 -1
  13. package/.next/server/app/_not-found.rsc +1 -1
  14. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  15. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  20. package/.next/server/app/admin.html +1 -1
  21. package/.next/server/app/admin.rsc +1 -1
  22. package/.next/server/app/admin.segments/_full.segment.rsc +1 -1
  23. package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  24. package/.next/server/app/admin.segments/_index.segment.rsc +1 -1
  25. package/.next/server/app/admin.segments/_tree.segment.rsc +1 -1
  26. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
  27. package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
  28. package/.next/server/app/index.html +2 -2
  29. package/.next/server/app/index.rsc +2 -2
  30. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  31. package/.next/server/app/index.segments/_full.segment.rsc +2 -2
  32. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  33. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  34. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  35. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  36. package/.next/server/app/login.html +2 -2
  37. package/.next/server/app/login.rsc +2 -2
  38. package/.next/server/app/login.segments/_full.segment.rsc +2 -2
  39. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  40. package/.next/server/app/login.segments/_index.segment.rsc +1 -1
  41. package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  42. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  43. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  44. package/.next/server/app/logs.html +1 -1
  45. package/.next/server/app/logs.rsc +1 -1
  46. package/.next/server/app/logs.segments/_full.segment.rsc +1 -1
  47. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  48. package/.next/server/app/logs.segments/_index.segment.rsc +1 -1
  49. package/.next/server/app/logs.segments/_tree.segment.rsc +1 -1
  50. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
  51. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  52. package/.next/server/app/messages.html +1 -1
  53. package/.next/server/app/messages.rsc +1 -1
  54. package/.next/server/app/messages.segments/_full.segment.rsc +1 -1
  55. package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
  56. package/.next/server/app/messages.segments/_index.segment.rsc +1 -1
  57. package/.next/server/app/messages.segments/_tree.segment.rsc +1 -1
  58. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
  59. package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
  60. package/.next/server/app/node.html +1 -1
  61. package/.next/server/app/node.rsc +1 -1
  62. package/.next/server/app/node.segments/_full.segment.rsc +1 -1
  63. package/.next/server/app/node.segments/_head.segment.rsc +1 -1
  64. package/.next/server/app/node.segments/_index.segment.rsc +1 -1
  65. package/.next/server/app/node.segments/_tree.segment.rsc +1 -1
  66. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
  67. package/.next/server/app/node.segments/node.segment.rsc +1 -1
  68. package/.next/server/app/nodes.html +1 -1
  69. package/.next/server/app/nodes.rsc +1 -1
  70. package/.next/server/app/nodes.segments/_full.segment.rsc +1 -1
  71. package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
  72. package/.next/server/app/nodes.segments/_index.segment.rsc +1 -1
  73. package/.next/server/app/nodes.segments/_tree.segment.rsc +1 -1
  74. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
  75. package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
  76. package/.next/server/app/page_client-reference-manifest.js +1 -1
  77. package/.next/server/app/server-logs.html +1 -1
  78. package/.next/server/app/server-logs.rsc +1 -1
  79. package/.next/server/app/server-logs.segments/_full.segment.rsc +1 -1
  80. package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
  81. package/.next/server/app/server-logs.segments/_index.segment.rsc +1 -1
  82. package/.next/server/app/server-logs.segments/_tree.segment.rsc +1 -1
  83. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
  84. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
  85. package/.next/server/app/settings/networks.html +1 -1
  86. package/.next/server/app/settings/networks.rsc +1 -1
  87. package/.next/server/app/settings/networks.segments/_full.segment.rsc +1 -1
  88. package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
  89. package/.next/server/app/settings/networks.segments/_index.segment.rsc +1 -1
  90. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +1 -1
  91. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
  92. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
  93. package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
  94. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  95. package/.next/server/app/settings/tokens.html +1 -1
  96. package/.next/server/app/settings/tokens.rsc +1 -1
  97. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +1 -1
  98. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
  99. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +1 -1
  100. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +1 -1
  101. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
  102. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
  103. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
  104. package/.next/server/app/settings.html +2 -2
  105. package/.next/server/app/settings.rsc +2 -2
  106. package/.next/server/app/settings.segments/_full.segment.rsc +2 -2
  107. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  108. package/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  109. package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  110. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  111. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  112. package/.next/server/app/tasks.html +1 -1
  113. package/.next/server/app/tasks.rsc +1 -1
  114. package/.next/server/app/tasks.segments/_full.segment.rsc +1 -1
  115. package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
  116. package/.next/server/app/tasks.segments/_index.segment.rsc +1 -1
  117. package/.next/server/app/tasks.segments/_tree.segment.rsc +1 -1
  118. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
  119. package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
  120. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  121. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  122. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  123. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  124. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  125. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  126. package/.next/server/middleware-build-manifest.js +3 -3
  127. package/.next/server/pages/404.html +1 -1
  128. package/.next/server/pages/500.html +1 -1
  129. package/.next/static/chunks/{059.x2_u04fls.js → 0.if~iq2kc9iv.js} +1 -1
  130. package/.next/static/chunks/{0xvpfbdq_1iwg.js → 0zlficjns17i1.js} +1 -1
  131. package/.next/static/chunks/1814~crcncitu.js +4 -0
  132. package/.next/trace +2 -2
  133. package/.next/trace-build +1 -1
  134. package/app/components/TopoGraph.tsx +53 -5
  135. package/package.json +1 -1
  136. package/scripts/topo-dense-alias-opacity-test.mjs +68 -0
  137. package/scripts/topo-hub-halo-r-hover-test.mjs +82 -0
  138. package/.next/static/chunks/152~t8cc8lw5k.js +0 -4
  139. /package/.next/static/{n0xvBKFL_GMRtCPB1M6VF → kw-JUn0GWFF6m6QLy9RUw}/_buildManifest.js +0 -0
  140. /package/.next/static/{n0xvBKFL_GMRtCPB1M6VF → kw-JUn0GWFF6m6QLy9RUw}/_clientMiddlewareManifest.js +0 -0
  141. /package/.next/static/{n0xvBKFL_GMRtCPB1M6VF → kw-JUn0GWFF6m6QLy9RUw}/_ssgManifest.js +0 -0
@@ -5754,13 +5754,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
5754
5754
  // Mode='spline' + R245 ease-in-out keySplines all
5755
5755
  // preserved. data-topo-hub-halo-radius attr exposes
5756
5756
  // value for tests.
5757
+ /* Round 451 / Loop: hub center halo radius lift on
5758
+ hoveredHub — r 20 → 22 (+2px, ~21% area). Adds another
5759
+ geometric axis to the hub-hover signature stack
5760
+ alongside R177 ring radius lift + R209 digit scale +
5761
+ R425 digit fw + R370 halo opacity + R386 highlight
5762
+ opacity + R441 core fill chroma. Pre-R451 the halo
5763
+ r stayed planted at R408's 20px while the rest of
5764
+ the hub structure responded to hover. R451 makes
5765
+ the halo breath outward on hover so the focal pulse
5766
+ intensifies under attention. SMIL `<animate>` on
5767
+ opacity continues independently (animateAttr=
5768
+ 'opacity' vs CSS-property r — non-conflicting). R408
5769
+ base radius 20 preserved as rest; +2 hover delta
5770
+ keeps clearance from the R177 hub-hover-ring at
5771
+ r=17 hover (halo is BEHIND the ring, halo r=22 sits
5772
+ 5px beyond the ring's hover-r=17, still well within
5773
+ the hub canvas envelope). data-topo-hub-halo-radius
5774
+ attr now reports the dynamic value. */
5775
+ const isHaloHovered = !reducedMotion && hoveredHub;
5776
+ const haloR = isHaloHovered ? 22 : 20;
5757
5777
  return (
5758
5778
  <circle
5759
- cx={cx} cy={cy} r="20"
5779
+ cx={cx} cy={cy}
5760
5780
  fill={isLight ? '#d1fae5' : '#10b981'}
5761
5781
  opacity={isLight ? 0.42 : 0.12}
5762
5782
  data-hub-busyness={busy}
5763
- data-topo-hub-halo-radius="20"
5783
+ data-topo-hub-halo-radius={haloR}
5784
+ data-topo-hub-halo-hovered={isHaloHovered ? 'true' : 'false'}
5764
5785
  data-topo-hub-halo-trough={isLight ? troughLight : troughDark}
5765
5786
  data-topo-hub-halo-peak={isLight ? peakLight : peakDark}
5766
5787
  /* Round 253 / Loop: hub grounding halo fill transition
@@ -5769,8 +5790,14 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
5769
5790
  animate on opacity continued running. CSS fill
5770
5791
  transition is independent of the SMIL animate
5771
5792
  (different attributes), so they compose without
5772
- conflict. */
5773
- style={{ transition: 'fill 200ms ease-out' }}
5793
+ conflict.
5794
+ R451: r as CSS property (R197/R198 idiom) so the
5795
+ hover-radius tween eases smoothly under the same
5796
+ 200ms cadence as fill. */
5797
+ style={{
5798
+ r: `${haloR}px`,
5799
+ transition: 'fill 200ms ease-out, r 200ms ease-out',
5800
+ } as React.CSSProperties}
5774
5801
  >
5775
5802
  {/* Round 244 / Loop: hub grounding halo breath gets
5776
5803
  ease-in-out keySplines, matching the active-node
@@ -7485,6 +7512,26 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
7485
7512
  // CSS pseudo-class; only the transition-property
7486
7513
  // moves to inline. Big fleets benefit most — this is
7487
7514
  // the path users see when their dashboard is busiest.
7515
+ /* Round 452 / Loop: dense plain-text alias rest
7516
+ opacity 0.9 → 0.95. Closes the alpha gap on the
7517
+ dense fleet's per-node label, sibling to R449
7518
+ legend-count-active 0.95→1.0 and R450 minimap
7519
+ viewport rest 0.9→0.95 — same "close the
7520
+ active-presence alpha gap" idiom applied here
7521
+ to the dense-mode alias text at fontSize=9-10
7522
+ monospace. Pre-R452 dense aliases at α=0.9 sat
7523
+ just below full alpha; for un-hovered nodes in
7524
+ a busy >16-node fleet this is the only label
7525
+ readable, so the 10% alpha gap added a subtle
7526
+ "soft-focused chrome" feel where the labels
7527
+ should read as definitive. +0.05 lift makes
7528
+ them confidently present without erasing the
7529
+ status.text + R110 stroke halo + paintOrder
7530
+ layering. R26 group-hover translate + R212
7531
+ fill 300ms transition + R110 stroke=container-
7532
+ Bg halo all preserved. data-node-dense-alias-
7533
+ text-opacity attr exposes the resolved value
7534
+ for tests. */
7488
7535
  <text
7489
7536
  x={pos.x}
7490
7537
  y={pos.y + radius + denseDrop}
@@ -7493,9 +7540,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
7493
7540
  fontSize={denseFs}
7494
7541
  fontFamily="monospace"
7495
7542
  fontWeight="700"
7496
- opacity={0.9}
7543
+ opacity={0.95}
7497
7544
  className="group-hover:-translate-y-[1.5px]"
7498
7545
  data-node-dense-alias-text={session.alias}
7546
+ data-node-dense-alias-text-opacity="0.95"
7499
7547
  style={{
7500
7548
  pointerEvents: 'none',
7501
7549
  paintOrder: 'stroke',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.5.1-preview.111",
3
+ "version": "0.5.1-preview.113",
4
4
  "description": "Agent Network Dashboard — Web UI for managing AI Agent networks",
5
5
  "scripts": {
6
6
  "dev": "next dev",
@@ -0,0 +1,68 @@
1
+ /* Round 452 verification: dense plain-text alias rest opacity 0.9 → 0.95.
2
+ * Sibling to R449/R450 canvas-presence alpha-gap closures.
3
+ *
4
+ * Contract:
5
+ * - need >16 sessions to enter dense layout
6
+ * - every dense alias-text reports opacity '0.95' + -opacity attr '0.95'
7
+ * - source-file probe confirms the value
8
+ */
9
+ import { chromium } from 'playwright';
10
+ import { readFileSync } from 'node:fs';
11
+
12
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
13
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
14
+
15
+ const browser = await chromium.launch({ headless: true });
16
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
17
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
18
+ await ctx.addInitScript(() => {
19
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
20
+ });
21
+ await ctx.route('**/api/hub/status*', async (route) => {
22
+ const r = await route.fetch();
23
+ const b = await r.json();
24
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
25
+ const mk = (alias, status) => ({
26
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
27
+ network_id: nid, project_dir: null,
28
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
29
+ });
30
+ // 20 nodes — crosses the >16 dense threshold
31
+ const sessions = [];
32
+ for (let i = 0; i < 20; i++) {
33
+ sessions.push(mk(`n${String(i).padStart(2, '0')}`, i % 3 === 0 ? 'working' : 'idle'));
34
+ }
35
+ await route.fulfill({ response: r, json: { ...b, sessions } });
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
+
40
+ const page = await ctx.newPage();
41
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
42
+ await page.waitForSelector('[data-node-dense-alias-text]', { timeout: 15000 });
43
+ await page.waitForTimeout(400);
44
+
45
+ const aliases = await page.evaluate(() => {
46
+ const ts = [...document.querySelectorAll('[data-node-dense-alias-text]')];
47
+ return ts.map(t => ({
48
+ alias: t.getAttribute('data-node-dense-alias-text'),
49
+ opacity: t.getAttribute('opacity'),
50
+ data_op: t.getAttribute('data-node-dense-alias-text-opacity'),
51
+ }));
52
+ });
53
+
54
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
55
+ const sourceWired = /opacity=\{0\.95\}/.test(fileText) && /data-node-dense-alias-text-opacity="0\.95"/.test(fileText);
56
+
57
+ await browser.close();
58
+
59
+ const results = {
60
+ dense_layout_count_ge_17: aliases.length >= 17,
61
+ all_opacity_0_95: aliases.every(a => a.opacity === '0.95'),
62
+ all_data_op_0_95: aliases.every(a => a.data_op === '0.95'),
63
+ source_wired: sourceWired,
64
+ };
65
+ const ok = Object.values(results).every(Boolean);
66
+ console.log(`${ok ? '✅' : '❌'} dense alias opacity 0.95:`, JSON.stringify(results),
67
+ '\n count:', aliases.length, 'sample:', JSON.stringify(aliases[0]));
68
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,82 @@
1
+ /* Round 451 verification: hub center halo radius hover lift —
2
+ * r 20 → 22 on hoveredHub.
3
+ *
4
+ * Contract:
5
+ * - rest: hub halo reports data-topo-hub-halo-radius '20' + hovered='false'
6
+ * - hover the hub <g>: halo lifts to r '22' + hovered='true'
7
+ */
8
+ import { chromium } from 'playwright';
9
+ import { readFileSync } from 'node:fs';
10
+
11
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
12
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
13
+
14
+ const browser = await chromium.launch({ headless: true });
15
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
16
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
17
+ await ctx.addInitScript(() => {
18
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
19
+ });
20
+ await ctx.route('**/api/hub/status*', async (route) => {
21
+ const r = await route.fetch();
22
+ const b = await r.json();
23
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
24
+ const mk = (alias, status) => ({
25
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
26
+ network_id: nid, project_dir: null,
27
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
28
+ });
29
+ await route.fulfill({ response: r, json: { ...b, sessions: [
30
+ mk('alpha', 'working'),
31
+ mk('beta', 'idle'),
32
+ ] } });
33
+ });
34
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
35
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
36
+
37
+ const page = await ctx.newPage();
38
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
39
+ await page.waitForSelector('[data-topo-hub-halo-radius]', { timeout: 15000 });
40
+ await page.waitForTimeout(400);
41
+
42
+ const readHalo = () => page.evaluate(() => {
43
+ const c = document.querySelector('[data-topo-hub-halo-radius]');
44
+ return c ? {
45
+ r_data: parseFloat(c.getAttribute('data-topo-hub-halo-radius') || '0'),
46
+ hovered: c.getAttribute('data-topo-hub-halo-hovered'),
47
+ } : null;
48
+ });
49
+
50
+ const rest = await readHalo();
51
+
52
+ let hover = null;
53
+ const hubBox = await page.evaluate(() => {
54
+ const hub = document.querySelector('[data-topo-hub]');
55
+ if (!hub) return null;
56
+ const b = hub.getBoundingClientRect();
57
+ return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
58
+ });
59
+ if (hubBox) {
60
+ await page.mouse.move(hubBox.x, hubBox.y);
61
+ await page.waitForTimeout(300);
62
+ hover = await readHalo();
63
+ await page.mouse.move(0, 0);
64
+ }
65
+
66
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
67
+ const sourceWired = /const haloR = isHaloHovered \? 22 : 20/.test(fileText);
68
+
69
+ await browser.close();
70
+
71
+ const results = {
72
+ rest_mounted: !!rest,
73
+ rest_r_20: rest?.r_data === 20,
74
+ rest_hovered_false: rest?.hovered === 'false',
75
+ hover_r_22: hover?.r_data === 22,
76
+ hover_hovered_true: hover?.hovered === 'true',
77
+ source_wired: sourceWired,
78
+ };
79
+ const ok = Object.values(results).every(Boolean);
80
+ console.log(`${ok ? '✅' : '❌'} hub halo hover r:`, JSON.stringify(results),
81
+ '\n rest:', rest, '\n hover:', hover);
82
+ process.exit(ok ? 0 : 1);