@sleep2agi/agent-network-dashboard 0.5.1-preview.10 → 0.5.1-preview.101

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 (245) 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 +6 -6
  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/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/_not-found.html +2 -2
  14. package/.next/server/app/_not-found.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  18. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  22. package/.next/server/app/admin.html +2 -2
  23. package/.next/server/app/admin.rsc +2 -2
  24. package/.next/server/app/admin.segments/_full.segment.rsc +2 -2
  25. package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  26. package/.next/server/app/admin.segments/_index.segment.rsc +2 -2
  27. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  28. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
  29. package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
  30. package/.next/server/app/index.html +2 -2
  31. package/.next/server/app/index.rsc +3 -3
  32. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  33. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  34. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  35. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  36. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  37. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  38. package/.next/server/app/login.html +2 -2
  39. package/.next/server/app/login.rsc +3 -3
  40. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  41. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  42. package/.next/server/app/login.segments/_index.segment.rsc +2 -2
  43. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  44. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  45. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  46. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  47. package/.next/server/app/logs.html +2 -2
  48. package/.next/server/app/logs.rsc +2 -2
  49. package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
  50. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  51. package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
  52. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  53. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
  54. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  55. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  56. package/.next/server/app/messages.html +2 -2
  57. package/.next/server/app/messages.rsc +2 -2
  58. package/.next/server/app/messages.segments/_full.segment.rsc +2 -2
  59. package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
  60. package/.next/server/app/messages.segments/_index.segment.rsc +2 -2
  61. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  62. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
  63. package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
  64. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  65. package/.next/server/app/node.html +2 -2
  66. package/.next/server/app/node.rsc +2 -2
  67. package/.next/server/app/node.segments/_full.segment.rsc +2 -2
  68. package/.next/server/app/node.segments/_head.segment.rsc +1 -1
  69. package/.next/server/app/node.segments/_index.segment.rsc +2 -2
  70. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  71. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
  72. package/.next/server/app/node.segments/node.segment.rsc +1 -1
  73. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  74. package/.next/server/app/nodes.html +2 -2
  75. package/.next/server/app/nodes.rsc +2 -2
  76. package/.next/server/app/nodes.segments/_full.segment.rsc +2 -2
  77. package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
  78. package/.next/server/app/nodes.segments/_index.segment.rsc +2 -2
  79. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  80. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
  81. package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
  82. package/.next/server/app/page_client-reference-manifest.js +1 -1
  83. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/server-logs.html +2 -2
  85. package/.next/server/app/server-logs.rsc +2 -2
  86. package/.next/server/app/server-logs.segments/_full.segment.rsc +2 -2
  87. package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
  88. package/.next/server/app/server-logs.segments/_index.segment.rsc +2 -2
  89. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  90. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
  91. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
  92. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app/settings/networks.html +2 -2
  94. package/.next/server/app/settings/networks.rsc +2 -2
  95. package/.next/server/app/settings/networks.segments/_full.segment.rsc +2 -2
  96. package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
  97. package/.next/server/app/settings/networks.segments/_index.segment.rsc +2 -2
  98. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  99. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
  100. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
  101. package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
  102. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  103. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  104. package/.next/server/app/settings/tokens.html +2 -2
  105. package/.next/server/app/settings/tokens.rsc +2 -2
  106. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +2 -2
  107. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
  108. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +2 -2
  109. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  110. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
  111. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
  112. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
  113. package/.next/server/app/settings.html +2 -2
  114. package/.next/server/app/settings.rsc +3 -3
  115. package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  116. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  117. package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  118. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  119. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  120. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  121. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  122. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  123. package/.next/server/app/tasks.html +2 -2
  124. package/.next/server/app/tasks.rsc +2 -2
  125. package/.next/server/app/tasks.segments/_full.segment.rsc +2 -2
  126. package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
  127. package/.next/server/app/tasks.segments/_index.segment.rsc +2 -2
  128. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
  130. package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
  131. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  132. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  133. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  134. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  135. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  136. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  137. package/.next/server/middleware-build-manifest.js +3 -3
  138. package/.next/server/pages/404.html +2 -2
  139. package/.next/server/pages/500.html +1 -1
  140. package/.next/static/chunks/017hq2-5l~_98.css +2 -0
  141. package/.next/static/chunks/05wf8bdep9pqe.js +1 -0
  142. package/.next/static/chunks/09~vnb~h-moab.js +4 -0
  143. package/.next/static/chunks/0whly2fnyyonc.js +1 -0
  144. package/.next/trace +2 -2
  145. package/.next/trace-build +1 -1
  146. package/app/components/TopoGraph.tsx +2037 -145
  147. package/package.json +1 -1
  148. package/scripts/topo-active-links-chip-hover-lift-test.mjs +93 -0
  149. package/scripts/topo-chip-digit-fontweight-test.mjs +105 -0
  150. package/scripts/topo-chip-digit-hover-bold-test.mjs +94 -0
  151. package/scripts/topo-chip-row-group-hover-brighten-test.mjs +107 -0
  152. package/scripts/topo-chip-row-hover-lift-test.mjs +95 -0
  153. package/scripts/topo-chrome-button-hover-lift-test.mjs +94 -0
  154. package/scripts/topo-chrome-segmented-radius-test.mjs +100 -0
  155. package/scripts/topo-click-ripple-opacity-test.mjs +99 -0
  156. package/scripts/topo-edge-badge-digit-fw-test.mjs +103 -0
  157. package/scripts/topo-edge-badge-fontsize-test.mjs +90 -0
  158. package/scripts/topo-edge-badge-hover-opacity-test.mjs +94 -0
  159. package/scripts/topo-edge-badge-hover-stroke-test.mjs +92 -0
  160. package/scripts/topo-edge-badge-letter-spacing-test.mjs +86 -0
  161. package/scripts/topo-edge-badge-opacity-test.mjs +80 -0
  162. package/scripts/topo-edge-badge-pin-opacity-test.mjs +86 -0
  163. package/scripts/topo-edge-badge-stroke-test.mjs +92 -0
  164. package/scripts/topo-edge-endpoint-hover-sw-test.mjs +127 -0
  165. package/scripts/topo-edge-freshness-floor-test.mjs +99 -0
  166. package/scripts/topo-edge-particle-hover-r-test.mjs +113 -0
  167. package/scripts/topo-edge-particle-radius-test.mjs +76 -0
  168. package/scripts/topo-edge-visible-linecap-test.mjs +89 -0
  169. package/scripts/topo-filter-pill-hover-lift-test.mjs +101 -0
  170. package/scripts/topo-filter-pill-hover-opacity-test.mjs +110 -0
  171. package/scripts/topo-filter-pill-value-fw-test.mjs +88 -0
  172. package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
  173. package/scripts/topo-flow-rail-hover-sw-test.mjs +113 -0
  174. package/scripts/topo-flow-rail-linecap-test.mjs +79 -0
  175. package/scripts/topo-freshness-chip-hierarchy-test.mjs +93 -0
  176. package/scripts/topo-freshness-chip-tabular-test.mjs +41 -0
  177. package/scripts/topo-freshness-floor-lift-test.mjs +92 -0
  178. package/scripts/topo-freshness-suffix-tabular-test.mjs +88 -0
  179. package/scripts/topo-fullscreen-icon-hover-scale-test.mjs +91 -0
  180. package/scripts/topo-group-box-stroke-test.mjs +105 -0
  181. package/scripts/topo-group-label-count-fontweight-test.mjs +108 -0
  182. package/scripts/topo-group-label-letter-spacing-test.mjs +113 -0
  183. package/scripts/topo-hover-detail-body-fw-test.mjs +101 -0
  184. package/scripts/topo-hover-detail-model-fw-test.mjs +98 -0
  185. package/scripts/topo-hover-detail-opacity-test.mjs +98 -0
  186. package/scripts/topo-hover-detail-rx-test.mjs +81 -0
  187. package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
  188. package/scripts/topo-hub-digit-fw-hover-test.mjs +102 -0
  189. package/scripts/topo-hub-halo-light-trough-test.mjs +88 -0
  190. package/scripts/topo-hub-halo-radius-test.mjs +86 -0
  191. package/scripts/topo-hub-halo-trough-test.mjs +83 -0
  192. package/scripts/topo-hub-highlight-opacity-test.mjs +88 -0
  193. package/scripts/topo-hub-highlight-radius-test.mjs +90 -0
  194. package/scripts/topo-hub-hover-ring-opacity-test.mjs +96 -0
  195. package/scripts/topo-hub-hover-ring-stroke-test.mjs +86 -0
  196. package/scripts/topo-hub-spoke-hover-opacity-test.mjs +119 -0
  197. package/scripts/topo-hub-spoke-hover-sw-test.mjs +121 -0
  198. package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
  199. package/scripts/topo-label-card-opacity-hover-test.mjs +99 -0
  200. package/scripts/topo-layout-toggle-hover-tracking-test.mjs +109 -0
  201. package/scripts/topo-layout-toggle-radius-test.mjs +87 -0
  202. package/scripts/topo-legend-label-fontweight-test.mjs +94 -0
  203. package/scripts/topo-legend-pin-ring-stroke-test.mjs +101 -0
  204. package/scripts/topo-legend-row-letter-spacing-test.mjs +101 -0
  205. package/scripts/topo-minimap-offline-opacity-test.mjs +90 -0
  206. package/scripts/topo-minimap-online-hover-opacity-test.mjs +92 -0
  207. package/scripts/topo-minimap-online-opacity-test.mjs +93 -0
  208. package/scripts/topo-minimap-online-radius-test.mjs +85 -0
  209. package/scripts/topo-minimap-viewport-linejoin-test.mjs +75 -0
  210. package/scripts/topo-minimap-viewport-rx-test.mjs +85 -0
  211. package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
  212. package/scripts/topo-node-alias-letter-spacing-test.mjs +112 -0
  213. package/scripts/topo-node-halo-hover-opacity-test.mjs +104 -0
  214. package/scripts/topo-node-halo-offline-opacity-test.mjs +87 -0
  215. package/scripts/topo-node-label-card-rx-test.mjs +85 -0
  216. package/scripts/topo-node-pulse-peak-test.mjs +89 -0
  217. package/scripts/topo-node-pulse-trough-test.mjs +91 -0
  218. package/scripts/topo-node-sub-text-letter-spacing-test.mjs +115 -0
  219. package/scripts/topo-panel-count-fw-hover-test.mjs +105 -0
  220. package/scripts/topo-panel-stroke-hover-test.mjs +110 -0
  221. package/scripts/topo-pressure-bar-height-test.mjs +92 -0
  222. package/scripts/topo-pressure-kicker-fontweight-test.mjs +76 -0
  223. package/scripts/topo-recent-pip-radius-2-test.mjs +72 -0
  224. package/scripts/topo-recent-pip-radius-test.mjs +76 -0
  225. package/scripts/topo-recent-row-content-opacity-test.mjs +81 -0
  226. package/scripts/topo-recent-row-letter-spacing-test.mjs +109 -0
  227. package/scripts/topo-recent-row-text-fontweight-test.mjs +90 -0
  228. package/scripts/topo-reset-hover-rotate-test.mjs +102 -0
  229. package/scripts/topo-spoke-active-opacity-test.mjs +104 -0
  230. package/scripts/topo-spoke-active-stroke-test.mjs +95 -0
  231. package/scripts/topo-spoke-idle-opacity-test.mjs +91 -0
  232. package/scripts/topo-status-ring-hover-sw-test.mjs +105 -0
  233. package/scripts/topo-vendor-chip-hover-lift-test.mjs +87 -0
  234. package/scripts/topo-vendor-glyph-fontweight-test.mjs +102 -0
  235. package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
  236. package/scripts/topo-vendor-suffix-hover-brighten-test.mjs +87 -0
  237. package/scripts/topo-zoom-icon-hover-scale-test.mjs +114 -0
  238. package/scripts/topo-zoom-level-hover-fw-test.mjs +95 -0
  239. package/.next/static/chunks/0aauz~36q5n2a.css +0 -2
  240. package/.next/static/chunks/0jwtl1a1lwyp5.js +0 -1
  241. package/.next/static/chunks/0lpe~8qw.tb2-.js +0 -4
  242. package/.next/static/chunks/0sl9zcww9om1e.js +0 -1
  243. /package/.next/static/{O8CVuAcu03lV_emn4fTFW → mZP-5x5RWlZ0ZvYObl74u}/_buildManifest.js +0 -0
  244. /package/.next/static/{O8CVuAcu03lV_emn4fTFW → mZP-5x5RWlZ0ZvYObl74u}/_clientMiddlewareManifest.js +0 -0
  245. /package/.next/static/{O8CVuAcu03lV_emn4fTFW → mZP-5x5RWlZ0ZvYObl74u}/_ssgManifest.js +0 -0
@@ -0,0 +1,112 @@
1
+ /* Round 427 verification: node alias label letter-spacing — 3-tier
2
+ * scale rest 0 / hover 0.3 / chat-target 0.5. Extends the hover-
3
+ * letter-spacing family (R344/R345/R347/R351/R420) to per-node alias
4
+ * scope.
5
+ *
6
+ * Contract:
7
+ * - rest: every visible alias-text reports letter-spacing 0px
8
+ * (resolves to keyword 'normal' in computed style on some engines —
9
+ * R218 trap; accept either form)
10
+ * - hover a node: that node's alias-text reports letter-spacing 0.3px,
11
+ * all others stay at rest 0
12
+ * - source-file probe confirms the 3-tier conditional + transition list
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: 1500 } });
22
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
23
+ await ctx.addInitScript(() => {
24
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
25
+ });
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, status) => ({
31
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
32
+ network_id: nid, project_dir: null,
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
34
+ });
35
+ // small fleet (3 nodes) so labels are in card-mode, not dense
36
+ await route.fulfill({ response: r, json: { ...b, sessions: [
37
+ mk('alpha', 'working'),
38
+ mk('beta', 'idle'),
39
+ mk('gamma', 'working'),
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: 'domcontentloaded' });
47
+ await page.waitForSelector('[data-node-alias-text]', { timeout: 15000 });
48
+ await page.waitForTimeout(400);
49
+
50
+ const readAll = () => page.evaluate(() => {
51
+ const ts = [...document.querySelectorAll('[data-node-alias-text]')];
52
+ return ts.map(t => ({
53
+ alias: t.getAttribute('data-node-alias-text'),
54
+ ls: t.style.letterSpacing, // inline style
55
+ ls_c: getComputedStyle(t).letterSpacing, // computed (may be 'normal')
56
+ chat: t.getAttribute('data-node-alias-chat-target'),
57
+ hover: t.getAttribute('data-node-alias-hovered'),
58
+ }));
59
+ });
60
+
61
+ const rest = await readAll();
62
+
63
+ // Hover the first node's group (its bounding rect)
64
+ const firstAlias = rest[0]?.alias;
65
+ let hover = null;
66
+ if (firstAlias) {
67
+ const box = await page.evaluate((alias) => {
68
+ const t = document.querySelector(`[data-node-alias-text="${alias}"]`);
69
+ if (!t) return null;
70
+ // Walk up to the node group (data-node) for the group-hover hit.
71
+ const node = t.closest('[data-node]');
72
+ const target = node || t;
73
+ const b = target.getBoundingClientRect();
74
+ return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
75
+ }, firstAlias);
76
+ if (box) {
77
+ await page.mouse.move(box.x, box.y);
78
+ await page.waitForTimeout(300);
79
+ hover = await readAll();
80
+ await page.mouse.move(0, 0);
81
+ }
82
+ }
83
+
84
+ // Source-file probe
85
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
86
+ const sourceWired = /chatAlias\s*===\s*session\.alias\s*\?\s*'0\.5px'\s*:\s*\n\s*hoveredAlias\s*===\s*session\.alias\s*\?\s*'0\.3px'\s*:\s*'0px'/.test(fileText);
87
+ const sourceTransition = /transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out'/.test(fileText);
88
+
89
+ await browser.close();
90
+
91
+ const isRest = (ls) => ls === '0px' || ls === '' || ls === 'normal';
92
+ const isMid = (ls) => ls === '0.3px';
93
+
94
+ const restAllZero = rest.length > 0 && rest.every(r => isRest(r.ls));
95
+ const hoveredEntry = hover?.find(r => r.alias === firstAlias);
96
+ const othersStillRest = hover ? hover.filter(r => r.alias !== firstAlias).every(r => isRest(r.ls)) : false;
97
+
98
+ const results = {
99
+ rest_mounted_count_gte_3: rest.length >= 3,
100
+ rest_chat_all_false: rest.every(r => r.chat === 'false'),
101
+ rest_hover_all_false: rest.every(r => r.hover === 'false'),
102
+ rest_letter_spacing_zero: restAllZero,
103
+ hover_target_ls_0_3: isMid(hoveredEntry?.ls || ''),
104
+ hover_target_hover_attr: hoveredEntry?.hover === 'true',
105
+ hover_others_stay_rest: othersStillRest,
106
+ source_three_tier_wired: sourceWired,
107
+ source_transition_kept: sourceTransition,
108
+ };
109
+ const ok = Object.values(results).every(Boolean);
110
+ console.log(`${ok ? '✅' : '❌'} node alias letter-spacing 3-tier:`, JSON.stringify(results),
111
+ '\n rest:', JSON.stringify(rest), '\n hover:', JSON.stringify(hover));
112
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,104 @@
1
+ /* Round 440 verification: node halo opacity hover lift —
2
+ * online cyber 0.65 → 0.80 on hoveredAlias match. Pure paint axis;
3
+ * no geometry change.
4
+ *
5
+ * Contract:
6
+ * - rest: every halo reports data-node-halo-hovered='false' and
7
+ * resolved-opacity matching its tier (online cyber 0.65)
8
+ * - hover one node: that halo flips to hovered='true' + resolved-
9
+ * opacity '0.8'
10
+ * - siblings stay rest
11
+ * - R51 sentinel intact (overlap test still finds nodes)
12
+ * - source-file probe confirms IIFE + 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: 1500 } });
22
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
23
+ await ctx.addInitScript(() => {
24
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
25
+ });
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, status) => ({
31
+ alias, status, model: 'claude-opus-4', 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', 'working'),
37
+ mk('beta', 'idle'),
38
+ mk('gamma', 'working'),
39
+ ] } });
40
+ });
41
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
42
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
43
+
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
46
+ await page.waitForSelector('[data-node-halo-hovered]', { timeout: 15000 });
47
+ await page.waitForTimeout(400);
48
+
49
+ const readAll = () => page.evaluate(() => {
50
+ const cs = [...document.querySelectorAll('[data-node-halo-hovered]')];
51
+ return cs.map(c => {
52
+ const node = c.closest('[data-node]');
53
+ return {
54
+ alias: node?.getAttribute('data-node') || null,
55
+ hovered: c.getAttribute('data-node-halo-hovered'),
56
+ opacity: parseFloat(c.getAttribute('data-node-halo-resolved-opacity') || '0'),
57
+ };
58
+ });
59
+ });
60
+
61
+ const rest = await readAll();
62
+
63
+ let hover = null;
64
+ const box = await page.evaluate(() => {
65
+ const t = document.querySelector('[data-node-alias-text="alpha"]');
66
+ if (!t) return null;
67
+ const node = t.closest('[data-node]');
68
+ const target = node || t;
69
+ const b = target.getBoundingClientRect();
70
+ return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
71
+ });
72
+ if (box) {
73
+ await page.mouse.move(box.x, box.y);
74
+ await page.waitForTimeout(300);
75
+ hover = await readAll();
76
+ await page.mouse.move(0, 0);
77
+ }
78
+
79
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
80
+ const sourceIIFE = /const isHaloHovered = !reducedMotion && hoveredAlias === session\.alias/.test(fileText);
81
+ const sourceCyberOnline = /isLight \? \(isHaloHovered \? 1\s*:\s*0\.85\) : \(isHaloHovered \? 0\.80 : 0\.65\)/.test(fileText);
82
+
83
+ await browser.close();
84
+
85
+ const restAllRest = rest.length > 0 && rest.every(r => r.hovered === 'false' && r.opacity === 0.65);
86
+ const hoveredEntry = hover?.find(r => r.alias === 'alpha');
87
+ const hoverTarget08 = hoveredEntry?.opacity === 0.80;
88
+ const hoverTargetFlag = hoveredEntry?.hovered === 'true';
89
+ const othersStayRest = hover ? hover.filter(r => r.alias !== 'alpha').every(r => r.hovered === 'false' && r.opacity === 0.65) : false;
90
+
91
+ const results = {
92
+ rest_halo_count_ge_3: rest.length >= 3,
93
+ rest_all_rest_state: restAllRest,
94
+ hover_target_opacity_0_8: hoverTarget08,
95
+ hover_target_attr_true: hoverTargetFlag,
96
+ hover_others_stay_rest: othersStayRest,
97
+ source_IIFE_wired: sourceIIFE,
98
+ source_cyber_online_wired: sourceCyberOnline,
99
+ };
100
+ const ok = Object.values(results).every(Boolean);
101
+ console.log(`${ok ? '✅' : '❌'} node-halo hover opacity:`, JSON.stringify(results),
102
+ '\n rest:', JSON.stringify(rest),
103
+ '\n hover target:', JSON.stringify(hoveredEntry));
104
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,87 @@
1
+ /* Round 407 verification: node halo offline opacity lift — cyber
2
+ * 0.25 → 0.30 and light 0.4 → 0.45. Probes both themes via two
3
+ * browser contexts with a single offline (ghost) session.
4
+ *
5
+ * Contract:
6
+ * - Online node (status: idle): opacity '0.65' cyber / '0.85' light
7
+ * (unchanged by R407 — invariant probe)
8
+ * - Offline node (status: offline + stale last_seen_at):
9
+ * * cyber: opacity attr === '0.3'
10
+ * * light: opacity attr === '0.45'
11
+ * * data-node-halo-offline-opacity === '0.3' / '0.45'
12
+ * - Source-file verification: the conditional
13
+ * `(isLight ? 0.45 : 0.30)` is present.
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
20
+ const stale = new Date(Date.now() - 30 * 60 * 1000).toISOString();
21
+
22
+ async function probeTheme(theme) {
23
+ const browser = await chromium.launch({ headless: true });
24
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript((t) => {
27
+ try { localStorage.setItem('anet-theme', t); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
28
+ }, theme);
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
+ await route.fulfill({ response: r, json: { ...b, sessions: [
34
+ { alias: 'alpha', status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
35
+ { alias: 'ghost', status: 'offline', model: 'claude-opus-4', runtime: 'claude-code-cli', network_id: nid, project_dir: null, created_at: stale, updated_at: stale, last_seen_at: stale },
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
+ const page = await ctx.newPage();
41
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
42
+ await page.waitForSelector('g[data-node]', { timeout: 15000 });
43
+ await page.waitForTimeout(400);
44
+ const probe = await page.evaluate(() => {
45
+ const nodes = Array.from(document.querySelectorAll('g[data-node]'));
46
+ const read = (alias) => {
47
+ const node = nodes.find((n) => n.getAttribute('data-node') === alias);
48
+ if (!node) return null;
49
+ const halo = node.querySelector('[data-node-halo-breath]');
50
+ return halo ? {
51
+ opacityAttr: halo.getAttribute('opacity'),
52
+ offlineData: halo.getAttribute('data-node-halo-offline-opacity'),
53
+ } : null;
54
+ };
55
+ return {
56
+ online: read('alpha'),
57
+ offline: read('ghost'),
58
+ };
59
+ });
60
+ await browser.close();
61
+ return probe;
62
+ }
63
+
64
+ const cyber = await probeTheme('cyber');
65
+ const light = await probeTheme('light');
66
+
67
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
68
+ const sourceHasOfflineLift = /isOnline\s*\?\s*\(isLight\s*\?\s*0\.85\s*:\s*0\.65\)\s*:\s*\(isLight\s*\?\s*0\.45\s*:\s*0\.30\)/.test(fileText);
69
+
70
+ const results = {
71
+ // Online (R407 doesn't touch online — invariant)
72
+ cyber_online_065: cyber?.online?.opacityAttr === '0.65',
73
+ light_online_085: light?.online?.opacityAttr === '0.85',
74
+ cyber_online_no_offline_attr: cyber?.online?.offlineData === null || cyber?.online?.offlineData === undefined,
75
+ // Offline (R407 lift)
76
+ cyber_offline_0_3: cyber?.offline?.opacityAttr === '0.3',
77
+ cyber_offline_data_0_3: cyber?.offline?.offlineData === '0.3',
78
+ light_offline_0_45: light?.offline?.opacityAttr === '0.45',
79
+ light_offline_data_0_45: light?.offline?.offlineData === '0.45',
80
+ // Source-file verification
81
+ source_offline_lift: sourceHasOfflineLift,
82
+ };
83
+ const ok = Object.values(results).every(Boolean);
84
+ console.log(`${ok ? '✅' : '❌'} node halo offline opacity lift:`, JSON.stringify(results),
85
+ '\n cyber:', cyber,
86
+ '\n light:', light);
87
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,85 @@
1
+ /* Round 411 verification: node label card rx 6 → 8. Corner-radius
2
+ * cascade family 8th anchor — compact card tier aligns with the
3
+ * R332/R375/R376 compact-chrome rx=8 tier.
4
+ *
5
+ * Contract:
6
+ * - Each node's label-card <rect> rx attr === '8'
7
+ * - data-node-label-card-rx === '8'
8
+ * - Pre-R411 invariants on the rect:
9
+ * * cardW (width) > 0
10
+ * * cardH (height) > 0
11
+ * * fill, stroke present
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) => ({
30
+ alias, status: 'idle', 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: [ mk('alpha'), mk('beta'), mk('gamma') ] } });
35
+ });
36
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
37
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
38
+
39
+ const page = await ctx.newPage();
40
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
41
+ await page.waitForSelector('[data-node-label-card]', { timeout: 15000 });
42
+ await page.waitForTimeout(400);
43
+
44
+ const probe = await page.evaluate(() => {
45
+ const cards = Array.from(document.querySelectorAll('[data-node-label-card]'));
46
+ if (cards.length === 0) return { count: 0 };
47
+ const sample = cards[0];
48
+ return {
49
+ count: cards.length,
50
+ rxAttr: sample.getAttribute('rx'),
51
+ rxData: sample.getAttribute('data-node-label-card-rx'),
52
+ widthAttr: sample.getAttribute('width'),
53
+ heightAttr: sample.getAttribute('height'),
54
+ fillPresent: !!sample.getAttribute('fill'),
55
+ strokePresent: !!sample.getAttribute('stroke'),
56
+ allRx8: cards.every((c) => c.getAttribute('rx') === '8'),
57
+ };
58
+ });
59
+
60
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
61
+ const sourceRx8 = /rx="8"\s*\n\s+fill=\{pal\.labelBox\.fill\}/.test(fileText) || /width=\{cardW\} height=\{cardH\} rx="8"/.test(fileText);
62
+ const sourceDataRx8 = /data-node-label-card-rx="8"/.test(fileText);
63
+
64
+ await browser.close();
65
+
66
+ const results = {
67
+ // At least one label card mounted
68
+ cards_count_ge_1: (probe.count || 0) >= 1,
69
+ // R411 wire
70
+ rx_attr_8: probe.rxAttr === '8',
71
+ rx_data_8: probe.rxData === '8',
72
+ all_cards_rx_8: probe.allRx8 === true,
73
+ // Pre-R411 invariants
74
+ width_present: Number(probe.widthAttr) > 0,
75
+ height_present: Number(probe.heightAttr) > 0,
76
+ fill_present: probe.fillPresent === true,
77
+ stroke_present: probe.strokePresent === true,
78
+ // Source-file canonical wire
79
+ source_rx_8: sourceRx8,
80
+ source_data_rx_8: sourceDataRx8,
81
+ };
82
+ const ok = Object.values(results).every(Boolean);
83
+ console.log(`${ok ? '✅' : '❌'} node label card rx 6 → 8:`, JSON.stringify(results),
84
+ '\n probe:', probe);
85
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,89 @@
1
+ /* Round 409 verification: active-node pulse SMIL peak opacity lift
2
+ * — cyber 0.18 → 0.20 / light 0.12 → 0.14. Active-state breath
3
+ * widens slightly at peak (+0.02 both themes) while trough stays.
4
+ *
5
+ * Contract:
6
+ * - Seed a flow link so a node becomes "active" (R243 isActive gate)
7
+ * - The active node's <g data-node-pulse> child has an <animate>
8
+ * opacity element with:
9
+ * * cyber: values='0.20;0.04;0.20' + data-node-pulse-peak='0.20'
10
+ * * light: values='0.14;0.02;0.14' + data-node-pulse-peak='0.14'
11
+ * - Pre-R409 invariants preserved:
12
+ * * dur='2.4s', calcMode='spline', repeatCount='indefinite'
13
+ * * keySplines='0.42 0 0.58 1;0.42 0 0.58 1'
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
20
+
21
+ async function probeTheme(theme) {
22
+ const browser = await chromium.launch({ headless: true });
23
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
24
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
25
+ await ctx.addInitScript((t) => {
26
+ try { localStorage.setItem('anet-theme', t); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
27
+ }, theme);
28
+ await ctx.route('**/api/hub/status*', async (route) => {
29
+ const r = await route.fetch();
30
+ const b = await r.json();
31
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
32
+ const mk = (alias) => ({
33
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
34
+ network_id: nid, project_dir: null,
35
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
36
+ });
37
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('alpha'), mk('beta'), mk('gamma') ] } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
40
+ json: { messages: [
41
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
42
+ ] },
43
+ }));
44
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForSelector('[data-node-pulse-active="true"]', { timeout: 15000 });
48
+ await page.waitForTimeout(500);
49
+ const probe = await page.evaluate(() => {
50
+ const pulseGroup = document.querySelector('[data-node-pulse-active="true"]');
51
+ if (!pulseGroup) return null;
52
+ const animates = pulseGroup.querySelectorAll('animate');
53
+ const opacityAnim = Array.from(animates).find(a => a.getAttribute('attributeName') === 'opacity');
54
+ return opacityAnim ? {
55
+ attributeName: opacityAnim.getAttribute('attributeName'),
56
+ values: opacityAnim.getAttribute('values'),
57
+ dur: opacityAnim.getAttribute('dur'),
58
+ calcMode: opacityAnim.getAttribute('calcMode'),
59
+ repeatCount: opacityAnim.getAttribute('repeatCount'),
60
+ keySplines: opacityAnim.getAttribute('keySplines'),
61
+ peakData: opacityAnim.getAttribute('data-node-pulse-peak'),
62
+ } : null;
63
+ });
64
+ await browser.close();
65
+ return probe;
66
+ }
67
+
68
+ const cyber = await probeTheme('cyber');
69
+ const light = await probeTheme('light');
70
+
71
+ const results = {
72
+ // R409: peak lifts to 0.20 cyber / 0.14 light
73
+ cyber_values_0_20: cyber?.values === '0.20;0.04;0.20',
74
+ cyber_peak_data_0_20: cyber?.peakData === '0.20',
75
+ light_values_0_14: light?.values === '0.14;0.02;0.14',
76
+ light_peak_data_0_14: light?.peakData === '0.14',
77
+ // Pre-R409 invariants
78
+ cyber_dur_2_4s: cyber?.dur === '2.4s',
79
+ cyber_spline: cyber?.calcMode === 'spline',
80
+ cyber_repeat_indefinite: cyber?.repeatCount === 'indefinite',
81
+ cyber_keySplines: cyber?.keySplines === '0.42 0 0.58 1;0.42 0 0.58 1',
82
+ light_dur_2_4s: light?.dur === '2.4s',
83
+ light_keySplines: light?.keySplines === '0.42 0 0.58 1;0.42 0 0.58 1',
84
+ };
85
+ const ok = Object.values(results).every(Boolean);
86
+ console.log(`${ok ? '✅' : '❌'} active-node pulse peak lift:`, JSON.stringify(results),
87
+ '\n cyber:', cyber,
88
+ '\n light:', light);
89
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,91 @@
1
+ /* Round 413 verification: active-node pulse SMIL trough opacity
2
+ * lift — cyber 0.04 → 0.05 / light 0.02 → 0.03. Extends the
3
+ * stale-state legibility lift family (8th anchor) and pairs with
4
+ * R409 peak lift so the per-node "active flow" breath reads
5
+ * confidently at both ends of its cycle.
6
+ *
7
+ * Contract:
8
+ * - Seed a flow link so a node becomes "active" (R243 isActive gate)
9
+ * - The active node's <animate> opacity element:
10
+ * * cyber: values='0.20;0.05;0.20' + data-node-pulse-trough='0.05'
11
+ * * light: values='0.14;0.03;0.14' + data-node-pulse-trough='0.03'
12
+ * - R409 peak invariant: data-node-pulse-peak='0.20' / '0.14'
13
+ * - Pre-R413 invariants preserved:
14
+ * * dur='2.4s', calcMode='spline', repeatCount='indefinite'
15
+ * * keySplines='0.42 0 0.58 1;0.42 0 0.58 1'
16
+ */
17
+ import { chromium } from 'playwright';
18
+ import { readFileSync } from 'node:fs';
19
+
20
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
21
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
22
+
23
+ async function probeTheme(theme) {
24
+ const browser = await chromium.launch({ headless: true });
25
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
26
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
27
+ await ctx.addInitScript((t) => {
28
+ try { localStorage.setItem('anet-theme', t); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
29
+ }, theme);
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) => ({
35
+ alias, status: 'idle', 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: [ mk('alpha'), mk('beta'), mk('gamma') ] } });
40
+ });
41
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
42
+ json: { messages: [
43
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
44
+ ] },
45
+ }));
46
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
47
+ const page = await ctx.newPage();
48
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
49
+ await page.waitForSelector('[data-node-pulse-active="true"]', { timeout: 15000 });
50
+ await page.waitForTimeout(500);
51
+ const probe = await page.evaluate(() => {
52
+ const pulseGroup = document.querySelector('[data-node-pulse-active="true"]');
53
+ if (!pulseGroup) return null;
54
+ const animates = pulseGroup.querySelectorAll('animate');
55
+ const opacityAnim = Array.from(animates).find(a => a.getAttribute('attributeName') === 'opacity');
56
+ return opacityAnim ? {
57
+ values: opacityAnim.getAttribute('values'),
58
+ peakData: opacityAnim.getAttribute('data-node-pulse-peak'),
59
+ troughData: opacityAnim.getAttribute('data-node-pulse-trough'),
60
+ dur: opacityAnim.getAttribute('dur'),
61
+ calcMode: opacityAnim.getAttribute('calcMode'),
62
+ keySplines: opacityAnim.getAttribute('keySplines'),
63
+ } : null;
64
+ });
65
+ await browser.close();
66
+ return probe;
67
+ }
68
+
69
+ const cyber = await probeTheme('cyber');
70
+ const light = await probeTheme('light');
71
+
72
+ const results = {
73
+ // R413: trough lifts to 0.05 cyber / 0.03 light
74
+ cyber_values_0_05: cyber?.values === '0.20;0.05;0.20',
75
+ cyber_trough_data_0_05: cyber?.troughData === '0.05',
76
+ light_values_0_03: light?.values === '0.14;0.03;0.14',
77
+ light_trough_data_0_03: light?.troughData === '0.03',
78
+ // R409 peak invariants
79
+ cyber_peak_data_0_20: cyber?.peakData === '0.20',
80
+ light_peak_data_0_14: light?.peakData === '0.14',
81
+ // Pre-R413 invariants preserved
82
+ cyber_dur_2_4s: cyber?.dur === '2.4s',
83
+ cyber_spline: cyber?.calcMode === 'spline',
84
+ cyber_keySplines: cyber?.keySplines === '0.42 0 0.58 1;0.42 0 0.58 1',
85
+ light_dur_2_4s: light?.dur === '2.4s',
86
+ };
87
+ const ok = Object.values(results).every(Boolean);
88
+ console.log(`${ok ? '✅' : '❌'} active-node pulse trough lift:`, JSON.stringify(results),
89
+ '\n cyber:', cyber,
90
+ '\n light:', light);
91
+ process.exit(ok ? 0 : 1);