@sleep2agi/agent-network-dashboard 0.5.1-preview.11 → 0.5.1-preview.110

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 (257) 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/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 +2 -2
  16. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  17. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  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 +2 -2
  25. package/.next/server/app/admin.segments/_full.segment.rsc +2 -2
  26. package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  27. package/.next/server/app/admin.segments/_index.segment.rsc +2 -2
  28. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  29. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
  30. package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
  31. package/.next/server/app/index.html +2 -2
  32. package/.next/server/app/index.rsc +3 -3
  33. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  34. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  35. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  36. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  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 +3 -3
  41. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  42. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  43. package/.next/server/app/login.segments/_index.segment.rsc +2 -2
  44. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  45. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  46. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  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 +2 -2
  50. package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
  51. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  52. package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
  53. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
  55. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  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 +2 -2
  59. package/.next/server/app/messages.segments/_full.segment.rsc +2 -2
  60. package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
  61. package/.next/server/app/messages.segments/_index.segment.rsc +2 -2
  62. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
  64. package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
  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 +2 -2
  68. package/.next/server/app/node.segments/_full.segment.rsc +2 -2
  69. package/.next/server/app/node.segments/_head.segment.rsc +1 -1
  70. package/.next/server/app/node.segments/_index.segment.rsc +2 -2
  71. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
  73. package/.next/server/app/node.segments/node.segment.rsc +1 -1
  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 +2 -2
  77. package/.next/server/app/nodes.segments/_full.segment.rsc +2 -2
  78. package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
  79. package/.next/server/app/nodes.segments/_index.segment.rsc +2 -2
  80. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
  82. package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
  83. package/.next/server/app/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  85. package/.next/server/app/server-logs.html +2 -2
  86. package/.next/server/app/server-logs.rsc +2 -2
  87. package/.next/server/app/server-logs.segments/_full.segment.rsc +2 -2
  88. package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
  89. package/.next/server/app/server-logs.segments/_index.segment.rsc +2 -2
  90. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  91. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
  92. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
  93. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/settings/networks.html +2 -2
  95. package/.next/server/app/settings/networks.rsc +2 -2
  96. package/.next/server/app/settings/networks.segments/_full.segment.rsc +2 -2
  97. package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
  98. package/.next/server/app/settings/networks.segments/_index.segment.rsc +2 -2
  99. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  100. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
  101. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
  102. package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
  103. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  104. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  105. package/.next/server/app/settings/tokens.html +2 -2
  106. package/.next/server/app/settings/tokens.rsc +2 -2
  107. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +2 -2
  108. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
  109. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +2 -2
  110. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  111. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
  112. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
  113. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
  114. package/.next/server/app/settings.html +2 -2
  115. package/.next/server/app/settings.rsc +3 -3
  116. package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  117. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  118. package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  119. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  120. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  121. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  122. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  123. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  124. package/.next/server/app/tasks.html +2 -2
  125. package/.next/server/app/tasks.rsc +2 -2
  126. package/.next/server/app/tasks.segments/_full.segment.rsc +2 -2
  127. package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
  128. package/.next/server/app/tasks.segments/_index.segment.rsc +2 -2
  129. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  130. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
  131. package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
  132. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  133. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  134. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  135. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  136. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  137. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  138. package/.next/server/middleware-build-manifest.js +3 -3
  139. package/.next/server/pages/404.html +2 -2
  140. package/.next/server/pages/500.html +1 -1
  141. package/.next/server/server-reference-manifest.js +1 -1
  142. package/.next/server/server-reference-manifest.json +1 -1
  143. package/.next/static/chunks/017hq2-5l~_98.css +2 -0
  144. package/.next/static/chunks/089itj87imeaf.js +1 -0
  145. package/.next/static/chunks/08egx9ra9v.2o.js +4 -0
  146. package/.next/static/chunks/0fg_4vbpjur-z.js +1 -0
  147. package/.next/trace +2 -2
  148. package/.next/trace-build +1 -1
  149. package/app/components/TopoGraph.tsx +2254 -161
  150. package/package.json +1 -1
  151. package/scripts/topo-active-links-chip-hover-lift-test.mjs +93 -0
  152. package/scripts/topo-chip-digit-fontweight-test.mjs +105 -0
  153. package/scripts/topo-chip-digit-hover-bold-test.mjs +94 -0
  154. package/scripts/topo-chip-row-group-hover-brighten-test.mjs +107 -0
  155. package/scripts/topo-chip-row-hover-lift-test.mjs +95 -0
  156. package/scripts/topo-chrome-button-hover-lift-test.mjs +94 -0
  157. package/scripts/topo-chrome-segmented-radius-test.mjs +100 -0
  158. package/scripts/topo-click-ripple-opacity-test.mjs +99 -0
  159. package/scripts/topo-edge-badge-digit-fw-test.mjs +103 -0
  160. package/scripts/topo-edge-badge-fontsize-test.mjs +90 -0
  161. package/scripts/topo-edge-badge-hover-opacity-test.mjs +94 -0
  162. package/scripts/topo-edge-badge-hover-stroke-test.mjs +92 -0
  163. package/scripts/topo-edge-badge-letter-spacing-test.mjs +86 -0
  164. package/scripts/topo-edge-badge-opacity-test.mjs +80 -0
  165. package/scripts/topo-edge-badge-pin-opacity-test.mjs +86 -0
  166. package/scripts/topo-edge-badge-stroke-test.mjs +92 -0
  167. package/scripts/topo-edge-endpoint-hover-sw-test.mjs +127 -0
  168. package/scripts/topo-edge-freshness-floor-test.mjs +99 -0
  169. package/scripts/topo-edge-particle-hover-r-test.mjs +113 -0
  170. package/scripts/topo-edge-particle-radius-test.mjs +76 -0
  171. package/scripts/topo-edge-visible-linecap-test.mjs +89 -0
  172. package/scripts/topo-endpoint-ring-r-hover-test.mjs +89 -0
  173. package/scripts/topo-filter-pill-hover-lift-test.mjs +101 -0
  174. package/scripts/topo-filter-pill-hover-opacity-test.mjs +110 -0
  175. package/scripts/topo-filter-pill-value-fw-test.mjs +88 -0
  176. package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
  177. package/scripts/topo-flow-rail-hover-sw-test.mjs +113 -0
  178. package/scripts/topo-flow-rail-linecap-test.mjs +79 -0
  179. package/scripts/topo-freshness-chip-hierarchy-test.mjs +93 -0
  180. package/scripts/topo-freshness-chip-tabular-test.mjs +41 -0
  181. package/scripts/topo-freshness-floor-lift-test.mjs +92 -0
  182. package/scripts/topo-freshness-suffix-tabular-test.mjs +88 -0
  183. package/scripts/topo-fullscreen-icon-hover-scale-test.mjs +91 -0
  184. package/scripts/topo-group-box-stroke-test.mjs +105 -0
  185. package/scripts/topo-group-label-count-fontweight-test.mjs +108 -0
  186. package/scripts/topo-group-label-count-fw-test.mjs +100 -0
  187. package/scripts/topo-group-label-letter-spacing-test.mjs +113 -0
  188. package/scripts/topo-hover-detail-body-fw-test.mjs +101 -0
  189. package/scripts/topo-hover-detail-model-fw-test.mjs +98 -0
  190. package/scripts/topo-hover-detail-opacity-test.mjs +98 -0
  191. package/scripts/topo-hover-detail-rx-test.mjs +81 -0
  192. package/scripts/topo-hub-core-fill-hover-test.mjs +85 -0
  193. package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
  194. package/scripts/topo-hub-digit-fw-hover-test.mjs +102 -0
  195. package/scripts/topo-hub-halo-light-trough-test.mjs +88 -0
  196. package/scripts/topo-hub-halo-radius-test.mjs +86 -0
  197. package/scripts/topo-hub-halo-trough-test.mjs +83 -0
  198. package/scripts/topo-hub-highlight-opacity-test.mjs +88 -0
  199. package/scripts/topo-hub-highlight-radius-test.mjs +90 -0
  200. package/scripts/topo-hub-hover-ring-opacity-test.mjs +96 -0
  201. package/scripts/topo-hub-hover-ring-stroke-test.mjs +86 -0
  202. package/scripts/topo-hub-spoke-hover-opacity-test.mjs +119 -0
  203. package/scripts/topo-hub-spoke-hover-sw-test.mjs +121 -0
  204. package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
  205. package/scripts/topo-label-card-opacity-hover-test.mjs +99 -0
  206. package/scripts/topo-layout-toggle-hover-tracking-test.mjs +109 -0
  207. package/scripts/topo-layout-toggle-radius-test.mjs +87 -0
  208. package/scripts/topo-legend-count-active-opacity-test.mjs +102 -0
  209. package/scripts/topo-legend-count-pin-fw-test.mjs +90 -0
  210. package/scripts/topo-legend-label-fontweight-test.mjs +94 -0
  211. package/scripts/topo-legend-pin-ring-stroke-test.mjs +101 -0
  212. package/scripts/topo-legend-row-letter-spacing-test.mjs +101 -0
  213. package/scripts/topo-minimap-offline-opacity-test.mjs +90 -0
  214. package/scripts/topo-minimap-online-hover-opacity-test.mjs +92 -0
  215. package/scripts/topo-minimap-online-opacity-test.mjs +93 -0
  216. package/scripts/topo-minimap-online-radius-test.mjs +85 -0
  217. package/scripts/topo-minimap-viewport-linejoin-test.mjs +75 -0
  218. package/scripts/topo-minimap-viewport-rx-test.mjs +85 -0
  219. package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
  220. package/scripts/topo-node-alias-letter-spacing-test.mjs +112 -0
  221. package/scripts/topo-node-halo-hover-opacity-test.mjs +104 -0
  222. package/scripts/topo-node-halo-offline-opacity-test.mjs +87 -0
  223. package/scripts/topo-node-label-card-rx-test.mjs +85 -0
  224. package/scripts/topo-node-pulse-peak-test.mjs +89 -0
  225. package/scripts/topo-node-pulse-trough-test.mjs +91 -0
  226. package/scripts/topo-node-sub-text-fw-test.mjs +75 -0
  227. package/scripts/topo-node-sub-text-letter-spacing-test.mjs +115 -0
  228. package/scripts/topo-panel-count-fw-hover-test.mjs +105 -0
  229. package/scripts/topo-panel-stroke-hover-test.mjs +110 -0
  230. package/scripts/topo-pressure-bar-height-test.mjs +92 -0
  231. package/scripts/topo-pressure-kicker-fontweight-test.mjs +76 -0
  232. package/scripts/topo-recent-pip-radius-2-test.mjs +72 -0
  233. package/scripts/topo-recent-pip-radius-test.mjs +76 -0
  234. package/scripts/topo-recent-row-content-opacity-test.mjs +81 -0
  235. package/scripts/topo-recent-row-count-pin-fw-test.mjs +106 -0
  236. package/scripts/topo-recent-row-letter-spacing-test.mjs +109 -0
  237. package/scripts/topo-recent-row-pip-hover-r-test.mjs +104 -0
  238. package/scripts/topo-recent-row-text-fontweight-test.mjs +90 -0
  239. package/scripts/topo-recent-signal-empty-fw-test.mjs +74 -0
  240. package/scripts/topo-runtime-icon-hover-test.mjs +96 -0
  241. package/scripts/topo-spoke-active-opacity-test.mjs +104 -0
  242. package/scripts/topo-spoke-active-stroke-test.mjs +95 -0
  243. package/scripts/topo-spoke-idle-opacity-test.mjs +91 -0
  244. package/scripts/topo-status-ring-hover-sw-test.mjs +105 -0
  245. package/scripts/topo-vendor-chip-hover-lift-test.mjs +87 -0
  246. package/scripts/topo-vendor-glyph-fontweight-test.mjs +102 -0
  247. package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
  248. package/scripts/topo-vendor-suffix-hover-brighten-test.mjs +87 -0
  249. package/scripts/topo-zoom-icon-hover-scale-test.mjs +114 -0
  250. package/scripts/topo-zoom-level-hover-fw-test.mjs +95 -0
  251. package/.next/static/chunks/0aauz~36q5n2a.css +0 -2
  252. package/.next/static/chunks/0qtk58rfhepeo.js +0 -1
  253. package/.next/static/chunks/0s2jh27ig6a.q.js +0 -4
  254. package/.next/static/chunks/17q6k~a.gl31q.js +0 -1
  255. /package/.next/static/{Ac38WRM1JjpuTYJKsfcUB → f_UsRugwjqtiAIxKM5iop}/_buildManifest.js +0 -0
  256. /package/.next/static/{Ac38WRM1JjpuTYJKsfcUB → f_UsRugwjqtiAIxKM5iop}/_clientMiddlewareManifest.js +0 -0
  257. /package/.next/static/{Ac38WRM1JjpuTYJKsfcUB → f_UsRugwjqtiAIxKM5iop}/_ssgManifest.js +0 -0
@@ -0,0 +1,90 @@
1
+ /* Round 446 verification: legend-row count fontWeight lift on isPinned.
2
+ * 600 → 700 when the status tier is pinned. Mirror of R444/R445 at
3
+ * legend-row scope.
4
+ *
5
+ * Contract:
6
+ * - rest: every legend-count reports font-weight '600' + pinned='false'
7
+ * - click a legend-status group: that row's count flips to '700' +
8
+ * pinned='true'; siblings stay rest
9
+ */
10
+ import { chromium } from 'playwright';
11
+ import { readFileSync } from 'node:fs';
12
+
13
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
14
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
15
+
16
+ const browser = await chromium.launch({ headless: true });
17
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
18
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
19
+ await ctx.addInitScript(() => {
20
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
21
+ });
22
+ await ctx.route('**/api/hub/status*', async (route) => {
23
+ const r = await route.fetch();
24
+ const b = await r.json();
25
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
26
+ const mk = (alias, status) => ({
27
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
28
+ network_id: nid, project_dir: null,
29
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
30
+ });
31
+ await route.fulfill({ response: r, json: { ...b, sessions: [
32
+ mk('alpha', 'working'),
33
+ mk('beta', 'working'),
34
+ mk('gamma', 'idle'),
35
+ ] } });
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-legend-count]', { timeout: 15000 });
43
+ await page.waitForTimeout(400);
44
+
45
+ const readAll = () => page.evaluate(() => {
46
+ const ts = [...document.querySelectorAll('[data-legend-count]')];
47
+ return ts.map(t => ({
48
+ key: t.getAttribute('data-legend-count'),
49
+ fw: t.getAttribute('font-weight'),
50
+ pinned: t.getAttribute('data-legend-count-pinned'),
51
+ }));
52
+ });
53
+
54
+ const rest = await readAll();
55
+
56
+ // Click the 'working' status row to pin it
57
+ let pinned = null;
58
+ const workingGroup = await page.$(`[data-legend-status="working"]`);
59
+ if (workingGroup) {
60
+ await workingGroup.click();
61
+ await page.waitForTimeout(250);
62
+ pinned = await readAll();
63
+ }
64
+
65
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
66
+ const sourceWired = /fontWeight=\{isPinned \? '700' : '600'\}/.test(fileText);
67
+
68
+ await browser.close();
69
+
70
+ const restAll600 = rest.every(r => r.fw === '600');
71
+ const restNoPin = rest.every(r => r.pinned === 'false');
72
+ const pinnedEntry = pinned?.find(r => r.key === 'working');
73
+ const pinFw_700 = pinnedEntry?.fw === '700';
74
+ const pinFlag = pinnedEntry?.pinned === 'true';
75
+ const othersStayRest = pinned ? pinned.filter(r => r.key !== 'working').every(r => r.fw === '600' && r.pinned === 'false') : false;
76
+
77
+ const results = {
78
+ rest_three_rows: rest.length === 3,
79
+ rest_all_fw_600: restAll600,
80
+ rest_no_pin: restNoPin,
81
+ pinned_target_fw_700: pinFw_700,
82
+ pinned_target_flag: pinFlag,
83
+ pinned_others_stay: othersStayRest,
84
+ source_wired: sourceWired,
85
+ };
86
+ const ok = Object.values(results).every(Boolean);
87
+ console.log(`${ok ? '✅' : '❌'} legend count fw pin:`, JSON.stringify(results),
88
+ '\n rest:', JSON.stringify(rest),
89
+ '\n pinned target:', JSON.stringify(pinnedEntry));
90
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,94 @@
1
+ /* Round 364 verification: legend-row label fontWeight 400 → 500.
2
+ * Sibling typography lift to R363 recent-row text fw 400 → 500.
3
+ * Both surfaces render small monospace text against panel chrome
4
+ * at fontSize 9-11 where SVG-default fw 400 sits at the legibility
5
+ * floor.
6
+ *
7
+ * Family snapshot post-R364:
8
+ * legend label fw 500 (R364, this round)
9
+ * legend count fw 600 (R309)
10
+ * recent alias fw 500 (R363)
11
+ * recent count fw 600/700 (R320)
12
+ *
13
+ * Contract (cyber, fixture with active rows):
14
+ * - Each rendered [data-legend-row-label] has font-weight === '500'.
15
+ * - Each has data-legend-row-label-font-weight === '500'.
16
+ * - Pre-R364 invariants:
17
+ * * fontSize=11 + fontFamily=monospace
18
+ * * R219 letter-spacing 0 → 0.5px on pin (style.transition still
19
+ * lists letter-spacing)
20
+ * * R55 fill transition retained
21
+ * * data-legend-row-label key surfaces (working / idle / offline)
22
+ */
23
+ import { chromium } from 'playwright';
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
27
+ const browser = await chromium.launch({ headless: true });
28
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
32
+ });
33
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
34
+ await ctx.route('**/api/hub/status*', async (route) => {
35
+ const r = await route.fetch();
36
+ const b = await r.json();
37
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
38
+ const mk = (alias) => ({
39
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
40
+ network_id: nid, project_dir: null,
41
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
42
+ });
43
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b'), mk('c') ] } });
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('[data-legend-row-label]', { timeout: 15000 });
51
+ await page.waitForTimeout(300);
52
+
53
+ const probe = await page.evaluate(() => {
54
+ const labels = Array.from(document.querySelectorAll('[data-legend-row-label]'));
55
+ return labels.map(el => {
56
+ const cs = getComputedStyle(el);
57
+ return {
58
+ key: el.getAttribute('data-legend-row-label'),
59
+ fontWeightAttr: el.getAttribute('font-weight'),
60
+ fontWeightData: el.getAttribute('data-legend-row-label-font-weight'),
61
+ fontSizeAttr: el.getAttribute('font-size'),
62
+ fontFamily: el.getAttribute('font-family'),
63
+ transition: cs.transition,
64
+ };
65
+ });
66
+ });
67
+
68
+ await browser.close();
69
+
70
+ const hasTrans = (s, prop) =>
71
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
72
+
73
+ const allRowsOk = probe.length > 0 && probe.every(r =>
74
+ r.fontWeightAttr === '500'
75
+ && r.fontWeightData === '500'
76
+ && r.fontSizeAttr === '11'
77
+ && /monospace/i.test(r.fontFamily || '')
78
+ && hasTrans(r.transition, 'letter-spacing')
79
+ && hasTrans(r.transition, 'fill')
80
+ );
81
+
82
+ const results = {
83
+ rows_rendered: probe.length >= 1,
84
+ all_rows_fw_500: probe.every(r => r.fontWeightAttr === '500'),
85
+ all_rows_data_500: probe.every(r => r.fontWeightData === '500'),
86
+ all_rows_fontsize_11: probe.every(r => r.fontSizeAttr === '11'),
87
+ all_rows_monospace: probe.every(r => /monospace/i.test(r.fontFamily || '')),
88
+ all_rows_have_ls_trans: probe.every(r => hasTrans(r.transition, 'letter-spacing')),
89
+ all_rows_have_fill_trans: probe.every(r => hasTrans(r.transition, 'fill')),
90
+ };
91
+ const ok = allRowsOk && Object.values(results).every(Boolean);
92
+ console.log(`${ok ? '✅' : '❌'} legend-row label fw 400 → 500:`, JSON.stringify(results),
93
+ '\n rows: ', probe.map(r => ({ key: r.key, fw: r.fontWeightAttr })));
94
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,101 @@
1
+ /* Round 402 verification: legend pin-ring strokeWidth 1.5 → 1.75.
2
+ * Sibling visual-weight bump (12th anchor) to R385 hub hover-ring
3
+ * — both are stroke-only pin/hover indicators painted as r=8 / r=14
4
+ * circles outside their target. R51 sentinel concern: 1.5 reserved
5
+ * for offline-node detection, but selector gated to g[data-node]
6
+ * ancestors so this legend-internal circle is invisible to probe.
7
+ *
8
+ * Contract:
9
+ * - data-legend-pin-ring-stroke-width === '1.75' (idle state too,
10
+ * since the attr surfaces the resolved value regardless of pin)
11
+ * - <circle> stroke-width attr === '1.75'
12
+ * - Pre-R402 invariants preserved:
13
+ * * r='8', fill='none', cx='16'
14
+ * * opacity reflects isPinned state (0 idle, 1 pinned)
15
+ * * data-legend-pin-ring-pinned attr present
16
+ * - Idle ring: opacity='0' (R181 always-mount + transition)
17
+ * - Pinned ring (after click): opacity='1'
18
+ */
19
+ import { chromium } from 'playwright';
20
+ import { readFileSync } from 'node:fs';
21
+
22
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
23
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
24
+
25
+ const browser = await chromium.launch({ headless: true });
26
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
27
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
28
+ await ctx.addInitScript(() => {
29
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } 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: fresh, updated_at: fresh, last_seen_at: fresh,
39
+ });
40
+ await route.fulfill({ response: r, json: { ...b, sessions: [
41
+ mk('alpha', 'working'),
42
+ mk('beta', '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('[data-legend-pin-ring]', { timeout: 15000 });
51
+ await page.waitForTimeout(400);
52
+
53
+ const restProbe = await page.evaluate(() => {
54
+ const ring = document.querySelector('[data-legend-pin-ring]');
55
+ return ring ? {
56
+ strokeWidthAttr: ring.getAttribute('stroke-width'),
57
+ strokeWidthData: ring.getAttribute('data-legend-pin-ring-stroke-width'),
58
+ rAttr: ring.getAttribute('r'),
59
+ fillAttr: ring.getAttribute('fill'),
60
+ cxAttr: ring.getAttribute('cx'),
61
+ opacityAttr: ring.getAttribute('opacity'),
62
+ pinned: ring.getAttribute('data-legend-pin-ring-pinned'),
63
+ } : null;
64
+ });
65
+
66
+ // Click a legend row to pin it
67
+ await page.click('[data-legend-status="working"]');
68
+ await page.waitForTimeout(300);
69
+
70
+ const pinnedProbe = await page.evaluate(() => {
71
+ const rings = Array.from(document.querySelectorAll('[data-legend-pin-ring]'));
72
+ const pinned = rings.find((r) => r.getAttribute('data-legend-pin-ring-pinned') === 'true');
73
+ return pinned ? {
74
+ strokeWidthAttr: pinned.getAttribute('stroke-width'),
75
+ opacityAttr: pinned.getAttribute('opacity'),
76
+ rAttr: pinned.getAttribute('r'),
77
+ } : null;
78
+ });
79
+
80
+ await browser.close();
81
+
82
+ const results = {
83
+ // R402 wire: stroke-width attr + data both 1.75
84
+ rest_strokeWidth_1_75: restProbe?.strokeWidthAttr === '1.75',
85
+ rest_data_1_75: restProbe?.strokeWidthData === '1.75',
86
+ // Pre-R402 invariants (R181 + base geometry)
87
+ rest_r_8: restProbe?.rAttr === '8',
88
+ rest_fill_none: restProbe?.fillAttr === 'none',
89
+ rest_cx_16: restProbe?.cxAttr === '16',
90
+ rest_opacity_0: restProbe?.opacityAttr === '0', // R181 idle invariant
91
+ rest_pinned_false: restProbe?.pinned === 'false',
92
+ // Pinned state preserves the bumped stroke
93
+ pinned_strokeWidth_1_75: pinnedProbe?.strokeWidthAttr === '1.75',
94
+ pinned_opacity_1: pinnedProbe?.opacityAttr === '1', // R181 pinned alpha
95
+ pinned_r_8: pinnedProbe?.rAttr === '8', // geometry preserved
96
+ };
97
+ const ok = Object.values(results).every(Boolean);
98
+ console.log(`${ok ? '✅' : '❌'} legend pin-ring strokeWidth 1.5 → 1.75:`, JSON.stringify(results),
99
+ '\n rest: ', restProbe,
100
+ '\n pinned: ', pinnedProbe);
101
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,101 @@
1
+ /* Round 433 verification: legend-row text 3-tier letter-spacing —
2
+ * rest 0 / hover 0.25 / pinned 0.5. Mirror of R432 group-label pattern
3
+ * at legend-row scope. Hover-letter-spacing family extension (9 anchors).
4
+ *
5
+ * Contract:
6
+ * - 3 legend rows mount (working / idle / offline)
7
+ * - rest: every legend-row-label reports letter-spacing '0px' +
8
+ * hovered='false' + pinned='false'
9
+ * - hover one legend-status group: that row's label letter-spacing
10
+ * '0.25px' + hovered='true'
11
+ * - siblings stay rest
12
+ * - source-file probe confirms 3-tier conditional + transition kept
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', 'working'),
38
+ mk('gamma', 'idle'),
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-legend-row-label]', { timeout: 15000 });
47
+ await page.waitForTimeout(400);
48
+
49
+ const readAll = () => page.evaluate(() => {
50
+ const ts = [...document.querySelectorAll('[data-legend-row-label]')];
51
+ return ts.map(t => ({
52
+ key: t.getAttribute('data-legend-row-label'),
53
+ ls: t.style.letterSpacing,
54
+ pinned: t.getAttribute('data-legend-row-label-pinned'),
55
+ hovered: t.getAttribute('data-legend-row-label-hovered'),
56
+ }));
57
+ });
58
+
59
+ const rest = await readAll();
60
+
61
+ // Hover the 'working' legend-status group wrapper (its onMouseEnter
62
+ // triggers setHoveredStatus('working') which flips the label tween).
63
+ let hover = null;
64
+ const box = await page.evaluate(() => {
65
+ const g = document.querySelector('[data-legend-status="working"]');
66
+ if (!g) return null;
67
+ const b = g.getBoundingClientRect();
68
+ return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
69
+ });
70
+ if (box) {
71
+ await page.mouse.move(box.x, box.y);
72
+ await page.waitForTimeout(300);
73
+ hover = await readAll();
74
+ await page.mouse.move(0, 0);
75
+ }
76
+
77
+ const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
78
+ const sourceWired = /letterSpacing:\s*isPinned\s*\?\s*'0\.5px'\s*:\s*\n?\s*hoveredStatus\s*===\s*row\.key\s*\?\s*'0\.25px'\s*:\s*'0px'/.test(fileText);
79
+
80
+ await browser.close();
81
+
82
+ const isRest = (ls) => ls === '0px' || ls === '' || ls === 'normal';
83
+
84
+ const hoveredEntry = hover?.find(r => r.key === 'working');
85
+ const othersStayRest = hover ? hover.filter(r => r.key !== 'working').every(r => isRest(r.ls) && r.hovered === 'false') : false;
86
+
87
+ const results = {
88
+ rest_three_rows: rest.length === 3,
89
+ rest_all_zero: rest.every(r => isRest(r.ls)),
90
+ rest_all_pinned_false: rest.every(r => r.pinned === 'false'),
91
+ rest_all_hovered_false: rest.every(r => r.hovered === 'false'),
92
+ hover_target_ls_0_25: hoveredEntry?.ls === '0.25px',
93
+ hover_target_hovered_true: hoveredEntry?.hovered === 'true',
94
+ hover_others_stay_rest: othersStayRest,
95
+ source_three_tier_wired: sourceWired,
96
+ };
97
+ const ok = Object.values(results).every(Boolean);
98
+ console.log(`${ok ? '✅' : '❌'} legend-row 3-tier letter-spacing:`, JSON.stringify(results),
99
+ '\n rest:', JSON.stringify(rest),
100
+ '\n hover target:', JSON.stringify(hoveredEntry));
101
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,90 @@
1
+ /* Round 372 verification: minimap offline-dot opacity 0.5 → 0.6.
2
+ * Sibling stale-state legibility lift to R358 (freshness ramp floor
3
+ * 0.25 → 0.30). Pre-R372 R198 drew offline dots at α=0.5; R372 lifts
4
+ * to 0.6 (+20 % relative) for better readability while keeping a
5
+ * clear two-tier distinction vs online α=0.9.
6
+ *
7
+ * The minimap only renders when view is non-default (zoom !== 1 ||
8
+ * pan). Test triggers zoom-in to mount it, then probes the offline
9
+ * dot's opacity attr.
10
+ *
11
+ * Contract:
12
+ * - data-topo-minimap-dot-online="false" element opacity === '0.6'.
13
+ * - data-topo-minimap-dot-opacity === '0.6' for offline dot.
14
+ * - Online dot still at opacity '0.9' (R198 invariant).
15
+ * - Pre-R372 invariants:
16
+ * * R198 offline r=1.2, online r=1.7 preserved
17
+ * * R198 transition list (opacity + fill + r) preserved
18
+ */
19
+ import { chromium } from 'playwright';
20
+ import { readFileSync } from 'node:fs';
21
+
22
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
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(() => {
27
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
28
+ });
29
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
30
+ const stale = new Date(Date.now() - 600 * 1000).toISOString();
31
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ // Mix of online (working) + offline sessions.
36
+ await route.fulfill({ response: r, json: { ...b, sessions: [
37
+ { alias: 'on-a', status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
38
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
39
+ { alias: 'on-b', status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
40
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
41
+ { alias: 'off-c', status: 'offline', model: 'claude-opus-4', runtime: 'claude-code-cli',
42
+ network_id: nid, project_dir: null, created_at: stale, updated_at: stale, last_seen_at: stale },
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
+ // Minimap only mounts when view is non-default — zoom in twice.
51
+ await page.waitForSelector('[data-topo-chrome-zoom-in]', { timeout: 15000 });
52
+ await page.click('[data-topo-chrome-zoom-in]');
53
+ await page.waitForTimeout(200);
54
+ await page.click('[data-topo-chrome-zoom-in]');
55
+ await page.waitForSelector('[data-topo-minimap-dot]', { timeout: 5000 });
56
+ await page.waitForTimeout(400);
57
+
58
+ const probe = await page.evaluate(() => {
59
+ const offline = document.querySelector('[data-topo-minimap-dot-online="false"]');
60
+ const online = document.querySelector('[data-topo-minimap-dot-online="true"]');
61
+ const cs = offline ? getComputedStyle(offline) : null;
62
+ return {
63
+ offlineOpacity: offline?.getAttribute('opacity') ?? null,
64
+ offlineData: offline?.getAttribute('data-topo-minimap-dot-opacity') ?? null,
65
+ offlineR: offline?.getAttribute('r') ?? null,
66
+ offlineTrans: cs?.transition ?? null,
67
+ onlineOpacity: online?.getAttribute('opacity') ?? null,
68
+ onlineR: online?.getAttribute('r') ?? null,
69
+ };
70
+ });
71
+
72
+ await browser.close();
73
+
74
+ const hasTrans = (s, prop) =>
75
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
76
+
77
+ const results = {
78
+ offline_opacity_0_6: probe.offlineOpacity === '0.6',
79
+ offline_data_0_6: probe.offlineData === '0.6',
80
+ offline_r_1_2: probe.offlineR === '1.2', // R198 invariant
81
+ online_opacity_0_9: probe.onlineOpacity === '0.9', // R198 invariant
82
+ online_r_1_7: probe.onlineR === '1.7', // R198 invariant
83
+ trans_has_opacity: hasTrans(probe.offlineTrans, 'opacity'), // R198
84
+ trans_has_fill: hasTrans(probe.offlineTrans, 'fill'), // R198
85
+ trans_has_r: hasTrans(probe.offlineTrans, 'r'), // R198
86
+ };
87
+ const ok = Object.values(results).every(Boolean);
88
+ console.log(`${ok ? '✅' : '❌'} minimap offline opacity 0.5 → 0.6:`, JSON.stringify(results),
89
+ '\n probe:', probe);
90
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,92 @@
1
+ /* Round 421 verification: minimap online dot opacity 0.95 → 1.0 on
2
+ * minimap container hover. Sibling to R346 viewport rect tween.
3
+ * Live-fleet anchors brighten when user hovers the minimap.
4
+ *
5
+ * Contract:
6
+ * - Minimap mounts on non-default view → zoom-in twice
7
+ * - Rest state: online dot opacity '0.95' (R392 invariant)
8
+ * - Hover state (page.hover on minimap container):
9
+ * * online dot opacity '1' + data '1'
10
+ * * R346 viewport strokeWidth '1.75'
11
+ * - Offline dot stays at R372 '0.6' in both states
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
+ const stale = new Date(Date.now() - 30 * 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
+ await route.fulfill({ response: r, json: { ...b, sessions: [
31
+ { 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 },
32
+ { 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 },
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('g[data-node]', { timeout: 15000 });
41
+ await page.waitForTimeout(300);
42
+
43
+ await page.click('[data-topo-chrome-zoom-in]');
44
+ await page.click('[data-topo-chrome-zoom-in]');
45
+ await page.waitForSelector('[data-topo-minimap-dot]', { timeout: 5000 });
46
+ await page.waitForTimeout(400);
47
+
48
+ const restProbe = await page.evaluate(() => {
49
+ const dots = Array.from(document.querySelectorAll('[data-topo-minimap-dot]'));
50
+ const on = dots.find((d) => d.getAttribute('data-topo-minimap-dot-online') === 'true');
51
+ const off = dots.find((d) => d.getAttribute('data-topo-minimap-dot-online') === 'false');
52
+ return {
53
+ online: on ? { opacity: on.getAttribute('opacity'), data: on.getAttribute('data-topo-minimap-dot-opacity') } : null,
54
+ offline: off ? { opacity: off.getAttribute('opacity'), data: off.getAttribute('data-topo-minimap-dot-opacity') } : null,
55
+ };
56
+ });
57
+
58
+ await page.hover('[data-topo-minimap]');
59
+ await page.waitForTimeout(400);
60
+
61
+ const hoverProbe = await page.evaluate(() => {
62
+ const dots = Array.from(document.querySelectorAll('[data-topo-minimap-dot]'));
63
+ const on = dots.find((d) => d.getAttribute('data-topo-minimap-dot-online') === 'true');
64
+ const off = dots.find((d) => d.getAttribute('data-topo-minimap-dot-online') === 'false');
65
+ const view = document.querySelector('[data-topo-minimap-viewport]');
66
+ return {
67
+ online: on ? { opacity: on.getAttribute('opacity'), data: on.getAttribute('data-topo-minimap-dot-opacity') } : null,
68
+ offline: off ? { opacity: off.getAttribute('opacity'), data: off.getAttribute('data-topo-minimap-dot-opacity') } : null,
69
+ viewportSw: view?.getAttribute('stroke-width') ?? null,
70
+ };
71
+ });
72
+
73
+ await browser.close();
74
+
75
+ const results = {
76
+ // Rest state (R392 baseline)
77
+ rest_online_0_95: restProbe.online?.opacity === '0.95',
78
+ rest_online_data_0_95: restProbe.online?.data === '0.95',
79
+ rest_offline_0_6: restProbe.offline?.opacity === '0.6',
80
+ // Hover state (R421 lift)
81
+ hover_online_1: hoverProbe.online?.opacity === '1',
82
+ hover_online_data_1: hoverProbe.online?.data === '1',
83
+ // R372 offline invariant: stays at 0.6 in both states
84
+ hover_offline_0_6: hoverProbe.offline?.opacity === '0.6',
85
+ // R346 viewport lift on hover (invariant)
86
+ hover_viewport_sw_1_75: hoverProbe.viewportSw === '1.75',
87
+ };
88
+ const ok = Object.values(results).every(Boolean);
89
+ console.log(`${ok ? '✅' : '❌'} minimap online dot hover opacity 0.95 → 1.0:`, JSON.stringify(results),
90
+ '\n rest: ', restProbe,
91
+ '\n hover:', hoverProbe);
92
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,93 @@
1
+ /* Round 392 verification: minimap online dot opacity 0.9 → 0.95.
2
+ * Theme-consistency / canvas-presence polish family (7th anchor)
3
+ * after R370 hub-ring, R371 edge-badge, R372 minimap offline,
4
+ * R386 hub-highlight idle, R387 hover-detail panel, R391 hub-spoke
5
+ * active. Halves the online dot's idle alpha gap (0.10 → 0.05).
6
+ * Mirrors R386's hub-highlight idle 0.9 → 0.95 on the minimap.
7
+ *
8
+ * Contract:
9
+ * - Minimap only mounts when view is non-default — test clicks
10
+ * zoom-in twice to mount it before probing.
11
+ * - Online dots: opacity attr === '0.95', data-opacity === '0.95',
12
+ * R384 r=1.9 invariant, data-online='true'
13
+ * - Offline dot (one ghost session): opacity attr === '0.6'
14
+ * (R372 invariant), r=1.2 invariant, data-online='false'
15
+ */
16
+ import { chromium } from 'playwright';
17
+ import { readFileSync } from 'node:fs';
18
+
19
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
20
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
21
+ const stale = new Date(Date.now() - 30 * 60 * 1000).toISOString(); // 30 min ago → offline
22
+
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(() => {
27
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } 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
+ await route.fulfill({ response: r, json: {
34
+ ...b,
35
+ sessions: [
36
+ { 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 },
37
+ { alias: 'beta', status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
38
+ { 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 },
39
+ ],
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('g[data-node]', { timeout: 15000 });
48
+ await page.waitForTimeout(300);
49
+
50
+ // Minimap mounts only on non-default view — click zoom-in twice
51
+ await page.click('[data-topo-chrome-zoom-in]');
52
+ await page.click('[data-topo-chrome-zoom-in]');
53
+ await page.waitForSelector('[data-topo-minimap-dot]', { timeout: 5000 });
54
+ await page.waitForTimeout(400);
55
+
56
+ const probe = await page.evaluate(() => {
57
+ const dots = Array.from(document.querySelectorAll('[data-topo-minimap-dot]'));
58
+ const online = dots.filter((d) => d.getAttribute('data-topo-minimap-dot-online') === 'true');
59
+ const offline = dots.filter((d) => d.getAttribute('data-topo-minimap-dot-online') === 'false');
60
+ const read = (el) => el ? ({
61
+ opacityAttr: el.getAttribute('opacity'),
62
+ opacityData: el.getAttribute('data-topo-minimap-dot-opacity'),
63
+ radiusAttr: el.getAttribute('r'),
64
+ }) : null;
65
+ return {
66
+ total: dots.length,
67
+ onlineCount: online.length,
68
+ offlineCount: offline.length,
69
+ onlineSample: read(online[0]),
70
+ offlineSample: read(offline[0]),
71
+ };
72
+ });
73
+
74
+ await browser.close();
75
+
76
+ const results = {
77
+ // Setup: 2 online + 1 offline
78
+ total_3_dots: probe.total === 3,
79
+ online_2_dots: probe.onlineCount === 2,
80
+ offline_1_dot: probe.offlineCount === 1,
81
+ // R392: online opacity 0.9 → 0.95
82
+ online_opacity_0_95: probe.onlineSample?.opacityAttr === '0.95',
83
+ online_data_0_95: probe.onlineSample?.opacityData === '0.95',
84
+ online_r_1_9: probe.onlineSample?.radiusAttr === '1.9', // R384 invariant
85
+ // R372 invariant: offline opacity 0.6
86
+ offline_opacity_0_6: probe.offlineSample?.opacityAttr === '0.6',
87
+ offline_data_0_6: probe.offlineSample?.opacityData === '0.6',
88
+ offline_r_1_2: probe.offlineSample?.radiusAttr === '1.2',
89
+ };
90
+ const ok = Object.values(results).every(Boolean);
91
+ console.log(`${ok ? '✅' : '❌'} minimap online dot opacity 0.9 → 0.95:`, JSON.stringify(results),
92
+ '\n probe:', JSON.stringify(probe, null, 2));
93
+ process.exit(ok ? 0 : 1);