@sleep2agi/agent-network-dashboard 0.5.2-preview.2 → 0.5.2-preview.25

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 (161) 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 +7 -7
  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/[root-of-the-server]__0sv~g.o._.js +1 -1
  121. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +1 -1
  122. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  123. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  124. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  125. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  126. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  127. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  128. package/.next/server/middleware-build-manifest.js +3 -3
  129. package/.next/server/pages/404.html +1 -1
  130. package/.next/server/pages/500.html +1 -1
  131. package/.next/static/chunks/0n06tze_y9dlf.js +1 -0
  132. package/.next/static/chunks/0u-~k4hsu~8wq.js +4 -0
  133. package/.next/static/chunks/{0slrhg-~r8l06.js → 0zil_-x~u.a7r.js} +1 -1
  134. package/.next/static/chunks/13l51qkvb6a09.js +1 -0
  135. package/.next/trace +2 -2
  136. package/.next/trace-build +1 -1
  137. package/app/components/TopoGraph.tsx +405 -37
  138. package/package.json +1 -1
  139. package/screenshots/v0.10.4-150-orphans/after.png +0 -0
  140. package/scripts/p150-orphan-layout-screenshot.mjs +82 -0
  141. package/scripts/topo-edge-badge-hot-glow-test.mjs +101 -0
  142. package/scripts/topo-edge-particle-opacity-lift-test.mjs +109 -0
  143. package/scripts/topo-group-label-glow-test.mjs +104 -0
  144. package/scripts/topo-hub-digit-glow-test.mjs +105 -0
  145. package/scripts/topo-legend-panel-title-fw-test.mjs +95 -0
  146. package/scripts/topo-legend-pin-ring-glow-test.mjs +105 -0
  147. package/scripts/topo-legend-row-text-cadence-test.mjs +93 -0
  148. package/scripts/topo-legend-row-tint-cadence-test.mjs +92 -0
  149. package/scripts/topo-minimap-dot-lift-test.mjs +115 -0
  150. package/scripts/topo-minimap-zoom-glow-test.mjs +86 -0
  151. package/scripts/topo-recent-panel-title-fw-test.mjs +106 -0
  152. package/scripts/topo-recent-row-freshness-glow-test.mjs +100 -0
  153. package/scripts/topo-recent-row-text-cadence-test.mjs +99 -0
  154. package/scripts/topo-recent-row-tint-cadence-test.mjs +96 -0
  155. package/scripts/topo-recent-row-ts-lift-test.mjs +97 -0
  156. package/.next/static/chunks/08ja1js.s74hj.js +0 -1
  157. package/.next/static/chunks/0cstcv2xh_f0p.js +0 -1
  158. package/.next/static/chunks/137a8ptkr6xg1.js +0 -4
  159. /package/.next/static/{i-jEoAS_A1mp9AfWPF-GU → 1r_vZ0kOyyWXIRiRstneR}/_buildManifest.js +0 -0
  160. /package/.next/static/{i-jEoAS_A1mp9AfWPF-GU → 1r_vZ0kOyyWXIRiRstneR}/_clientMiddlewareManifest.js +0 -0
  161. /package/.next/static/{i-jEoAS_A1mp9AfWPF-GU → 1r_vZ0kOyyWXIRiRstneR}/_ssgManifest.js +0 -0
@@ -0,0 +1,82 @@
1
+ /* v0.10.4 #150 — orphan layout screenshot evidence.
2
+ *
3
+ * Fixture: 3 prefix groups (5+3+4 = 12 grouped) + 5 distinct-prefix
4
+ * singletons (orphans). Pre-#150 the singletons appeared scattered
5
+ * in centred bands between cluster boxes. Post-#150 they all bundle
6
+ * into one "其他" cluster box at the bottom. */
7
+ import { chromium } from 'playwright';
8
+ import { readFileSync } from 'node:fs';
9
+
10
+ const out = process.argv[2] || 'screenshots/v0.10.4-150-orphans/after';
11
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
12
+ const browser = await chromium.launch({ headless: true });
13
+ const ctx = await browser.newContext({ viewport: { width: 1600, height: 1500 }, deviceScaleFactor: 2 });
14
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
15
+ await ctx.addInitScript(() => {
16
+ try {
17
+ localStorage.setItem('anet-theme', 'cyber');
18
+ sessionStorage.setItem('anet_v3_auth', '1');
19
+ // Force grid layout so the orphan band is visible
20
+ localStorage.setItem('anet-topo-layout', 'grid');
21
+ } catch {}
22
+ });
23
+ const fresh = new Date(Date.now() - 60_000).toISOString();
24
+ const sessions = [];
25
+ // Group 1: ai-insight × 5
26
+ for (let i = 1; i <= 5; i++) sessions.push({
27
+ alias: `ai-insight-node-${i}`, status: i === 1 ? 'working' : 'idle',
28
+ model: 'claude-opus-4', runtime: 'claude-code-cli',
29
+ network_id: 'default', project_dir: null,
30
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
31
+ });
32
+ // Group 2: agent-network-dashboard × 3
33
+ for (let i = 1; i <= 3; i++) sessions.push({
34
+ alias: `agent-network-dashboard-${i}`, status: i === 1 ? 'working' : 'idle',
35
+ model: 'gpt-4o', runtime: 'codex-sdk',
36
+ network_id: 'default', project_dir: null,
37
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
38
+ });
39
+ // Group 3: p-station × 4
40
+ for (let i = 1; i <= 4; i++) sessions.push({
41
+ alias: `p-station-worker-${i}`, status: i === 1 ? 'working' : 'idle',
42
+ model: 'minimax/abab6', runtime: 'http-api',
43
+ network_id: 'default', project_dir: null,
44
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
45
+ });
46
+ // Orphans: 5 distinct-prefix singletons — should bundle into "其他" bottom band
47
+ ['monitor-prod', 'dispatcher-bot', 'runner-helper', 'oracle-svc', 'cron-worker'].forEach((alias, idx) => {
48
+ sessions.push({
49
+ alias, status: idx % 2 === 0 ? 'working' : 'idle',
50
+ model: 'claude-sonnet-4', runtime: 'claude-agent-sdk',
51
+ network_id: 'default', project_dir: null,
52
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
53
+ });
54
+ });
55
+ await ctx.route('**/api/hub/status*', async (route) => {
56
+ const r = await route.fetch();
57
+ const b = await r.json();
58
+ await route.fulfill({ response: r, json: { ...b, sessions } });
59
+ });
60
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
61
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
62
+ const page = await ctx.newPage();
63
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
64
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length >= 17, { timeout: 30000 });
65
+ await page.waitForTimeout(800);
66
+ const box = await page.evaluate(() => {
67
+ const chrome = document.querySelector('[data-topo-chrome]');
68
+ let card = chrome?.parentElement;
69
+ while (card && !card.querySelector(':scope > svg')) card = card?.parentElement;
70
+ if (!card) return null;
71
+ card.scrollIntoView({ block: 'start' });
72
+ return new Promise(resolve => requestAnimationFrame(() => {
73
+ const r = card.getBoundingClientRect();
74
+ resolve({ x: Math.max(0, r.x), y: Math.max(0, r.y), width: r.width, height: r.height });
75
+ }));
76
+ });
77
+ await page.waitForTimeout(400);
78
+ if (box && box.width > 100) {
79
+ await page.screenshot({ path: `${out}.png`, clip: { x: box.x, y: box.y, width: box.width, height: Math.min(box.height, 1200) } });
80
+ }
81
+ console.log(`✅ wrote ${out}.png`);
82
+ await browser.close();
@@ -0,0 +1,101 @@
1
+ /* Round 480 verification: edge badge circle gains filter: drop-
2
+ * shadow glow on isHot (link.count >= 10). 5th anchor in the
3
+ * R476/R477/R478/R479 drop-shadow family — first traffic-volume-
4
+ * gated variant.
5
+ *
6
+ * Contract:
7
+ * - cold edge (count < 10): data-edge-badge-glow='false' AND
8
+ * computed filter === 'none'
9
+ * - hot edge (count >= 10): glow='true' AND computed filter
10
+ * starts with 'drop-shadow' using hotStroke @ 0x80 alpha
11
+ * - source-file conditional 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 nowIso = () => new Date().toISOString();
18
+ const sessionFresh = new Date(Date.now() - 60 * 1000).toISOString();
19
+
20
+ const browser = await chromium.launch({ headless: true });
21
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
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: sessionFresh, updated_at: sessionFresh, last_seen_at: sessionFresh,
38
+ });
39
+ await route.fulfill({ response: r, json: { ...b, sessions: [
40
+ mk('a·1', 'working'), mk('a·2', 'idle'), mk('b·1', 'working'),
41
+ ] } });
42
+ });
43
+ // Build messages: 12 from a·1 → a·2 (HOT, count=12 >= 10)
44
+ // 2 from b·1 → a·1 (cold, count=2 < 10)
45
+ const hotMessages = [];
46
+ for (let i = 0; i < 12; i++) {
47
+ hotMessages.push({
48
+ id: `hot-${i}`, from_alias: 'a·1', to_alias: 'a·2',
49
+ content: `m${i}`, created_at: nowIso(),
50
+ });
51
+ }
52
+ const coldMessages = [
53
+ { id: 'c1', from_alias: 'b·1', to_alias: 'a·1', content: 'c1', created_at: nowIso() },
54
+ { id: 'c2', from_alias: 'b·1', to_alias: 'a·1', content: 'c2', created_at: nowIso() },
55
+ ];
56
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: {
57
+ messages: [...hotMessages, ...coldMessages],
58
+ } }));
59
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
60
+
61
+ const page = await ctx.newPage();
62
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
63
+ await page.waitForSelector('[data-edge-badge-glow]', { timeout: 15000 });
64
+ await page.waitForTimeout(500);
65
+
66
+ const probe = await page.evaluate(() => {
67
+ const badges = [...document.querySelectorAll('[data-edge-badge-glow]')];
68
+ return badges.map(b => {
69
+ const cs = getComputedStyle(b);
70
+ return {
71
+ glow: b.getAttribute('data-edge-badge-glow'),
72
+ filter: cs.filter,
73
+ };
74
+ });
75
+ });
76
+
77
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
78
+ const sourceGlowAttr = /data-edge-badge-glow=\{isHot/.test(src);
79
+ const sourceDropShadow = /drop-shadow\(0 0 3px \$\{hotStroke\}80\)/.test(src);
80
+ const sourceFilterTween = /filter 200ms ease-out/.test(src);
81
+
82
+ await browser.close();
83
+
84
+ const hot = probe.find(b => b.glow === 'true');
85
+ const cold = probe.find(b => b.glow === 'false');
86
+
87
+ const results = {
88
+ badges_count_ge_2: probe.length >= 2,
89
+ hot_badge_found: !!hot,
90
+ hot_filter_has_drop: hot && /drop-shadow/.test(hot.filter),
91
+ cold_badge_found: !!cold,
92
+ cold_filter_none: cold?.filter === 'none',
93
+ source_glow_attr: sourceGlowAttr,
94
+ source_drop_shadow: sourceDropShadow,
95
+ source_filter_tween: sourceFilterTween,
96
+ };
97
+ const ok = Object.values(results).every(Boolean);
98
+ console.log(`${ok ? '✅' : '❌'} edge badge hot-lane drop-shadow:`, JSON.stringify(results),
99
+ '\n hot badge:', JSON.stringify(hot),
100
+ '\n cold badge:', JSON.stringify(cold));
101
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,109 @@
1
+ /* Round 485 verification: edge particle opacity lifts to 1.0 on
2
+ * isHoveredEdge OR isEndpointHoveredEdge (user hovering edge OR
3
+ * one of its endpoint nodes). Extends R484's "inspection overrides
4
+ * encoding" pattern to a 2nd anchor at the edge-particle scope.
5
+ *
6
+ * Contract:
7
+ * - rest stale edge: opacity matches freshness × edgeOpacityMul,
8
+ * data-edge-particle-opacity-lifted='false'
9
+ * - hover one of the endpoint nodes: that edge's particle
10
+ * opacity lifts to '1', lifted='true', rest-opacity attr
11
+ * preserved (encoding intact)
12
+ * - source-file conditional wired
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const sessionFresh = new Date(Date.now() - 60 * 1000).toISOString();
19
+ const fiveMinAgo = new Date(Date.now() - 5 * 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, status) => ({
36
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
37
+ network_id: nid, project_dir: null,
38
+ created_at: sessionFresh, updated_at: sessionFresh, last_seen_at: sessionFresh,
39
+ });
40
+ await route.fulfill({ response: r, json: { ...b, sessions: [
41
+ mk('a·1', 'working'), mk('a·2', 'idle'),
42
+ ] } });
43
+ });
44
+ // Stale message — freshness alpha decays to ~0.30 floor
45
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: {
46
+ messages: [
47
+ { id: 'm1', from_alias: 'a·1', to_alias: 'a·2', content: 'old', created_at: fiveMinAgo },
48
+ ],
49
+ } }));
50
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
51
+
52
+ const page = await ctx.newPage();
53
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
54
+ await page.waitForSelector('[data-edge-particle]', { timeout: 15000 });
55
+ await page.waitForTimeout(500);
56
+
57
+ const readParticle = () => page.evaluate(() => {
58
+ const p = document.querySelector('[data-edge-particle]');
59
+ if (!p) return null;
60
+ return {
61
+ key: p.getAttribute('data-edge-particle'),
62
+ lifted: p.getAttribute('data-edge-particle-opacity-lifted'),
63
+ restOp: p.getAttribute('data-edge-particle-opacity-rest'),
64
+ opacity: p.getAttribute('opacity'),
65
+ };
66
+ });
67
+
68
+ const rest = await readParticle();
69
+ // Hover the source node (a·1) to trigger isEndpointHoveredEdge
70
+ const sourceNode = await page.$('g[data-node="a·1"]');
71
+ let hovered = null;
72
+ if (sourceNode) {
73
+ await sourceNode.hover();
74
+ await page.waitForTimeout(400);
75
+ hovered = await readParticle();
76
+ }
77
+
78
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
79
+ const sourceLiftConditional = /opacity=\{\(isHoveredEdge \|\| isEndpointHoveredEdge\) \? 1 : Math\.min\(1, fresh \* edgeOpacityMul\)\}/.test(src);
80
+ const sourceLiftedAttr = /data-edge-particle-opacity-lifted=/.test(src);
81
+ const sourceRestAttr = /data-edge-particle-opacity-rest=/.test(src);
82
+
83
+ await browser.close();
84
+
85
+ const restOpacityNum = parseFloat(rest?.opacity || '0');
86
+ const hoverRestOpacityNum = parseFloat(hovered?.restOp || '0');
87
+ // Encoding preservation: in BOTH states the rest-opacity attr stays
88
+ // below 1.0 (freshness decay still encoded) — even though it may
89
+ // shift between states due to R56 edgeOpacityMul hover boost.
90
+ // The point: on hover, the LIVE opacity reads as 1 (override active)
91
+ // while the underlying decay encoding is still present in the attr.
92
+ const encodingPreservedOnHover = hoverRestOpacityNum < 1.0;
93
+
94
+ const results = {
95
+ particle_present: !!rest,
96
+ rest_lifted_false: rest?.lifted === 'false',
97
+ rest_opacity_decayed: restOpacityNum < 0.7,
98
+ hover_lifted_true: hovered?.lifted === 'true',
99
+ hover_opacity_is_1: hovered?.opacity === '1',
100
+ encoding_preserved_on_hover: encodingPreservedOnHover,
101
+ source_lift_conditional: sourceLiftConditional,
102
+ source_lifted_attr: sourceLiftedAttr,
103
+ source_rest_attr: sourceRestAttr,
104
+ };
105
+ const ok = Object.values(results).every(Boolean);
106
+ console.log(`${ok ? '✅' : '❌'} edge particle opacity lift on inspect:`, JSON.stringify(results),
107
+ '\n rest:', JSON.stringify(rest),
108
+ '\n hovered:', JSON.stringify(hovered));
109
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,104 @@
1
+ /* Round 479 verification: group-label parent text gains filter:
2
+ * drop-shadow glow on isPinned. 4th anchor in the R476/R477/R478
3
+ * drop-shadow visual-polish family.
4
+ *
5
+ * Contract:
6
+ * - at rest (no group pinned): every group label has data-group-
7
+ * label-glow='false' AND computed filter === 'none'
8
+ * - click a group-label hitbox: that group's label flips to
9
+ * glow='true' + filter starts with 'drop-shadow' using
10
+ * pal.legendAccent at 0x80 alpha
11
+ * - sibling group labels stay rest (no spillover)
12
+ * - source-file conditional wired
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
19
+
20
+ const browser = await chromium.launch({ headless: true });
21
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
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', 'grid');
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: [
40
+ mk('alpha·1', 'working'), mk('alpha·2', 'idle'),
41
+ mk('beta·1', 'working'), mk('beta·2', 'idle'),
42
+ ] } });
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
+
47
+ const page = await ctx.newPage();
48
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
49
+ await page.waitForSelector('[data-group-label-glow]', { timeout: 15000 });
50
+ await page.waitForTimeout(500);
51
+
52
+ const readAll = () => page.evaluate(() => {
53
+ const labels = [...document.querySelectorAll('[data-group-label-glow]')];
54
+ return labels.map(l => {
55
+ const cs = getComputedStyle(l);
56
+ return {
57
+ key: l.getAttribute('data-group-label'),
58
+ glow: l.getAttribute('data-group-label-glow'),
59
+ filter: cs.filter,
60
+ };
61
+ });
62
+ });
63
+
64
+ const rest = await readAll();
65
+ const firstKey = rest[0]?.key;
66
+
67
+ // Click hitbox to pin the first group
68
+ let pinned = null;
69
+ if (firstKey) {
70
+ const hit = await page.$(`[data-group-label-hit="${firstKey}"]`);
71
+ if (hit) {
72
+ await hit.click();
73
+ await page.waitForTimeout(400);
74
+ pinned = await readAll();
75
+ }
76
+ }
77
+
78
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
79
+ const sourceGlowAttr = /data-group-label-glow=\{isPinned/.test(src);
80
+ const sourceDropShadow = /drop-shadow\(0 0 3px \$\{pal\.legendAccent\}80\)/.test(src);
81
+
82
+ await browser.close();
83
+
84
+ const restCount = rest.length;
85
+ const restAllFalse = rest.every(r => r.glow === 'false' && r.filter === 'none');
86
+ const pinnedTarget = pinned?.find(r => r.key === firstKey);
87
+ const pinTargetGlow = pinnedTarget?.glow === 'true';
88
+ const pinTargetHasShadow = pinnedTarget && /drop-shadow/.test(pinnedTarget.filter);
89
+ const pinSiblingsStill = pinned ? pinned.filter(r => r.key !== firstKey).every(r => r.glow === 'false') : false;
90
+
91
+ const results = {
92
+ rest_count_ge_2: restCount >= 2,
93
+ rest_all_false: restAllFalse,
94
+ pinned_target_glow: pinTargetGlow,
95
+ pinned_target_shadow: pinTargetHasShadow,
96
+ pinned_siblings_rest: pinSiblingsStill,
97
+ source_glow_attr: sourceGlowAttr,
98
+ source_drop_shadow: sourceDropShadow,
99
+ };
100
+ const ok = Object.values(results).every(Boolean);
101
+ console.log(`${ok ? '✅' : '❌'} group-label drop-shadow glow:`, JSON.stringify(results),
102
+ '\n rest:', JSON.stringify(rest),
103
+ '\n pinned target:', JSON.stringify(pinnedTarget));
104
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,105 @@
1
+ /* Round 476 verification: hub working-count digit gains filter:
2
+ * drop-shadow glow on hub-hover. Stacks with the existing 4-axis
3
+ * hub-hover gesture (R209 scale + R425 fw + R253 fill + R213
4
+ * opacity). data-topo-hub-working-count-glow attr exposes the gate.
5
+ *
6
+ * Contract:
7
+ * - at rest (no hover): data-topo-hub-working-count-glow='false',
8
+ * filter is empty / 'none'
9
+ * - on mouseenter hub-core: glow='true', filter starts with
10
+ * 'drop-shadow'
11
+ * - source-file conditional wired (cyber + light theme variants)
12
+ * - transition list extends to include 'filter 200ms ease-out'
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
19
+
20
+ const browser = await chromium.launch({ headless: true });
21
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
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
+ // Need at least one working session so the digit renders (R213 opacity gate)
40
+ await route.fulfill({ response: r, json: { ...b, sessions: [
41
+ mk('a·1', 'working'), mk('a·2', 'idle'), mk('a·3', 'working'),
42
+ ] } });
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
+
47
+ const page = await ctx.newPage();
48
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
49
+ await page.waitForSelector('[data-topo-hub-working-count]', { timeout: 15000 });
50
+ await page.waitForTimeout(500);
51
+
52
+ const readDigit = () => page.evaluate(() => {
53
+ const t = document.querySelector('[data-topo-hub-working-count]');
54
+ if (!t) return null;
55
+ const cs = getComputedStyle(t);
56
+ return {
57
+ glow: t.getAttribute('data-topo-hub-working-count-glow'),
58
+ count: t.getAttribute('data-topo-hub-working-count'),
59
+ filter: cs.filter,
60
+ transition: cs.transition,
61
+ };
62
+ });
63
+
64
+ const restProbe = await readDigit();
65
+
66
+ // Hover the hub via the hover-trigger surface (hub <g> or core)
67
+ await page.hover('[data-topo-hub-core]');
68
+ await page.waitForTimeout(400);
69
+ const hoverProbe = await readDigit();
70
+
71
+ // Leave hub
72
+ await page.mouse.move(2, 2);
73
+ await page.waitForTimeout(400);
74
+ const restAgainProbe = await readDigit();
75
+
76
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
77
+ const sourceGlowAttr = /data-topo-hub-working-count-glow=/.test(src);
78
+ const sourceDropShadow = /drop-shadow\(0 0 \dpx rgba\(/.test(src);
79
+ const sourceFilterTween = /filter 200ms ease-out/.test(src);
80
+
81
+ await browser.close();
82
+
83
+ const restGlowFalse = restProbe?.glow === 'false';
84
+ const hoverGlowTrue = hoverProbe?.glow === 'true';
85
+ const restAgainFalse = restAgainProbe?.glow === 'false';
86
+ // Computed filter — at rest should be 'none' or empty; on hover should
87
+ // contain 'drop-shadow'.
88
+ const restFilterEmpty = restProbe?.filter === 'none' || !restProbe?.filter;
89
+ const hoverFilterHasShadow = /drop-shadow/.test(hoverProbe?.filter || '');
90
+
91
+ const results = {
92
+ rest_glow_false: restGlowFalse,
93
+ hover_glow_true: hoverGlowTrue,
94
+ rest_again_false: restAgainFalse,
95
+ rest_filter_none: restFilterEmpty,
96
+ hover_filter_dropshadow: hoverFilterHasShadow,
97
+ source_glow_attr: sourceGlowAttr,
98
+ source_drop_shadow: sourceDropShadow,
99
+ source_filter_tween: sourceFilterTween,
100
+ };
101
+ const ok = Object.values(results).every(Boolean);
102
+ console.log(`${ok ? '✅' : '❌'} hub digit drop-shadow glow:`, JSON.stringify(results),
103
+ '\n rest:', JSON.stringify(restProbe),
104
+ '\n hover:', JSON.stringify(hoverProbe));
105
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,95 @@
1
+ /* Round 483 verification: legend panel title "legend" gains
2
+ * fontWeight 700 → 800 on pinnedStatus. Mirrors R482's idiom at
3
+ * the legend panel scope. Together R482 + R483 close the panel-
4
+ * title symmetry on the "data tightens under attention" pattern.
5
+ *
6
+ * Contract:
7
+ * - rest (no pinned status): fw='700' + active='false'
8
+ * - click a legend-row hitbox to pin status filter: fw flips
9
+ * to '800' + active='true'
10
+ * - click again + mouse-move-off to release: back to fw='700'
11
+ * - source-file conditional 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: [
39
+ mk('a·1', 'working'), mk('a·2', 'idle'), mk('a·3', 'offline'),
40
+ ] } });
41
+ });
42
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
44
+
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
47
+ await page.waitForSelector('[data-legend-panel-title]', { timeout: 15000 });
48
+ await page.waitForTimeout(500);
49
+
50
+ const readTitle = () => page.evaluate(() => {
51
+ const t = document.querySelector('[data-legend-panel-title]');
52
+ if (!t) return null;
53
+ return {
54
+ fw: t.getAttribute('data-legend-panel-title-fw'),
55
+ active: t.getAttribute('data-legend-panel-title-active'),
56
+ liveFW: t.getAttribute('font-weight'),
57
+ };
58
+ });
59
+
60
+ const rest = await readTitle();
61
+ // Click a legend-row tinted hitbox to set pinnedStatus
62
+ await page.click('[data-legend-row-tinted]');
63
+ await page.waitForTimeout(400);
64
+ const pinned = await readTitle();
65
+ // Click again + move pointer away (per R482 gotcha note)
66
+ await page.click('[data-legend-row-tinted]');
67
+ await page.mouse.move(50, 50);
68
+ await page.waitForTimeout(400);
69
+ const restAgain = await readTitle();
70
+
71
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
72
+ const sourceFwConditional = /fontWeight=\{pinnedStatus \? '800' : '700'\}/.test(src);
73
+ const sourceFwTween = /font-weight 200ms ease-out/.test(src);
74
+ const sourceDataAttr = /data-legend-panel-title-fw=\{pinnedStatus \?/.test(src);
75
+
76
+ await browser.close();
77
+
78
+ const restIs700 = rest?.fw === '700' && rest?.active === 'false' && rest?.liveFW === '700';
79
+ const pinIs800 = pinned?.fw === '800' && pinned?.active === 'true' && pinned?.liveFW === '800';
80
+ const restAgainIs700 = restAgain?.fw === '700' && restAgain?.active === 'false' && restAgain?.liveFW === '700';
81
+
82
+ const results = {
83
+ rest_is_700_inactive: restIs700,
84
+ pin_flips_to_800_active: pinIs800,
85
+ unpin_back_to_700: restAgainIs700,
86
+ source_fw_conditional: sourceFwConditional,
87
+ source_fw_tween: sourceFwTween,
88
+ source_data_attr: sourceDataAttr,
89
+ };
90
+ const ok = Object.values(results).every(Boolean);
91
+ console.log(`${ok ? '✅' : '❌'} legend-panel title fw 700→800:`, JSON.stringify(results),
92
+ '\n rest:', JSON.stringify(rest),
93
+ '\n pinned:', JSON.stringify(pinned),
94
+ '\n rest again:', JSON.stringify(restAgain));
95
+ process.exit(ok ? 0 : 1);