@sleep2agi/agent-network-dashboard 0.5.1-preview.99 → 0.5.2-preview.0

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 (203) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/diagnostics/route-bundle-stats.json +32 -32
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/server/app/_global-error.html +1 -1
  7. package/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/_not-found.html +2 -2
  15. package/.next/server/app/_not-found.rsc +12 -12
  16. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  17. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  18. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  21. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/admin.html +2 -2
  24. package/.next/server/app/admin.rsc +14 -14
  25. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  26. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  27. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  28. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  29. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  30. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  31. package/.next/server/app/index.html +2 -2
  32. package/.next/server/app/index.rsc +14 -14
  33. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  34. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  35. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  36. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  37. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  38. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  39. package/.next/server/app/login.html +2 -2
  40. package/.next/server/app/login.rsc +14 -14
  41. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  42. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  43. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  44. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  45. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  46. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  47. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/logs.html +2 -2
  49. package/.next/server/app/logs.rsc +14 -14
  50. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  55. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  56. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/messages.html +2 -2
  58. package/.next/server/app/messages.rsc +14 -14
  59. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  60. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  61. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  62. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  64. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  65. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app/node.html +2 -2
  67. package/.next/server/app/node.rsc +14 -14
  68. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  69. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  70. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  71. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  73. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  74. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/nodes.html +2 -2
  76. package/.next/server/app/nodes.rsc +14 -14
  77. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  78. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  79. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  80. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  82. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  83. package/.next/server/app/page.js.nft.json +1 -1
  84. package/.next/server/app/page_client-reference-manifest.js +1 -1
  85. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  86. package/.next/server/app/server-logs.html +2 -2
  87. package/.next/server/app/server-logs.rsc +14 -14
  88. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  89. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  90. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  91. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  92. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  93. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  94. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  95. package/.next/server/app/settings/networks.html +2 -2
  96. package/.next/server/app/settings/networks.rsc +14 -14
  97. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  98. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  99. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  100. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  101. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  102. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  103. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  104. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  105. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  106. package/.next/server/app/settings/tokens.html +2 -2
  107. package/.next/server/app/settings/tokens.rsc +14 -14
  108. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  109. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  110. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  111. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  112. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  113. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  114. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  115. package/.next/server/app/settings.html +2 -2
  116. package/.next/server/app/settings.rsc +14 -14
  117. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  118. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  119. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  120. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  121. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  122. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  123. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  124. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  125. package/.next/server/app/tasks.html +2 -2
  126. package/.next/server/app/tasks.rsc +14 -14
  127. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  128. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  129. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  130. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  131. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  132. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  133. package/.next/server/chunks/ssr/{[root-of-the-server]__03b.f76._.js → [root-of-the-server]__0sv~g.o._.js} +2 -2
  134. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +1 -0
  135. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  136. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  137. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  138. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  139. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  140. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  141. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  142. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  143. package/.next/server/middleware-build-manifest.js +3 -3
  144. package/.next/server/pages/404.html +2 -2
  145. package/.next/server/pages/500.html +1 -1
  146. package/.next/server/server-reference-manifest.js +1 -1
  147. package/.next/server/server-reference-manifest.json +1 -1
  148. package/.next/static/chunks/{0l_~q07bhpkcx.js → 03a4--7ncekmk.js} +1 -1
  149. package/.next/static/chunks/0a4hmfvj-81x5.css +2 -0
  150. package/.next/static/chunks/0caxil0dw-oe9.js +4 -0
  151. package/.next/static/chunks/{03~~oirxz7~vc.js → 0p8xwrfjtykvn.js} +1 -1
  152. package/.next/static/chunks/0x.m3vy8e5iit.js +1 -0
  153. package/.next/static/chunks/12gq1w9k_7v06.js +1 -0
  154. package/.next/trace +2 -2
  155. package/.next/trace-build +1 -1
  156. package/app/components/ServersDrawer.tsx +17 -0
  157. package/app/components/TopoGraph.tsx +770 -76
  158. package/package.json +1 -1
  159. package/screenshots/v0.10.2-disk-verify/disk-render-full.png +0 -0
  160. package/screenshots/v0.10.2-disk-verify/disk-render.png +0 -0
  161. package/screenshots/v0.11.0-147/after.png +0 -0
  162. package/scripts/p0-147-screenshot.mjs +53 -0
  163. package/scripts/p0-servers-drawer-screenshot.mjs +2 -0
  164. package/scripts/topo-any-hover-attr-test.mjs +83 -0
  165. package/scripts/topo-any-pinned-attr-test.mjs +86 -0
  166. package/scripts/topo-chrome-fullscreen-icon-sw-test.mjs +92 -0
  167. package/scripts/topo-chrome-reset-icon-sw-test.mjs +80 -0
  168. package/scripts/topo-chrome-zoom-icon-sw-test.mjs +90 -0
  169. package/scripts/topo-dashboard-version-attr-test.mjs +69 -0
  170. package/scripts/topo-dense-alias-opacity-test.mjs +68 -0
  171. package/scripts/topo-edge-particle-hover-r-test.mjs +113 -0
  172. package/scripts/topo-endpoint-ring-r-hover-test.mjs +89 -0
  173. package/scripts/topo-fleet-count-attrs-test.mjs +87 -0
  174. package/scripts/topo-group-box-geom-transition-test.mjs +110 -0
  175. package/scripts/topo-group-box-rx-pin-test.mjs +103 -0
  176. package/scripts/topo-group-label-count-fw-test.mjs +100 -0
  177. package/scripts/topo-group-label-fw-pin-test.mjs +99 -0
  178. package/scripts/topo-group-label-tint-geom-test.mjs +94 -0
  179. package/scripts/topo-group-label-tint-transition-test.mjs +97 -0
  180. package/scripts/topo-group-pip-fontsize-test.mjs +106 -0
  181. package/scripts/topo-group-tier-attr-test.mjs +84 -0
  182. package/scripts/topo-group-tint-rx-pin-test.mjs +107 -0
  183. package/scripts/topo-hub-core-fill-hover-test.mjs +85 -0
  184. package/scripts/topo-hub-halo-r-hover-test.mjs +82 -0
  185. package/scripts/topo-legend-count-active-opacity-test.mjs +102 -0
  186. package/scripts/topo-legend-count-pin-fw-test.mjs +90 -0
  187. package/scripts/topo-minimap-viewport-opacity-test.mjs +96 -0
  188. package/scripts/topo-node-halo-hover-opacity-test.mjs +104 -0
  189. package/scripts/topo-node-halo-light-offline-test.mjs +80 -0
  190. package/scripts/topo-node-sub-text-fw-test.mjs +75 -0
  191. package/scripts/topo-overlap-stale-build-guard-test.mjs +66 -0
  192. package/scripts/topo-overlap-test.mjs +42 -1
  193. package/scripts/topo-recent-row-count-pin-fw-test.mjs +106 -0
  194. package/scripts/topo-recent-row-pip-hover-r-test.mjs +104 -0
  195. package/scripts/topo-runtime-icon-hover-test.mjs +96 -0
  196. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +0 -1
  197. package/.next/static/chunks/0.sf46gnv4wwm.js +0 -1
  198. package/.next/static/chunks/017hq2-5l~_98.css +0 -2
  199. package/.next/static/chunks/0_igeywsok2_-.js +0 -1
  200. package/.next/static/chunks/0igz0bww16uvc.js +0 -4
  201. /package/.next/static/{4rHxTzLwe2XC9M1rN-MpJ → 5IMzNtH5S5Oqe-FCw1CX4}/_buildManifest.js +0 -0
  202. /package/.next/static/{4rHxTzLwe2XC9M1rN-MpJ → 5IMzNtH5S5Oqe-FCw1CX4}/_clientMiddlewareManifest.js +0 -0
  203. /package/.next/static/{4rHxTzLwe2XC9M1rN-MpJ → 5IMzNtH5S5Oqe-FCw1CX4}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.5.1-preview.99",
3
+ "version": "0.5.2-preview.0",
4
4
  "description": "Agent Network Dashboard — Web UI for managing AI Agent networks",
5
5
  "scripts": {
6
6
  "dev": "next dev",
@@ -0,0 +1,53 @@
1
+ import { chromium } from 'playwright';
2
+ import { readFileSync } from 'node:fs';
3
+ const out = process.argv[2] || 'screenshots/v0.11.0-147/after';
4
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
5
+ const browser = await chromium.launch({ headless: true });
6
+ const ctx = await browser.newContext({ viewport: { width: 1600, height: 1400 }, deviceScaleFactor: 2 });
7
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
8
+ await ctx.addInitScript(() => { try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); localStorage.setItem('anet-topo-layout', 'grid'); } catch {} });
9
+ const fresh = new Date(Date.now() - 60_000).toISOString();
10
+ // Build a multi-prefix-group fleet (4 prefixes × 5 nodes = 20 nodes — triggers grid + multiple cluster boxes)
11
+ const sessions = [];
12
+ const prefixes = ['ai-insight', 'agent-network-dashboard', 'p-station', 'pay-blueleap'];
13
+ const models = ['claude-opus-4', 'gpt-4o', 'internlm/internlm2', 'minimax/abab6'];
14
+ prefixes.forEach((prefix, gIdx) => {
15
+ for (let i = 1; i <= 5; i++) {
16
+ sessions.push({
17
+ alias: `${prefix}-node-${i}`,
18
+ status: i === 1 ? 'working' : 'idle',
19
+ model: models[gIdx],
20
+ runtime: 'claude-code-cli',
21
+ network_id: 'default', project_dir: null,
22
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
23
+ });
24
+ }
25
+ });
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ const r = await route.fetch();
28
+ const b = await r.json();
29
+ await route.fulfill({ response: r, json: { ...b, sessions } });
30
+ });
31
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
32
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
33
+ const page = await ctx.newPage();
34
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
35
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length >= 20, { timeout: 30000 });
36
+ await page.waitForTimeout(800);
37
+ const box = await page.evaluate(() => {
38
+ const chrome = document.querySelector('[data-topo-chrome]');
39
+ let card = chrome?.parentElement;
40
+ while (card && !card.querySelector(':scope > svg')) card = card?.parentElement;
41
+ if (!card) return null;
42
+ card.scrollIntoView({ block: 'start' });
43
+ return new Promise(resolve => requestAnimationFrame(() => {
44
+ const r = card.getBoundingClientRect();
45
+ resolve({ x: Math.max(0, r.x), y: Math.max(0, r.y), width: r.width, height: r.height });
46
+ }));
47
+ });
48
+ await page.waitForTimeout(400);
49
+ if (box && box.width > 100) {
50
+ await page.screenshot({ path: `${out}.png`, clip: { x: box.x, y: box.y, width: box.width, height: Math.min(box.height, 980) } });
51
+ }
52
+ console.log(`✅ wrote ${out}.png`);
53
+ await browser.close();
@@ -35,6 +35,7 @@ await ctx.route('**/api/hub/servers*', (route) => {
35
35
  disk_total_gb: 500,
36
36
  cpu_history: [25, 28, 32, 30, 35, 38, 40, 36, 33, 30],
37
37
  mem_history: [50, 52, 55, 56, 58, 57, 59, 60, 58, 57],
38
+ disk_history: [40, 42, 43, 43, 44, 44, 44, 44, 44, 44],
38
39
  agents: [
39
40
  { alias: 'claude-1', runtime: 'claude-code-cli', status: 'working', progress: 0.42 },
40
41
  { alias: 'codex-1', runtime: 'codex-sdk', status: 'working', progress: 0.18 },
@@ -54,6 +55,7 @@ await ctx.route('**/api/hub/servers*', (route) => {
54
55
  disk_total_gb: 500,
55
56
  cpu_history: [80, 88, 92, 95, 96, 94, 93, 91, 90, 89],
56
57
  mem_history: [85, 87, 89, 91, 90, 92, 93, 91, 90, 91],
58
+ disk_history: [86, 88, 89, 90, 90, 91, 92, 92, 92, 92],
57
59
  agents: [
58
60
  { alias: 'minimax-busy', runtime: 'claude-code-cli', status: 'working', progress: 0.78 },
59
61
  ],
@@ -0,0 +1,83 @@
1
+ /* Round 466 verification: root svg gains `data-topo-any-hover`
2
+ * aggregate hover attr. Composed from 6 per-surface hover vars
3
+ * (hoveredAlias, hoveredHub, hoveredEdgeKey, hoveredGroupLabel,
4
+ * hoveredStatus, hoveredVendor). Read-only computed signal —
5
+ * useful for tests + external CSS hooks.
6
+ *
7
+ * Contract:
8
+ * - at rest (no hover): data-topo-any-hover === 'false'
9
+ * - hover a node: 'true'
10
+ * - leave the node: back to 'false'
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', 'grid');
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('alpha·1', 'working'),
40
+ mk('alpha·2', 'idle'),
41
+ ] } });
42
+ });
43
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
44
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
45
+
46
+ const page = await ctx.newPage();
47
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
48
+ await page.waitForSelector('svg[data-topo-any-hover]', { timeout: 15000 });
49
+ await page.waitForTimeout(400);
50
+
51
+ const readAttr = () => page.evaluate(() =>
52
+ document.querySelector('svg[viewBox="0 0 1000 680"]')?.getAttribute('data-topo-any-hover')
53
+ );
54
+
55
+ const restValue = await readAttr();
56
+
57
+ // Hover a group label to flip hoveredGroupLabel
58
+ await page.hover('[data-group-label-hit]');
59
+ await page.waitForTimeout(200);
60
+ const hoverValue = await readAttr();
61
+
62
+ // Move pointer far away to clear hover
63
+ await page.mouse.move(2, 2);
64
+ await page.waitForTimeout(300);
65
+ const restAgainValue = await readAttr();
66
+
67
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
68
+ const sourceHasAttr = /data-topo-any-hover=\{/.test(src);
69
+ const sourceCombines6Vars = /hoveredAlias \|\| hoveredHub \|\| hoveredEdgeKey \|\| hoveredGroupLabel \|\|/.test(src);
70
+
71
+ await browser.close();
72
+
73
+ const results = {
74
+ rest_is_false: restValue === 'false',
75
+ hover_flips_true: hoverValue === 'true',
76
+ rest_again_is_false: restAgainValue === 'false',
77
+ source_attr_wired: sourceHasAttr,
78
+ source_6_vars: sourceCombines6Vars,
79
+ };
80
+ const ok = Object.values(results).every(Boolean);
81
+ console.log(`${ok ? '✅' : '❌'} data-topo-any-hover aggregate:`, JSON.stringify(results),
82
+ '\n rest:', restValue, '/ hover:', hoverValue, '/ rest again:', restAgainValue);
83
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,86 @@
1
+ /* Round 467 verification: root svg gains `data-topo-any-pinned`
2
+ * aggregate pin attr — sibling to R466 data-topo-any-hover.
3
+ * Composed from 4 pin state vars (pinnedStatus, pinnedGroup,
4
+ * pinnedVendor, pinnedEdgeKey). Together they form a 2-bit
5
+ * inspection-mode signal on the canvas root.
6
+ *
7
+ * Contract:
8
+ * - at rest: data-topo-any-pinned === 'false'
9
+ * - click a group-label hitbox: attr flips to 'true'
10
+ * - press Escape (R62/R63/R88/R116 universal-cancel): back to 'false'
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', 'grid');
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('alpha·1', 'working'),
40
+ mk('alpha·2', 'idle'),
41
+ mk('beta·1', 'working'),
42
+ mk('beta·2', 'idle'),
43
+ ] } });
44
+ });
45
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
46
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
47
+
48
+ const page = await ctx.newPage();
49
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
50
+ await page.waitForSelector('svg[data-topo-any-pinned]', { timeout: 15000 });
51
+ await page.waitForTimeout(400);
52
+
53
+ const readAttr = () => page.evaluate(() =>
54
+ document.querySelector('svg[viewBox="0 0 1000 680"]')?.getAttribute('data-topo-any-pinned')
55
+ );
56
+
57
+ const restValue = await readAttr();
58
+
59
+ // Click group-label hitbox → pinnedGroup flips
60
+ await page.click('[data-group-label-hit]');
61
+ await page.waitForTimeout(300);
62
+ const pinnedValue = await readAttr();
63
+
64
+ // Esc → universal cancel
65
+ await page.mouse.move(500, 400); // move off the hitbox so Esc isn't swallowed
66
+ await page.keyboard.press('Escape');
67
+ await page.waitForTimeout(300);
68
+ const restAgainValue = await readAttr();
69
+
70
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
71
+ const sourceHasAttr = /data-topo-any-pinned=\{/.test(src);
72
+ const sourceCombines4Vars = /pinnedStatus \|\| pinnedGroup \|\| pinnedVendor \|\| pinnedEdgeKey/.test(src);
73
+
74
+ await browser.close();
75
+
76
+ const results = {
77
+ rest_is_false: restValue === 'false',
78
+ click_flips_true: pinnedValue === 'true',
79
+ esc_resets_false: restAgainValue === 'false',
80
+ source_attr_wired: sourceHasAttr,
81
+ source_4_vars: sourceCombines4Vars,
82
+ };
83
+ const ok = Object.values(results).every(Boolean);
84
+ console.log(`${ok ? '✅' : '❌'} data-topo-any-pinned aggregate:`, JSON.stringify(results),
85
+ '\n rest:', restValue, '/ pinned:', pinnedValue, '/ esc:', restAgainValue);
86
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,92 @@
1
+ /* Round 455 verification: chrome fullscreen icon (ENTER + EXIT
2
+ * variants) strokeWidth hover lift via Tailwind arbitrary class
3
+ * group-hover:[stroke-width:2.8]. Completes the chrome icon hover
4
+ * sw lift family (6 anchors).
5
+ *
6
+ * Contract:
7
+ * - rest: fullscreen-enter icon reports attr stroke-width '2.5' +
8
+ * computed '2.5px'
9
+ * - hover the fullscreen button: that icon computed '2.8px'
10
+ * - source-file probe confirms BOTH variants (enter + exit) carry
11
+ * the group-hover:[stroke-width:2.8] class
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: 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
+ await ctx.route('**/api/hub/status*', async (route) => {
26
+ const r = await route.fetch();
27
+ const b = await r.json();
28
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
29
+ const mk = (alias, status) => ({
30
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
31
+ network_id: nid, project_dir: null,
32
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
33
+ });
34
+ await route.fulfill({ response: r, json: { ...b, sessions: [
35
+ mk('alpha', 'working'),
36
+ mk('beta', 'idle'),
37
+ ] } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
40
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
41
+
42
+ const page = await ctx.newPage();
43
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
44
+ await page.waitForSelector('[data-topo-chrome-fullscreen-icon]', { timeout: 15000 });
45
+ await page.waitForTimeout(400);
46
+
47
+ const readIcon = () => page.evaluate(() => {
48
+ const i = document.querySelector('[data-topo-chrome-fullscreen-icon]');
49
+ return i ? {
50
+ variant: i.getAttribute('data-topo-chrome-fullscreen-icon'),
51
+ sw_attr: i.getAttribute('stroke-width'),
52
+ sw_computed: getComputedStyle(i).strokeWidth,
53
+ } : null;
54
+ });
55
+
56
+ const rest = await readIcon();
57
+
58
+ // Hover the fullscreen button parent — the button shares the group
59
+ // scope with the SVG icon child via Tailwind group/group-hover utility.
60
+ let hover = null;
61
+ const btn = await page.$('button[aria-label*="fullscreen" i], button[title*="ullscreen" i]');
62
+ if (btn) {
63
+ await btn.hover({ force: true });
64
+ await page.waitForTimeout(300);
65
+ hover = await readIcon();
66
+ await page.mouse.move(0, 0);
67
+ }
68
+
69
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
70
+ // className appears in TWO fullscreen-icon SVGs (enter + exit). Probe
71
+ // for both variants via separate searches:
72
+ const sourceFullscreenIconCount = (fileText.match(/data-topo-chrome-fullscreen-icon=/g) || []).length;
73
+ const sourceArbitrarySwCount = (fileText.match(/group-hover:\[stroke-width:2\.8\]/g) || []).length;
74
+ // We expect: zoom-in + zoom-out + fullscreen-enter + fullscreen-exit = 4 total
75
+ const sourceFourMatches = sourceArbitrarySwCount >= 4;
76
+
77
+ await browser.close();
78
+
79
+ const results = {
80
+ rest_mounted: !!rest,
81
+ rest_attr_2_5: rest?.sw_attr === '2.5',
82
+ rest_computed_2_5: rest?.sw_computed === '2.5px' || rest?.sw_computed === '2.5',
83
+ hover_computed_2_8: hover?.sw_computed === '2.8px' || hover?.sw_computed === '2.8',
84
+ source_fullscreen_variants: sourceFullscreenIconCount === 2,
85
+ source_arbitrary_4_matches: sourceFourMatches,
86
+ };
87
+ const ok = Object.values(results).every(Boolean);
88
+ console.log(`${ok ? '✅' : '❌'} chrome fullscreen icon sw hover:`, JSON.stringify(results),
89
+ '\n rest:', JSON.stringify(rest),
90
+ '\n hover:', JSON.stringify(hover),
91
+ '\n source matches: variants=' + sourceFullscreenIconCount + ' arbitrary-sw=' + sourceArbitrarySwCount);
92
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,80 @@
1
+ /* Round 453 verification: chrome reset icon strokeWidth hover lift —
2
+ * 2.5 → 2.8 on hoveredReset && !resetSpinning. Sibling to R443
3
+ * runtime badge inner-icon sw lift.
4
+ *
5
+ * Contract:
6
+ * - rest: reset icon reports stroke-width '2.5' + hover='false'
7
+ * - hover the reset button: icon stroke-width '2.8' + hover='true'
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
+ await route.fulfill({ response: r, json: { ...b, sessions: [
31
+ mk('alpha', 'working'),
32
+ mk('beta', 'idle'),
33
+ ] } });
34
+ });
35
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
36
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
37
+
38
+ const page = await ctx.newPage();
39
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
40
+ await page.waitForSelector('[data-topo-chrome-reset-icon]', { timeout: 15000 });
41
+ await page.waitForTimeout(400);
42
+
43
+ const readIcon = () => page.evaluate(() => {
44
+ const i = document.querySelector('[data-topo-chrome-reset-icon]');
45
+ return i ? {
46
+ sw: i.getAttribute('stroke-width'),
47
+ sw_d: i.getAttribute('data-topo-chrome-reset-icon-stroke-width'),
48
+ hover: i.getAttribute('data-topo-chrome-reset-icon-hover'),
49
+ } : null;
50
+ });
51
+
52
+ const rest = await readIcon();
53
+
54
+ let hover = null;
55
+ const btn = await page.$('[data-topo-chrome-reset]');
56
+ if (btn) {
57
+ await btn.hover({ force: true });
58
+ await page.waitForTimeout(250);
59
+ hover = await readIcon();
60
+ await page.mouse.move(0, 0);
61
+ }
62
+
63
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
64
+ const sourceWired = /strokeWidth=\{hoveredReset && !resetSpinning \? '2\.8' : '2\.5'\}/.test(fileText);
65
+
66
+ await browser.close();
67
+
68
+ const results = {
69
+ rest_mounted: !!rest,
70
+ rest_sw_2_5: rest?.sw === '2.5',
71
+ rest_hover_false: rest?.hover === 'false',
72
+ hover_sw_2_8: hover?.sw === '2.8',
73
+ hover_sw_data_2_8: hover?.sw_d === '2.8',
74
+ hover_hover_true: hover?.hover === 'true',
75
+ source_wired: sourceWired,
76
+ };
77
+ const ok = Object.values(results).every(Boolean);
78
+ console.log(`${ok ? '✅' : '❌'} chrome reset icon sw hover:`, JSON.stringify(results),
79
+ '\n rest:', rest, '\n hover:', hover);
80
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,90 @@
1
+ /* Round 454 verification: chrome zoom +/- icons strokeWidth hover lift
2
+ * via Tailwind arbitrary class group-hover:[stroke-width:2.8].
3
+ * The attribute strokeWidth='2.5' stays at rest; CSS hover overrides
4
+ * with stroke-width: 2.8.
5
+ *
6
+ * Contract:
7
+ * - rest: zoom-in/zoom-out icons report computed stroke-width '2.5px'
8
+ * - hover the zoom-in button: that icon computed stroke-width '2.8px'
9
+ * - source-file probe confirms group-hover:[stroke-width:2.8] in
10
+ * both icon className strings
11
+ */
12
+ import { chromium } from 'playwright';
13
+ import { readFileSync } from 'node:fs';
14
+
15
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
16
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
17
+
18
+ const browser = await chromium.launch({ headless: true });
19
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
20
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
21
+ await ctx.addInitScript(() => {
22
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
23
+ });
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, status) => ({
29
+ alias, status, model: 'claude-opus-4', 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', 'working'),
35
+ mk('beta', 'idle'),
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-chrome-zoom-in-icon]', { timeout: 15000 });
44
+ await page.waitForTimeout(400);
45
+
46
+ const readIcons = () => page.evaluate(() => {
47
+ const zi = document.querySelector('[data-topo-chrome-zoom-in-icon]');
48
+ const zo = document.querySelector('[data-topo-chrome-zoom-out-icon]');
49
+ return {
50
+ zoom_in: zi ? { sw: zi.getAttribute('stroke-width'), sw_computed: getComputedStyle(zi).strokeWidth } : null,
51
+ zoom_out: zo ? { sw: zo.getAttribute('stroke-width'), sw_computed: getComputedStyle(zo).strokeWidth } : null,
52
+ };
53
+ });
54
+
55
+ const rest = await readIcons();
56
+
57
+ // Hover zoom-in button
58
+ let hover = null;
59
+ const btn = await page.$('[data-topo-chrome-zoom-in]');
60
+ if (btn) {
61
+ await btn.hover({ force: true });
62
+ await page.waitForTimeout(300);
63
+ hover = await readIcons();
64
+ await page.mouse.move(0, 0);
65
+ }
66
+
67
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
68
+ // className appears BEFORE data-attr on the icon SVG, so check both
69
+ // orderings + count >= 2 to confirm both zoom icons have the class.
70
+ const sourceMatch = fileText.match(/group-hover:\[stroke-width:2\.8\]/g);
71
+ const sourceTwoMatches = sourceMatch && sourceMatch.length >= 2;
72
+ const sourceZoomOut = /group-hover:\[stroke-width:2\.8\][\s\S]{0,400}data-topo-chrome-zoom-out-icon/.test(fileText);
73
+ const sourceZoomIn = /group-hover:\[stroke-width:2\.8\][\s\S]{0,400}data-topo-chrome-zoom-in-icon/.test(fileText);
74
+
75
+ await browser.close();
76
+
77
+ const results = {
78
+ rest_zi_attr_2_5: rest.zoom_in?.sw === '2.5',
79
+ rest_zo_attr_2_5: rest.zoom_out?.sw === '2.5',
80
+ rest_zi_computed_2_5: rest.zoom_in?.sw_computed === '2.5px' || rest.zoom_in?.sw_computed === '2.5',
81
+ hover_zi_computed_2_8: hover?.zoom_in?.sw_computed === '2.8px' || hover?.zoom_in?.sw_computed === '2.8',
82
+ source_zoom_out_wired: sourceZoomOut,
83
+ source_zoom_in_wired: sourceZoomIn,
84
+ source_two_matches: sourceTwoMatches,
85
+ };
86
+ const ok = Object.values(results).every(Boolean);
87
+ console.log(`${ok ? '✅' : '❌'} chrome zoom icon sw hover:`, JSON.stringify(results),
88
+ '\n rest:', JSON.stringify(rest),
89
+ '\n hover:', JSON.stringify(hover));
90
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,69 @@
1
+ /* Round 462 verification: TopoGraph root <svg> exposes the
2
+ * shipped DASHBOARD_VERSION via data-dashboard-version attr.
3
+ * Closes the feedback_dash_zombie_port_3000.md memory rule:
4
+ * "verify ships via SVG DOM, not tmux 'Ready'" — pre-R462 there
5
+ * was no in-DOM signal of which build the dash was serving.
6
+ *
7
+ * Contract:
8
+ * - svg[viewBox="0 0 1000 680"] reports data-dashboard-version
9
+ * matching package.json#version (a non-empty string that
10
+ * starts with a digit)
11
+ * - the dash is serving the EXPECTED preview (rejects zombie
12
+ * server scenarios where DOM and package.json disagree)
13
+ * - source-file conditional wired (import + attr presence)
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 expected = JSON.parse(readFileSync('/home/vansin/agent-network-dashboard/package.json', 'utf8')).version;
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', 'grid');
28
+ sessionStorage.setItem('anet_v3_auth', '1');
29
+ } catch {}
30
+ });
31
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
32
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
33
+
34
+ const page = await ctx.newPage();
35
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
36
+ await page.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 15000 });
37
+ await page.waitForTimeout(400);
38
+
39
+ const probe = await page.evaluate(() => {
40
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
41
+ return {
42
+ attr: svg ? svg.getAttribute('data-dashboard-version') : null,
43
+ hasSvg: !!svg,
44
+ };
45
+ });
46
+
47
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
48
+ const sourceHasImport = /import \{ DASHBOARD_VERSION \} from '\.\.\/lib\/version'/.test(src);
49
+ const sourceHasAttr = /data-dashboard-version=\{DASHBOARD_VERSION\}/.test(src);
50
+
51
+ await browser.close();
52
+
53
+ const attrPresent = typeof probe.attr === 'string' && probe.attr.length > 0;
54
+ const attrIsVersionish = attrPresent && /^\d/.test(probe.attr);
55
+ const attrMatchesPkg = probe.attr === expected;
56
+
57
+ const results = {
58
+ svg_present: probe.hasSvg,
59
+ attr_present: attrPresent,
60
+ attr_is_version_shape: attrIsVersionish,
61
+ attr_matches_package: attrMatchesPkg,
62
+ source_import_wired: sourceHasImport,
63
+ source_attr_wired: sourceHasAttr,
64
+ };
65
+ const ok = Object.values(results).every(Boolean);
66
+ console.log(`${ok ? '✅' : '❌'} svg data-dashboard-version:`, JSON.stringify(results),
67
+ '\n expected:', expected,
68
+ '\n found:', probe.attr);
69
+ process.exit(ok ? 0 : 1);
@@ -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);