@sleep2agi/agent-network-dashboard 0.4.6-preview.296 → 0.4.6-preview.298

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 (140) 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 +1 -1
  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/{10fd_xc2wby20.js → 0q.5.qo_-5x3p.js} +1 -1
  130. package/.next/static/chunks/{0a-oa4iszsfip.js → 0u4iv522~iwlj.js} +1 -1
  131. package/.next/static/chunks/{17tm33giyx7jp.js → 0uxtf~vrod297.js} +1 -1
  132. package/.next/trace +2 -2
  133. package/.next/trace-build +1 -1
  134. package/app/components/TopoGraph.tsx +39 -8
  135. package/package.json +1 -1
  136. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  137. package/scripts/topo-starfield-density-test.mjs +89 -0
  138. /package/.next/static/{ZEnlqgeA3IXLqAHX-wPVj → qiBZObTArpixjYUPlKPKu}/_buildManifest.js +0 -0
  139. /package/.next/static/{ZEnlqgeA3IXLqAHX-wPVj → qiBZObTArpixjYUPlKPKu}/_clientMiddlewareManifest.js +0 -0
  140. /package/.next/static/{ZEnlqgeA3IXLqAHX-wPVj → qiBZObTArpixjYUPlKPKu}/_ssgManifest.js +0 -0
package/.next/trace-build CHANGED
@@ -1 +1 @@
1
- [{"name":"run-turbopack","duration":5555922,"timestamp":108076747916,"id":14,"parentId":1,"tags":{},"startTime":1778916048994,"traceId":"654647ebd82a1ab4"},{"name":"turbopack-build-events","duration":101,"timestamp":108077065244,"id":15,"parentId":1,"tags":{},"startTime":1778916049312,"traceId":"654647ebd82a1ab4"},{"name":"run-typescript","duration":9383177,"timestamp":108082313386,"id":16,"parentId":1,"tags":{},"startTime":1778916054560,"traceId":"654647ebd82a1ab4"},{"name":"static-check","duration":882222,"timestamp":108091894166,"id":19,"parentId":1,"tags":{},"startTime":1778916064140,"traceId":"654647ebd82a1ab4"},{"name":"static-generation","duration":615386,"timestamp":108092790998,"id":105,"parentId":1,"tags":{},"startTime":1778916065037,"traceId":"654647ebd82a1ab4"},{"name":"telemetry-flush","duration":25963,"timestamp":108093442748,"id":114,"parentId":1,"tags":{},"startTime":1778916065689,"traceId":"654647ebd82a1ab4"},{"name":"next-build","duration":16938623,"timestamp":108076530129,"id":1,"tags":{"buildMode":"default","version":"16.2.3","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1778916048776,"traceId":"654647ebd82a1ab4"}]
1
+ [{"name":"run-turbopack","duration":5815298,"timestamp":108560591191,"id":14,"parentId":1,"tags":{},"startTime":1778916532837,"traceId":"90d4c1d828320960"},{"name":"turbopack-build-events","duration":73,"timestamp":108560871479,"id":15,"parentId":1,"tags":{},"startTime":1778916533118,"traceId":"90d4c1d828320960"},{"name":"run-typescript","duration":9215882,"timestamp":108566416380,"id":16,"parentId":1,"tags":{},"startTime":1778916538663,"traceId":"90d4c1d828320960"},{"name":"static-check","duration":808482,"timestamp":108575830962,"id":19,"parentId":1,"tags":{},"startTime":1778916548077,"traceId":"90d4c1d828320960"},{"name":"static-generation","duration":572969,"timestamp":108576651563,"id":105,"parentId":1,"tags":{},"startTime":1778916548898,"traceId":"90d4c1d828320960"},{"name":"telemetry-flush","duration":38814,"timestamp":108577255792,"id":114,"parentId":1,"tags":{},"startTime":1778916549502,"traceId":"90d4c1d828320960"},{"name":"next-build","duration":16931194,"timestamp":108560363442,"id":1,"tags":{"buildMode":"default","version":"16.2.3","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1778916532610,"traceId":"90d4c1d828320960"}]
@@ -2810,19 +2810,31 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2810
2810
  the hub click outright. */}
2811
2811
  <circle cx={cx} cy={cy} r="330" fill="url(#topo-radar)" style={{ pointerEvents: 'none' }} />
2812
2812
 
2813
- {/* Round 45: subtle star field — 24 deterministic dots scattered
2813
+ {/* Round 45: subtle star field — deterministic dots scattered
2814
2814
  across the canvas give the radar bg some depth. Skipped on
2815
- light theme so the white surface stays clean. */}
2815
+ light theme so the white surface stays clean.
2816
+ Round 291 / Loop: starfield dot count 28 → 14 (50%
2817
+ reduction). Post-R290 inner radar ring retirement the
2818
+ canvas has cleared meaningfully — sweep + 3 radar rings
2819
+ + tier guides + nodes + edges are doing the visual work.
2820
+ The starfield's role is atmospheric depth, not
2821
+ information; cutting density by half preserves the
2822
+ "space/radar" feel while removing decoration the eye
2823
+ has to skip. Same R275-R281 减法 family idiom as the
2824
+ orbit / halo / spoke retirements; same R290 pivot back
2825
+ to subtractive register. data-topo-starfield-dot
2826
+ attribute makes the dots probe-able for the regression
2827
+ test. */}
2816
2828
  {!isLight && (
2817
- <g opacity="0.5" style={{ pointerEvents: 'none' }}>
2818
- {Array.from({ length: 28 }).map((_, i) => {
2829
+ <g opacity="0.5" style={{ pointerEvents: 'none' }} data-topo-starfield>
2830
+ {Array.from({ length: 14 }).map((_, i) => {
2819
2831
  // Deterministic pseudo-random scatter so positions are
2820
2832
  // stable between renders (no JS hydration mismatch).
2821
2833
  const seed = i * 9301 + 49297;
2822
2834
  const x = ((seed * 13) % 1000);
2823
2835
  const y = ((seed * 7) % 680);
2824
2836
  const r = (i % 3 === 0) ? 1.2 : 0.7;
2825
- return <circle key={i} cx={x} cy={y} r={r} fill="#a5b4fc" opacity={0.35 + (i % 4) * 0.05} />;
2837
+ return <circle key={i} cx={x} cy={y} r={r} fill="#a5b4fc" opacity={0.35 + (i % 4) * 0.05} data-topo-starfield-dot={i} />;
2826
2838
  })}
2827
2839
  </g>
2828
2840
  )}
@@ -2872,9 +2884,28 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2872
2884
 
2873
2885
  {/* radar rings — pure decoration at fixed radii, independent of
2874
2886
  node positions so the radar aesthetic is preserved across tier
2875
- changes. */}
2876
- {[90, 170, 250, 330].map(radius => (
2877
- <circle key={radius} cx={cx} cy={cy} r={radius} fill="none" stroke={pal.ringStroke} strokeWidth="1" opacity={isLight ? 0.6 : 0.35} />
2887
+ changes.
2888
+ Round 290 / Loop: drop the innermost radar ring at r=90.
2889
+ That ring sat ~66px outside the hub (hub radius 24, halo
2890
+ r=18), in the exact zone R276 (orbit particles) / R278
2891
+ (working halo) / R280 (backdrop spokes) cleared during
2892
+ the R275-R281 减法 arc. Post-cleanup the lone r=90 ring
2893
+ read as a leftover decorative loop hugging the hub — a
2894
+ visual element with no remaining sibling to anchor.
2895
+ Dropping it returns to the subtractive register after
2896
+ R282-R289's 8 加法 rounds and lets the hub breathe. The
2897
+ outer three rings (170 / 250 / 330) still carry the
2898
+ radar aesthetic across the canvas. New data-topo-radar-
2899
+ ring attribute exposes each remaining ring radius for
2900
+ test probing. */}
2901
+ {[170, 250, 330].map(radius => (
2902
+ <circle
2903
+ key={radius}
2904
+ cx={cx} cy={cy} r={radius}
2905
+ fill="none" stroke={pal.ringStroke} strokeWidth="1"
2906
+ opacity={isLight ? 0.6 : 0.35}
2907
+ data-topo-radar-ring={radius}
2908
+ />
2878
2909
  ))}
2879
2910
 
2880
2911
  {/* Round 54 / Loop: tier-radius guide rings. The radar rings above
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.4.6-preview.296",
3
+ "version": "0.4.6-preview.298",
4
4
  "description": "Agent Network Dashboard — Web UI for managing AI Agent networks",
5
5
  "scripts": {
6
6
  "dev": "next dev",
@@ -0,0 +1,89 @@
1
+ /* Round 290 verification: innermost radar ring at r=90 is retired.
2
+ * Post-R290 only the outer three rings (170 / 250 / 330) remain.
3
+ * Returns to 减法 register after R282-R289's 8 加法 rounds.
4
+ *
5
+ * Contract:
6
+ * - data-topo-radar-ring attributes resolve to ['170', '250', '330'].
7
+ * - NO element has data-topo-radar-ring="90".
8
+ * - All three remaining rings preserve fill='none' + stroke + the
9
+ * pal.ringStroke colour (regression on stroke styling).
10
+ * - R289 watermark letterSpacing + R287 minimap rect + R283 monogram
11
+ * stroke intact (regression).
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 browser = await chromium.launch({ headless: true });
18
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
22
+ });
23
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
24
+ await ctx.route('**/api/hub/status*', async (route) => {
25
+ const r = await route.fetch();
26
+ const b = await r.json();
27
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
28
+ const mk = (alias, model) => ({
29
+ alias, status: 'working', model, runtime: 'claude-code-cli',
30
+ network_id: nid, project_dir: null,
31
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
32
+ });
33
+ await route.fulfill({ response: r, json: { ...b, sessions: [
34
+ mk('alpha', 'claude-opus-4'),
35
+ mk('beta', 'gpt-4o'),
36
+ ] } });
37
+ });
38
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
39
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
40
+
41
+ const page = await ctx.newPage();
42
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
43
+ await page.waitForSelector('[data-topo-radar-ring]', { timeout: 15000 });
44
+ await page.waitForSelector('[data-topo-brand-watermark]', { timeout: 10000 });
45
+ await page.waitForTimeout(300);
46
+
47
+ const probe = await page.evaluate(() => {
48
+ const rings = [...document.querySelectorAll('[data-topo-radar-ring]')];
49
+ const sortedRadii = rings.map(r => r.getAttribute('data-topo-radar-ring')).sort((a, b) => +a - +b);
50
+ const innerRing90 = document.querySelector('[data-topo-radar-ring="90"]');
51
+ const allHaveFillNone = rings.every(r => r.getAttribute('fill') === 'none');
52
+ const allHaveStroke = rings.every(r => r.getAttribute('stroke'));
53
+ const wm = document.querySelector('[data-topo-brand-watermark]');
54
+ const wmLs = wm ? getComputedStyle(wm).letterSpacing : null;
55
+ const alphaNode = document.querySelector('g[data-node="alpha"]');
56
+ const alphaCircles = alphaNode ? [...alphaNode.querySelectorAll('circle')] : [];
57
+ const alphaAvatarCircle = alphaCircles
58
+ .map(c => ({ r: +c.getAttribute('r') || 0, sw: c.getAttribute('stroke-width'), isBadge: c.hasAttribute('data-runtime-badge') }))
59
+ .filter(c => c.r > 0 && c.r < 18 && !c.isBadge)
60
+ .sort((a, b) => b.r - a.r)[0];
61
+ return {
62
+ radii: sortedRadii,
63
+ ringCount: rings.length,
64
+ innerRing90Present: innerRing90 !== null,
65
+ allFillNone: allHaveFillNone,
66
+ allStroke: allHaveStroke,
67
+ watermarkLetterSpacing: wmLs,
68
+ alphaAvatarStroke: alphaAvatarCircle?.sw ?? null,
69
+ };
70
+ });
71
+ await browser.close();
72
+
73
+ const wmLsVal = parseFloat(probe.watermarkLetterSpacing) || 0;
74
+
75
+ const results = {
76
+ three_rings_remain: probe.ringCount === 3,
77
+ radii_are_170_250_330: JSON.stringify(probe.radii) === JSON.stringify(['170', '250', '330']),
78
+ r90_ring_absent: probe.innerRing90Present === false,
79
+ all_rings_fill_none: probe.allFillNone,
80
+ all_rings_have_stroke: probe.allStroke,
81
+ r289_watermark_ls_kept: wmLsVal >= 0.4 && wmLsVal <= 0.7,
82
+ r283_monogram_stroke_kept: probe.alphaAvatarStroke === '1.5',
83
+ };
84
+ const ok = Object.values(results).every(Boolean);
85
+ console.log(`${ok ? '✅' : '❌'} inner radar ring retired:`, JSON.stringify(results),
86
+ '\n remaining radii:', probe.radii,
87
+ '\n watermark letter-spacing (R289):', probe.watermarkLetterSpacing,
88
+ '\n alpha avatar stroke (R283):', probe.alphaAvatarStroke);
89
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,89 @@
1
+ /* Round 291 verification: starfield decorative dot count 28 → 14
2
+ * (50% reduction). Continuation of the 减法 register R290 pivoted
3
+ * back to. Atmospheric depth preserved; visual density cut in
4
+ * half so the eye has less ambient decoration to skip past.
5
+ *
6
+ * Contract:
7
+ * - [data-topo-starfield] container exists (cyber only).
8
+ * - 14 [data-topo-starfield-dot] children (was 28).
9
+ * - opacity 0.5 on the container unchanged (regression).
10
+ * - Light theme: starfield NOT rendered (regression — pre-R291
11
+ * behaviour, conditional on !isLight).
12
+ * - R290 radar ring set still ['170','250','330'] (regression).
13
+ * - R289 watermark letterSpacing + R283 monogram stroke intact.
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 browser = await chromium.launch({ headless: true });
20
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
21
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
22
+ await ctx.addInitScript(() => {
23
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
24
+ });
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ const r = await route.fetch();
28
+ const b = await r.json();
29
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
30
+ const mk = (alias, model) => ({
31
+ alias, status: 'working', model, runtime: 'claude-code-cli',
32
+ network_id: nid, project_dir: null,
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
34
+ });
35
+ await route.fulfill({ response: r, json: { ...b, sessions: [
36
+ mk('alpha', 'claude-opus-4'),
37
+ mk('beta', 'gpt-4o'),
38
+ ] } });
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
+
43
+ const page = await ctx.newPage();
44
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
45
+ await page.waitForSelector('[data-topo-starfield]', { timeout: 15000 });
46
+ await page.waitForSelector('[data-topo-brand-watermark]', { timeout: 10000 });
47
+ await page.waitForTimeout(300);
48
+
49
+ const probe = await page.evaluate(() => {
50
+ const sf = document.querySelector('[data-topo-starfield]');
51
+ const dots = document.querySelectorAll('[data-topo-starfield-dot]');
52
+ const opacity = sf?.getAttribute('opacity') ?? null;
53
+ const rings = [...document.querySelectorAll('[data-topo-radar-ring]')]
54
+ .map(r => r.getAttribute('data-topo-radar-ring')).sort((a, b) => +a - +b);
55
+ const wm = document.querySelector('[data-topo-brand-watermark]');
56
+ const wmLs = wm ? getComputedStyle(wm).letterSpacing : null;
57
+ const alphaNode = document.querySelector('g[data-node="alpha"]');
58
+ const alphaCircles = alphaNode ? [...alphaNode.querySelectorAll('circle')] : [];
59
+ const alphaAvatarCircle = alphaCircles
60
+ .map(c => ({ r: +c.getAttribute('r') || 0, sw: c.getAttribute('stroke-width'), isBadge: c.hasAttribute('data-runtime-badge') }))
61
+ .filter(c => c.r > 0 && c.r < 18 && !c.isBadge)
62
+ .sort((a, b) => b.r - a.r)[0];
63
+ return {
64
+ starfieldPresent: sf !== null,
65
+ dotCount: dots.length,
66
+ opacity,
67
+ radarRings: rings,
68
+ watermarkLs: wmLs,
69
+ alphaAvatarStroke: alphaAvatarCircle?.sw ?? null,
70
+ };
71
+ });
72
+ await browser.close();
73
+
74
+ const wmLsVal = parseFloat(probe.watermarkLs) || 0;
75
+
76
+ const results = {
77
+ starfield_container_present: probe.starfieldPresent,
78
+ fourteen_dots: probe.dotCount === 14,
79
+ opacity_0_5_kept: probe.opacity === '0.5',
80
+ r290_three_rings: JSON.stringify(probe.radarRings) === JSON.stringify(['170', '250', '330']),
81
+ r289_watermark_ls_kept: wmLsVal >= 0.4 && wmLsVal <= 0.7,
82
+ r283_monogram_stroke_kept: probe.alphaAvatarStroke === '1.5',
83
+ };
84
+ const ok = Object.values(results).every(Boolean);
85
+ console.log(`${ok ? '✅' : '❌'} starfield density:`, JSON.stringify(results),
86
+ '\n dot count (expect 14):', probe.dotCount,
87
+ '\n opacity:', probe.opacity,
88
+ '\n radar rings (R290):', probe.radarRings);
89
+ process.exit(ok ? 0 : 1);