@sleep2agi/agent-network-dashboard 0.4.6-preview.281 → 0.4.6-preview.283

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 (143) 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/{0c_eg6lxw~36i.js → 0770ctmo-jd~3.js} +3 -3
  130. package/.next/static/chunks/{0qnfuwkukbu01.js → 12ih-cxmgr09g.js} +1 -1
  131. package/.next/static/chunks/{11ilooy_9niy-.js → 13ku0uxsx2o6i.js} +1 -1
  132. package/.next/trace +2 -2
  133. package/.next/trace-build +1 -1
  134. package/app/components/TopoGraph.tsx +42 -2
  135. package/package.json +1 -1
  136. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  137. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  138. package/scripts/topo-freshness-text-color-coherence-test.mjs +0 -97
  139. package/scripts/topo-orbit-busyness-test.mjs +0 -96
  140. package/scripts/topo-orbit-opacity-bucket-test.mjs +0 -124
  141. /package/.next/static/{gjUOBeB2JqesZ_cSX14ph → gPw1YGsDb6psvirUWKGfQ}/_buildManifest.js +0 -0
  142. /package/.next/static/{gjUOBeB2JqesZ_cSX14ph → gPw1YGsDb6psvirUWKGfQ}/_clientMiddlewareManifest.js +0 -0
  143. /package/.next/static/{gjUOBeB2JqesZ_cSX14ph → gPw1YGsDb6psvirUWKGfQ}/_ssgManifest.js +0 -0
@@ -1,97 +0,0 @@
1
- /* Round 272 verification: FreshnessChip text prefix matches color
2
- * state (fresh="live", stale="lag").
3
- *
4
- * Pre-R272 the chip read "live · {sec}s" in both states:
5
- * fresh: gray + "live · 3s" → color says fresh, text says fresh ✓
6
- * stale: amber + "live · 15s" → color says stale, text says fresh ✗ (contradicts)
7
- *
8
- * Post-R272:
9
- * fresh: gray + "live · 3s" → color and text both say fresh ✓
10
- * stale: amber + "lag · 15s" → color and text both signal stale ✓
11
- *
12
- * The chip's `stale` flag fires when sec > 10. Hard to wait 11s in
13
- * a test, so we test the fresh-state directly (default render is
14
- * fresh since useRef captures Date.now() at mount and 0s has
15
- * elapsed). For stale, we'd need to wait — instead, verify the
16
- * code path by checking the chip exists with fresh text + correct
17
- * `data-freshness-chip-stale='false'` attr.
18
- *
19
- * Test scope:
20
- * 1. Chip exists with data-freshness-chip attribute.
21
- * 2. At fresh state, data-freshness-chip-stale === 'false'.
22
- * 3. At fresh state, text starts with "live · " (prefix word "live").
23
- * 4. At fresh state, color is gray (computed color contains 156 or
24
- * similar Tailwind gray RGB).
25
- * 5. Source code regression: the new JSX uses {stale ? 'lag' : 'live'}
26
- * ternary (verified by grep'ing the rendered HTML node structure
27
- * — the ternary always renders ONE of the two words, so the chip
28
- * text should contain a single 3-letter prefix word).
29
- * 6. R271 regression: legend row hitbox y=21 (working row).
30
- */
31
- import { chromium } from 'playwright';
32
- import { readFileSync } from 'node:fs';
33
-
34
- const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
35
- const browser = await chromium.launch({ headless: true });
36
- const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
37
- await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
38
- await ctx.addInitScript(() => {
39
- try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
40
- });
41
- const fresh = new Date(Date.now() - 60 * 1000).toISOString();
42
- await ctx.route('**/api/hub/status*', async (route) => {
43
- const r = await route.fetch();
44
- const b = await r.json();
45
- const nid = (b.sessions || [])[0]?.network_id || 'default';
46
- const mk = (alias) => ({
47
- alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
48
- network_id: nid, project_dir: null,
49
- created_at: fresh, updated_at: fresh, last_seen_at: fresh,
50
- });
51
- await route.fulfill({ response: r, json: { ...b, sessions: [
52
- mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
53
- ] } });
54
- });
55
- await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
56
- await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
57
-
58
- const page = await ctx.newPage();
59
- await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
60
- await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
61
- await page.waitForSelector('[data-freshness-chip]', { timeout: 10000 });
62
- await page.waitForSelector('[data-legend-status="working"] rect', { timeout: 10000 });
63
- await page.waitForTimeout(300);
64
-
65
- const probe = await page.evaluate(() => {
66
- const chip = document.querySelector('[data-freshness-chip]');
67
- const legendRect = document.querySelector('[data-legend-status="working"] rect');
68
- return {
69
- chipText: chip ? chip.textContent.trim() : null,
70
- chipStale: chip ? chip.getAttribute('data-freshness-chip-stale') : null,
71
- chipColor: chip ? window.getComputedStyle(chip).color : null,
72
- legendRectY: legendRect ? +legendRect.getAttribute('y') : null,
73
- };
74
- });
75
- await browser.close();
76
-
77
- // Fresh state: text should start with "live · " and stale attr === 'false'.
78
- // gray chip color: text-gray-400 in cyber theme = rgb(156, 163, 175) or oklab(...) → contains "156" or "rgb".
79
- const results = {
80
- chip_present: probe.chipText !== null,
81
- chip_is_fresh: probe.chipStale === 'false',
82
- chip_text_starts_with_live: probe.chipText != null && probe.chipText.startsWith('live · '),
83
- chip_text_no_lag_when_fresh: probe.chipText != null && !probe.chipText.startsWith('lag · '),
84
- // Fresh color in cyber is text-gray-400. Tailwind v4 renders modern
85
- // browsers as oklab()/lab()/oklch(). Accept any computed-color
86
- // function format that indicates a color was actually applied.
87
- chip_color_set: probe.chipColor != null
88
- && /(rgb|oklab|lab|oklch|hsl)/.test(probe.chipColor),
89
- r271_legend_rect_y_21: probe.legendRectY === 21,
90
- };
91
- const ok = Object.values(results).every(Boolean);
92
- console.log(`${ok ? '✅' : '❌'} freshness text color coherence:`, JSON.stringify(results),
93
- '\n chip text:', JSON.stringify(probe.chipText),
94
- '\n chip stale attr:', probe.chipStale,
95
- '\n chip color:', probe.chipColor,
96
- '\n legend rect y:', probe.legendRectY);
97
- process.exit(ok ? 0 : 1);
@@ -1,96 +0,0 @@
1
- /* Round 131 verification: outer-ring orbit period buckets on
2
- * workingCount, mirroring R84's hub-breath cadence. Idle fleet
3
- * keeps the original 16s "calm sweep"; as work piles up the
4
- * orbit accelerates to 14 / 12 / 10s. Both motion layers now
5
- * read the same underlying "is the network busy" signal.
6
- *
7
- * Bucket boundaries identical to R84 (line ~2702):
8
- * workingCount === 0 → bucket 0, dur 16s
9
- * workingCount in [1, 2] → bucket 1, dur 14s
10
- * workingCount in [3, 5] → bucket 2, dur 12s
11
- * workingCount >= 6 → bucket 3, dur 10s
12
- *
13
- * R50 only renders on cyber theme (light is skipped to keep the
14
- * white surface clean). All probes use cyber.
15
- *
16
- * Probe fleet sizes pick one representative per bucket.
17
- */
18
- import { chromium } from 'playwright';
19
- import { readFileSync } from 'node:fs';
20
-
21
- const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
22
-
23
- async function probe(workingCount, totalCount) {
24
- const browser = await chromium.launch({ headless: true });
25
- const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
26
- await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
27
- await ctx.addInitScript(() => {
28
- try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
29
- });
30
- const fresh = new Date(Date.now() - 60 * 1000).toISOString();
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 sessions = [];
36
- for (let i = 0; i < totalCount; i++) {
37
- sessions.push({
38
- alias: `node${i}`,
39
- status: i < workingCount ? 'working' : 'idle',
40
- model: 'claude-opus-4', runtime: 'cli-claude-code',
41
- network_id: nid, project_dir: null,
42
- created_at: fresh, updated_at: fresh, last_seen_at: fresh,
43
- });
44
- }
45
- await route.fulfill({ response: r, json: { ...b, sessions } });
46
- });
47
- await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
48
- await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
49
-
50
- const page = await ctx.newPage();
51
- await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
52
- await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n, totalCount, { timeout: 30000 });
53
- await page.waitForTimeout(400);
54
-
55
- const out = await page.evaluate(() => {
56
- const orbits = [...document.querySelectorAll('[data-topo-orbit-bucket]')];
57
- return orbits.map(g => ({
58
- bucket: g.getAttribute('data-topo-orbit-bucket'),
59
- dur: g.getAttribute('data-topo-orbit-dur'),
60
- animDur: g.querySelector('animateTransform')?.getAttribute('dur'),
61
- }));
62
- });
63
-
64
- await browser.close();
65
- return out;
66
- }
67
-
68
- // Pick one from each bucket
69
- const b0 = await probe(0, 4); // workingCount=0 → bucket 0, dur 16
70
- const b1 = await probe(2, 4); // workingCount=2 → bucket 1, dur 14
71
- const b2 = await probe(4, 5); // workingCount=4 → bucket 2, dur 12
72
- const b3 = await probe(7, 8); // workingCount=7 → bucket 3, dur 10
73
-
74
- const allConsistent = (orbits, bucket, dur) =>
75
- orbits.length === 4 &&
76
- orbits.every(o => o.bucket === String(bucket) && o.dur === String(dur) && o.animDur === `${dur}s`);
77
-
78
- const results = {
79
- b0_fourOrbits: b0.length === 4,
80
- b0_bucket0_16s: allConsistent(b0, 0, 16),
81
- b1_bucket1_14s: allConsistent(b1, 1, 14),
82
- b2_bucket2_12s: allConsistent(b2, 2, 12),
83
- b3_bucket3_10s: allConsistent(b3, 3, 10),
84
- // dur strictly decreases as busy increases
85
- dur_monotonic:
86
- Number(b0[0]?.dur) > Number(b1[0]?.dur) &&
87
- Number(b1[0]?.dur) > Number(b2[0]?.dur) &&
88
- Number(b2[0]?.dur) > Number(b3[0]?.dur),
89
- };
90
- const ok = Object.values(results).every(Boolean);
91
- console.log(`${ok ? '✅' : '❌'} orbit busyness:`, JSON.stringify(results),
92
- `\n b0(0w/4)=`, b0,
93
- `\n b1(2w/4)=`, b1,
94
- `\n b2(4w/5)=`, b2,
95
- `\n b3(7w/8)=`, b3);
96
- process.exit(ok ? 0 : 1);
@@ -1,124 +0,0 @@
1
- /* Round 216 verification: outer-ring orbit particle opacity scales
2
- * with busy bucket alongside R131's speed scaling. Idle fleet → dim
3
- * particles; busy fleet → bright. transitions on opacity 300ms.
4
- *
5
- * Bucket map: workingCount 0 → busy 0; 1-2 → 1; 3-5 → 2; 6+ → 3
6
- * opacity [0.5, 0.7, 0.85, 1.0]
7
- *
8
- * Test scenarios (cyber dark, orbit particles only render in dark):
9
- * A. 0 working sessions (4 idle): busy=0, opacity≈0.5
10
- * B. 2 working sessions (2 working+2 idle): busy=1, opacity≈0.7
11
- * C. 6 working sessions: busy=3, opacity≈1.0
12
- * Every orbit particle has style.transition: 'opacity 300ms'
13
- * Every orbit <g> exposes data-topo-orbit-opacity matching the
14
- * bucket value (test verifies derivation + DOM agreement).
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 browser = await chromium.launch({ headless: true });
21
-
22
- async function pageFor(workingN) {
23
- const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
24
- await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
25
- await ctx.addInitScript(() => {
26
- try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
27
- });
28
- const fresh = new Date(Date.now() - 60 * 1000).toISOString();
29
- const totalSessions = Math.max(workingN, 4);
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
- const sessions = [];
40
- for (let i = 0; i < totalSessions; i++) {
41
- sessions.push(mk(`node-${(i + 1).toString().padStart(2, '0')}`, i < workingN ? 'working' : 'idle'));
42
- }
43
- await route.fulfill({ response: r, json: { ...b, sessions } });
44
- });
45
- await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
46
- await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
47
- const page = await ctx.newPage();
48
- await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
49
- await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n, totalSessions, { timeout: 30000 });
50
- await page.waitForSelector('[data-topo-orbit-bucket]', { timeout: 10000, state: 'attached' });
51
- await page.waitForTimeout(300);
52
- return page;
53
- }
54
-
55
- const probe = async (page) => page.evaluate(() => {
56
- return [...document.querySelectorAll('[data-topo-orbit-bucket]')].map(el => {
57
- const inner = el.querySelector('circle');
58
- return {
59
- bucket: el.getAttribute('data-topo-orbit-bucket'),
60
- bucketOpacity: el.getAttribute('data-topo-orbit-opacity'),
61
- dur: el.getAttribute('data-topo-orbit-dur'),
62
- circleOpacity: parseFloat(inner?.getAttribute('opacity') || ''),
63
- transition: inner?.style.transition,
64
- };
65
- });
66
- });
67
-
68
- // Scenario A: 0 working → busy=0, opacity=0.5
69
- const pageA = await pageFor(0);
70
- const probeA = await probe(pageA);
71
- await pageA.close();
72
-
73
- // Scenario B: 2 working → busy=1, opacity=0.7
74
- const pageB = await pageFor(2);
75
- const probeB = await probe(pageB);
76
- await pageB.close();
77
-
78
- // Scenario C: 6 working → busy=3, opacity=1.0
79
- const pageC = await pageFor(6);
80
- const probeC = await probe(pageC);
81
- await pageC.close();
82
- await browser.close();
83
-
84
- const near = (got, want, tol = 0.05) => Math.abs(got - want) < tol;
85
- const hasOpacityT = (s) => /opacity\s+(?:300ms|0\.3s)/.test(s || '');
86
-
87
- const results = {
88
- // 4 orbit particles in each scenario (R50 phase array of 4)
89
- A_four_particles: probeA.length === 4,
90
- B_four_particles: probeB.length === 4,
91
- C_four_particles: probeC.length === 4,
92
-
93
- // Scenario A — idle fleet (busy=0)
94
- A_bucket_0: probeA.every(p => p.bucket === '0'),
95
- A_opacity_half: probeA.every(p => near(p.circleOpacity, 0.5)),
96
- A_data_attr_matches: probeA.every(p => parseFloat(p.bucketOpacity) === p.circleOpacity),
97
-
98
- // Scenario B — light load (busy=1)
99
- B_bucket_1: probeB.every(p => p.bucket === '1'),
100
- B_opacity_0p7: probeB.every(p => near(p.circleOpacity, 0.7)),
101
-
102
- // Scenario C — heavy load (busy=3)
103
- C_bucket_3: probeC.every(p => p.bucket === '3'),
104
- C_opacity_full: probeC.every(p => near(p.circleOpacity, 1.0)),
105
-
106
- // Brightness actually grew across buckets
107
- brightness_grew: probeA[0].circleOpacity < probeB[0].circleOpacity
108
- && probeB[0].circleOpacity < probeC[0].circleOpacity,
109
-
110
- // Transitions present in every particle of every scenario
111
- A_all_have_transition: probeA.every(p => hasOpacityT(p.transition)),
112
- B_all_have_transition: probeB.every(p => hasOpacityT(p.transition)),
113
- C_all_have_transition: probeC.every(p => hasOpacityT(p.transition)),
114
-
115
- // R131's existing speed scaling still derives — dur shortens as
116
- // bucket grows
117
- speed_scales: parseFloat(probeA[0].dur || '0') > parseFloat(probeC[0].dur || '99'),
118
- };
119
- const ok = Object.values(results).every(Boolean);
120
- console.log(`${ok ? '✅' : '❌'} orbit opacity bucket:`, JSON.stringify(results),
121
- `\n probeA (0 working):`, probeA.slice(0, 1),
122
- `\n probeB (2 working):`, probeB.slice(0, 1),
123
- `\n probeC (6 working):`, probeC.slice(0, 1));
124
- process.exit(ok ? 0 : 1);