@sleep2agi/agent-network-dashboard 0.5.1-preview.7 → 0.5.1-preview.71

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 (218) 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/04.j~y8e~sbs4.js +1 -0
  141. package/.next/static/chunks/06llamqb4jsu..js +4 -0
  142. package/.next/static/chunks/0_p8jkzdw5x2_.css +2 -0
  143. package/.next/static/chunks/12heglqfrp1bm.js +1 -0
  144. package/.next/trace +2 -2
  145. package/.next/trace-build +1 -1
  146. package/app/components/TopoGraph.tsx +1426 -84
  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-row-hover-lift-test.mjs +95 -0
  151. package/scripts/topo-chrome-button-hover-lift-test.mjs +94 -0
  152. package/scripts/topo-chrome-segmented-radius-test.mjs +100 -0
  153. package/scripts/topo-click-ripple-opacity-test.mjs +99 -0
  154. package/scripts/topo-edge-badge-fontsize-test.mjs +90 -0
  155. package/scripts/topo-edge-badge-hover-opacity-test.mjs +94 -0
  156. package/scripts/topo-edge-badge-hover-stroke-test.mjs +92 -0
  157. package/scripts/topo-edge-badge-opacity-test.mjs +80 -0
  158. package/scripts/topo-edge-badge-pin-opacity-test.mjs +86 -0
  159. package/scripts/topo-edge-badge-stroke-test.mjs +92 -0
  160. package/scripts/topo-edge-freshness-floor-test.mjs +99 -0
  161. package/scripts/topo-edge-visible-linecap-test.mjs +89 -0
  162. package/scripts/topo-filter-pill-hover-lift-test.mjs +101 -0
  163. package/scripts/topo-filter-pill-hover-opacity-test.mjs +110 -0
  164. package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
  165. package/scripts/topo-flow-rail-linecap-test.mjs +79 -0
  166. package/scripts/topo-freshness-chip-hierarchy-test.mjs +93 -0
  167. package/scripts/topo-freshness-chip-tabular-test.mjs +41 -0
  168. package/scripts/topo-freshness-floor-lift-test.mjs +92 -0
  169. package/scripts/topo-freshness-suffix-tabular-test.mjs +88 -0
  170. package/scripts/topo-fullscreen-icon-hover-scale-test.mjs +91 -0
  171. package/scripts/topo-group-box-stroke-test.mjs +105 -0
  172. package/scripts/topo-group-label-count-fontweight-test.mjs +108 -0
  173. package/scripts/topo-hover-detail-body-fw-test.mjs +101 -0
  174. package/scripts/topo-hover-detail-model-fw-test.mjs +98 -0
  175. package/scripts/topo-hover-detail-opacity-test.mjs +98 -0
  176. package/scripts/topo-hover-detail-rx-test.mjs +81 -0
  177. package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
  178. package/scripts/topo-hub-halo-light-trough-test.mjs +88 -0
  179. package/scripts/topo-hub-halo-radius-test.mjs +86 -0
  180. package/scripts/topo-hub-halo-trough-test.mjs +83 -0
  181. package/scripts/topo-hub-highlight-opacity-test.mjs +88 -0
  182. package/scripts/topo-hub-highlight-radius-test.mjs +90 -0
  183. package/scripts/topo-hub-hover-ring-opacity-test.mjs +96 -0
  184. package/scripts/topo-hub-hover-ring-stroke-test.mjs +86 -0
  185. package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
  186. package/scripts/topo-layout-toggle-hover-tracking-test.mjs +109 -0
  187. package/scripts/topo-layout-toggle-radius-test.mjs +87 -0
  188. package/scripts/topo-legend-label-fontweight-test.mjs +94 -0
  189. package/scripts/topo-legend-pin-ring-stroke-test.mjs +101 -0
  190. package/scripts/topo-minimap-offline-opacity-test.mjs +90 -0
  191. package/scripts/topo-minimap-online-opacity-test.mjs +93 -0
  192. package/scripts/topo-minimap-online-radius-test.mjs +85 -0
  193. package/scripts/topo-minimap-viewport-linejoin-test.mjs +75 -0
  194. package/scripts/topo-minimap-viewport-rx-test.mjs +85 -0
  195. package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
  196. package/scripts/topo-node-halo-offline-opacity-test.mjs +87 -0
  197. package/scripts/topo-node-pulse-peak-test.mjs +89 -0
  198. package/scripts/topo-panel-count-letterspacing-test.mjs +89 -0
  199. package/scripts/topo-panel-rect-opacity-hover-test.mjs +109 -0
  200. package/scripts/topo-pressure-bar-height-test.mjs +92 -0
  201. package/scripts/topo-pressure-kicker-fontweight-test.mjs +76 -0
  202. package/scripts/topo-recent-pip-radius-2-test.mjs +72 -0
  203. package/scripts/topo-recent-pip-radius-test.mjs +76 -0
  204. package/scripts/topo-recent-row-text-fontweight-test.mjs +90 -0
  205. package/scripts/topo-reset-hover-rotate-test.mjs +102 -0
  206. package/scripts/topo-spoke-active-opacity-test.mjs +104 -0
  207. package/scripts/topo-vendor-chip-hover-lift-test.mjs +87 -0
  208. package/scripts/topo-vendor-glyph-fontweight-test.mjs +102 -0
  209. package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
  210. package/scripts/topo-zoom-icon-hover-scale-test.mjs +114 -0
  211. package/scripts/topo-zoom-level-hover-letterspacing-test.mjs +91 -0
  212. package/.next/static/chunks/0aauz~36q5n2a.css +0 -2
  213. package/.next/static/chunks/0bja1amnrg3li.js +0 -1
  214. package/.next/static/chunks/0k~uc0~~19hyy.js +0 -4
  215. package/.next/static/chunks/0wtq_6dnzems6.js +0 -1
  216. /package/.next/static/{x9zCCrMkHsIYlXNY791KF → gaK6yNvVjshUCmKR9qrPn}/_buildManifest.js +0 -0
  217. /package/.next/static/{x9zCCrMkHsIYlXNY791KF → gaK6yNvVjshUCmKR9qrPn}/_clientMiddlewareManifest.js +0 -0
  218. /package/.next/static/{x9zCCrMkHsIYlXNY791KF → gaK6yNvVjshUCmKR9qrPn}/_ssgManifest.js +0 -0
@@ -0,0 +1,88 @@
1
+ /* Round 386 verification: hub-highlight idle opacity 0.9 → 0.95.
2
+ * Theme-consistency / canvas-presence polish family (4th anchor)
3
+ * after R370 hub-ring 0.7→0.8 cyber, R371 edge-badge 0.82→0.85
4
+ * cyber, R372 minimap offline 0.5→0.6. Lifts the focal point's
5
+ * idle "lamp lit but no work" core from a faded 0.9 to a present
6
+ * 0.95 (idle alpha gap halved).
7
+ *
8
+ * Contract:
9
+ * - When workingCount === 0 (all sessions idle / connected):
10
+ * * opacity attr === '0.95'
11
+ * * data-topo-hub-highlight-opacity === '0.95'
12
+ * * data-topo-hub-highlight-visible === 'true'
13
+ * - When workingCount > 0 (≥ 1 working session):
14
+ * * opacity attr === '0' (R130 takeover gate preserved)
15
+ * * data-topo-hub-highlight-visible === 'false'
16
+ * - Pre-R386 invariants preserved:
17
+ * * data-topo-hub-highlight-radius === '5.5' (R365)
18
+ * * fill === '#d1fae5'
19
+ *
20
+ * Test fixture supplies all-idle and all-working scenarios via
21
+ * /api/hub/status route fulfillment.
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
28
+
29
+ async function probeWith(sessionStatus) {
30
+ const browser = await chromium.launch({ headless: true });
31
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
32
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
33
+ await ctx.addInitScript(() => {
34
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
35
+ });
36
+ await ctx.route('**/api/hub/status*', async (route) => {
37
+ const r = await route.fetch();
38
+ const b = await r.json();
39
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
40
+ const mk = (alias) => ({
41
+ alias, status: sessionStatus, model: 'claude-opus-4', runtime: 'claude-code-cli',
42
+ network_id: nid, project_dir: null,
43
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
44
+ });
45
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b') ] } });
46
+ });
47
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
48
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForSelector('[data-topo-hub-highlight]', { timeout: 15000 });
52
+ await page.waitForTimeout(350);
53
+ const probe = await page.evaluate(() => {
54
+ const e = document.querySelector('[data-topo-hub-highlight]');
55
+ return {
56
+ opacityAttr: e?.getAttribute('opacity') ?? null,
57
+ opacityData: e?.getAttribute('data-topo-hub-highlight-opacity') ?? null,
58
+ visible: e?.getAttribute('data-topo-hub-highlight-visible') ?? null,
59
+ radius: e?.getAttribute('data-topo-hub-highlight-radius') ?? null,
60
+ fill: e?.getAttribute('fill') ?? null,
61
+ };
62
+ });
63
+ await browser.close();
64
+ return probe;
65
+ }
66
+
67
+ const idle = await probeWith('idle');
68
+ const working = await probeWith('working');
69
+
70
+ const results = {
71
+ // Idle: lifted opacity 0.95 visible
72
+ idle_opacity_attr_0_95: idle.opacityAttr === '0.95',
73
+ idle_opacity_data_0_95: idle.opacityData === '0.95',
74
+ idle_visible_true: idle.visible === 'true',
75
+ // Working: R130 takeover, opacity 0
76
+ working_opacity_attr_0: working.opacityAttr === '0',
77
+ working_visible_false: working.visible === 'false',
78
+ // Cross-state invariants (R365 + fill)
79
+ radius_5_5_idle: idle.radius === '5.5',
80
+ radius_5_5_working: working.radius === '5.5',
81
+ fill_d1fae5_idle: idle.fill === '#d1fae5',
82
+ fill_d1fae5_working: working.fill === '#d1fae5',
83
+ };
84
+ const ok = Object.values(results).every(Boolean);
85
+ console.log(`${ok ? '✅' : '❌'} hub-highlight idle opacity 0.9 → 0.95:`, JSON.stringify(results),
86
+ '\n idle: ', idle,
87
+ '\n working:', working);
88
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,90 @@
1
+ /* Round 365 verification: hub-center 'lit-lamp' decorative highlight
2
+ * circle radius 5 → 5.5. Visible when workingCount === 0 (idle fleet
3
+ * decor per R130). Sibling visual-weight bump family — extends to
4
+ * 6 canvas anchors:
5
+ * R287 minimap viewport stroke 1 → 1.5
6
+ * R295 legend swatch base radius 5.5 → 6
7
+ * R359 recent-row pip base radius 1.6 → 1.8
8
+ * R360 hub digit fontSize 11 → 12
9
+ * R361 edge-badge digit fontSize 10 → 11
10
+ * R365 hub-highlight base radius 5 → 5.5 (this round)
11
+ *
12
+ * 21 % area bump (π·5.5² / π·5² = 1.21). Still well inside r=10 core.
13
+ *
14
+ * Test approach: feed an IDLE fleet (no working sessions) so the
15
+ * highlight is fully opaque.
16
+ *
17
+ * Contract:
18
+ * - data-topo-hub-highlight element r attr === '5.5'.
19
+ * - data-topo-hub-highlight-radius === '5.5'.
20
+ * - data-topo-hub-highlight-visible === 'true' (idle fleet).
21
+ * - opacity attr === '0.9' (workingCount === 0 branch).
22
+ * - Pre-R365 invariants:
23
+ * * cx + cy unchanged (visual center stable)
24
+ * * fill='#d1fae5' (R130 lamp color)
25
+ * * style.transition contains opacity (R213 always-mount gate)
26
+ * * pointerEvents: 'none' (R130)
27
+ */
28
+ import { chromium } from 'playwright';
29
+ import { readFileSync } from 'node:fs';
30
+
31
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
32
+ const browser = await chromium.launch({ headless: true });
33
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
34
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
35
+ await ctx.addInitScript(() => {
36
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
37
+ });
38
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
39
+ await ctx.route('**/api/hub/status*', async (route) => {
40
+ const r = await route.fetch();
41
+ const b = await r.json();
42
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
43
+ // IDLE: status !== 'working' so workingCount=0 and the highlight is opaque.
44
+ const mk = (alias) => ({
45
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
46
+ network_id: nid, project_dir: null,
47
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
48
+ });
49
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b'), mk('c') ] } });
50
+ });
51
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
52
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
53
+
54
+ const page = await ctx.newPage();
55
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
56
+ await page.waitForSelector('[data-topo-hub-highlight]', { timeout: 15000 });
57
+ await page.waitForTimeout(300);
58
+
59
+ const probe = await page.evaluate(() => {
60
+ const h = document.querySelector('[data-topo-hub-highlight]');
61
+ const cs = h ? getComputedStyle(h) : null;
62
+ return {
63
+ rAttr: h?.getAttribute('r') ?? null,
64
+ rData: h?.getAttribute('data-topo-hub-highlight-radius') ?? null,
65
+ visibleAttr: h?.getAttribute('data-topo-hub-highlight-visible') ?? null,
66
+ opacityAttr: h?.getAttribute('opacity') ?? null,
67
+ fill: h?.getAttribute('fill') ?? null,
68
+ transition: cs?.transition ?? null,
69
+ pointerEv: cs?.pointerEvents ?? null,
70
+ };
71
+ });
72
+
73
+ await browser.close();
74
+
75
+ const hasTrans = (s, prop) =>
76
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
77
+
78
+ const results = {
79
+ r_attr_5_5: probe.rAttr === '5.5',
80
+ r_data_5_5: probe.rData === '5.5',
81
+ visible_true: probe.visibleAttr === 'true',
82
+ opacity_0_9: probe.opacityAttr === '0.9',
83
+ fill_d1fae5: probe.fill === '#d1fae5',
84
+ trans_has_opacity: hasTrans(probe.transition, 'opacity'),
85
+ pointer_events_none: probe.pointerEv === 'none',
86
+ };
87
+ const ok = Object.values(results).every(Boolean);
88
+ console.log(`${ok ? '✅' : '❌'} hub-highlight radius 5 → 5.5:`, JSON.stringify(results),
89
+ '\n probe:', probe);
90
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,96 @@
1
+ /* Round 370 verification: hub hover-ring cyber opacity 0.7 → 0.8.
2
+ * R177 designed the hub hover-ring at opacity-0 → 0.85 (light) / 0 →
3
+ * 0.7 (cyber). The 15 % gap between themes meant cyber-theme users
4
+ * got a noticeably softer hover cue than light-theme users against
5
+ * backgrounds that should equalise (dark bg needs more luminance to
6
+ * read as 'on'). R370 bumps cyber 0.7 → 0.8, narrowing the theme
7
+ * gap to 5 % (light stays at 0.85).
8
+ *
9
+ * Contract (cyber theme):
10
+ * - Rest (hub NOT hovered): hub-hover-ring opacity attr === '0'.
11
+ * - Hover hub: opacity attr === '0.8'.
12
+ * - data-topo-hub-hover-ring-opacity === '0.8' on hover.
13
+ * - Pre-R370 invariants:
14
+ * * r=17 on hover (R177)
15
+ * * stroke=#10b981 (cyber, R253)
16
+ * * strokeWidth=1.5
17
+ * * pointerEvents:none
18
+ * * R177 r + opacity transitions preserved
19
+ */
20
+ import { chromium } from 'playwright';
21
+ import { readFileSync } from 'node:fs';
22
+
23
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
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(() => {
28
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
29
+ });
30
+ const fresh = new Date(Date.now() - 60 * 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
+ const mk = (alias) => ({
36
+ alias, status: 'working', 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: [ mk('a'), mk('b'), mk('c') ] } });
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-topo-hub-hover-ring]', { timeout: 15000 });
48
+ await page.waitForTimeout(300);
49
+
50
+ const restProbe = await page.evaluate(() => {
51
+ const r = document.querySelector('[data-topo-hub-hover-ring]');
52
+ const cs = r ? getComputedStyle(r) : null;
53
+ return {
54
+ opacityAttr: r?.getAttribute('opacity') ?? null,
55
+ opacityData: r?.getAttribute('data-topo-hub-hover-ring-opacity') ?? null,
56
+ rAttr: r?.getAttribute('r') ?? null,
57
+ radiusData: r?.getAttribute('data-topo-hub-hover-ring-radius') ?? null,
58
+ strokeAttr: r?.getAttribute('stroke') ?? null,
59
+ strokeWidthAttr: r?.getAttribute('stroke-width') ?? null,
60
+ pointerEv: cs?.pointerEvents ?? null,
61
+ };
62
+ });
63
+
64
+ // Hover the hub group.
65
+ await page.hover('[data-topo-hub]');
66
+ await page.waitForTimeout(300);
67
+ const hoverProbe = await page.evaluate(() => {
68
+ const r = document.querySelector('[data-topo-hub-hover-ring]');
69
+ return {
70
+ opacityAttr: r?.getAttribute('opacity') ?? null,
71
+ opacityData: r?.getAttribute('data-topo-hub-hover-ring-opacity') ?? null,
72
+ rAttr: r?.getAttribute('r') ?? null,
73
+ radiusData: r?.getAttribute('data-topo-hub-hover-ring-radius') ?? null,
74
+ };
75
+ });
76
+
77
+ await browser.close();
78
+
79
+ const results = {
80
+ rest_opacity_0: restProbe.opacityAttr === '0',
81
+ rest_opacity_data_0: restProbe.opacityData === '0',
82
+ rest_r_14: restProbe.rAttr === '14', // R177 rest
83
+ rest_radius_data_14: restProbe.radiusData === '14',
84
+ rest_stroke_cyber: restProbe.strokeAttr === '#10b981', // R253 cyber
85
+ rest_stroke_width_1_5: restProbe.strokeWidthAttr === '1.5', // R51 sentinel reserved but R177
86
+ pointer_events_none: restProbe.pointerEv === 'none',
87
+ hover_opacity_0_8: hoverProbe.opacityAttr === '0.8', // R370 cyber bump
88
+ hover_opacity_data_0_8: hoverProbe.opacityData === '0.8',
89
+ hover_r_17: hoverProbe.rAttr === '17', // R177 hover lift
90
+ hover_radius_data_17: hoverProbe.radiusData === '17',
91
+ };
92
+ const ok = Object.values(results).every(Boolean);
93
+ console.log(`${ok ? '✅' : '❌'} hub hover-ring opacity 0.7 → 0.8 (cyber):`, JSON.stringify(results),
94
+ '\n rest: ', restProbe,
95
+ '\n hover:', hoverProbe);
96
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,86 @@
1
+ /* Round 385 verification: hub hover-ring strokeWidth 1.5 → 1.75.
2
+ * Sibling visual-weight bump (11th anchor) to R367 edge-badge rest
3
+ * stroke 1 → 1.25. The ring is only visible during hub hover
4
+ * (opacity=0 rest), so this manifests as a thicker hover-state
5
+ * ring on the canvas focal point.
6
+ *
7
+ * Contract:
8
+ * - [data-topo-hub-hover-ring] stroke-width attr === '1.75'.
9
+ * - data-topo-hub-hover-ring-stroke-width === '1.75'.
10
+ * - Pre-R385 invariants on the same circle:
11
+ * * R177 r=14 (rest) / 17 (hover)
12
+ * * R253 stroke = isLight ? '#059669' : '#10b981'
13
+ * * R370 opacity 0 (rest) / 0.8 cyber on hover
14
+ * * pointerEvents:none
15
+ * - Stays clear of R51 sentinel strokeWidth 3 (1.75 < 3).
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 browser = await chromium.launch({ headless: true });
22
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
23
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
24
+ await ctx.addInitScript(() => {
25
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
26
+ });
27
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
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: 'working', 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('a'), mk('b'), mk('c') ] } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
40
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
41
+
42
+ const page = await ctx.newPage();
43
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
44
+ await page.waitForSelector('[data-topo-hub-hover-ring]', { timeout: 15000 });
45
+ await page.waitForTimeout(300);
46
+
47
+ const restProbe = await page.evaluate(() => {
48
+ const r = document.querySelector('[data-topo-hub-hover-ring]');
49
+ return {
50
+ strokeWidthAttr: r?.getAttribute('stroke-width') ?? null,
51
+ strokeWidthData: r?.getAttribute('data-topo-hub-hover-ring-stroke-width') ?? null,
52
+ rAttr: r?.getAttribute('r') ?? null,
53
+ opacityAttr: r?.getAttribute('opacity') ?? null,
54
+ strokeAttr: r?.getAttribute('stroke') ?? null,
55
+ };
56
+ });
57
+
58
+ // Hover hub to verify visible state.
59
+ await page.hover('[data-topo-hub]');
60
+ await page.waitForTimeout(300);
61
+ const hoverProbe = await page.evaluate(() => {
62
+ const r = document.querySelector('[data-topo-hub-hover-ring]');
63
+ return {
64
+ strokeWidthAttr: r?.getAttribute('stroke-width') ?? null,
65
+ rAttr: r?.getAttribute('r') ?? null,
66
+ opacityAttr: r?.getAttribute('opacity') ?? null,
67
+ };
68
+ });
69
+
70
+ await browser.close();
71
+
72
+ const results = {
73
+ rest_stroke_width_1_75: restProbe.strokeWidthAttr === '1.75',
74
+ rest_data_stroke_1_75: restProbe.strokeWidthData === '1.75',
75
+ rest_r_14: restProbe.rAttr === '14', // R177 rest
76
+ rest_opacity_0: restProbe.opacityAttr === '0',
77
+ rest_stroke_cyber: restProbe.strokeAttr === '#10b981', // R253 cyber
78
+ hover_stroke_width_1_75: hoverProbe.strokeWidthAttr === '1.75', // strokeWidth doesn't change with hover
79
+ hover_r_17: hoverProbe.rAttr === '17', // R177 hover lift
80
+ hover_opacity_0_8: hoverProbe.opacityAttr === '0.8', // R370 cyber bump
81
+ };
82
+ const ok = Object.values(results).every(Boolean);
83
+ console.log(`${ok ? '✅' : '❌'} hub hover-ring strokeWidth 1.5 → 1.75:`, JSON.stringify(results),
84
+ '\n rest: ', restProbe,
85
+ '\n hover:', hoverProbe);
86
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,80 @@
1
+ /* Round 382 verification: hub-spoke path strokeLinecap='round'.
2
+ * Sibling SVG stroke-softening polish to R378 flow-rail dashes +
3
+ * R380 group box dashes — three dashed-stroke surfaces now share
4
+ * 'round' linecap.
5
+ *
6
+ * Dashed-stroke linecap family snapshot post-R382:
7
+ * R378 flow-rail '2 12' -> soft 3-px pills
8
+ * R380 group box '6 6' -> soft 7.5-px pills
9
+ * R382 hub spoke '6 14' -> soft 7-px pills (this round)
10
+ *
11
+ * Contract:
12
+ * - Every [data-topo-hub-spoke-active] path has stroke-linecap='round'.
13
+ * - data-topo-hub-spoke-linecap === 'round'.
14
+ * - Idle spokes still have strokeDasharray='6 14' (R382 doesn't touch
15
+ * the dasharray); active spokes still have 'none'.
16
+ * - Pre-R382 invariants preserved on each spoke: strokeWidth (1 idle /
17
+ * 2 active), R241 stroke + stroke-width + opacity 250ms transition.
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
+ 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: 'working', 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('a'), mk('b'), mk('c') ] } });
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-topo-hub-spoke-active]', { timeout: 15000 });
47
+ await page.waitForTimeout(300);
48
+
49
+ const probe = await page.evaluate(() => {
50
+ const spokes = Array.from(document.querySelectorAll('[data-topo-hub-spoke-active]'));
51
+ return spokes.map(el => ({
52
+ activeAttr: el.getAttribute('data-topo-hub-spoke-active'),
53
+ linecapAttr: el.getAttribute('stroke-linecap'),
54
+ linecapData: el.getAttribute('data-topo-hub-spoke-linecap'),
55
+ strokeWidthAttr: el.getAttribute('stroke-width'),
56
+ dasharrayAttr: el.getAttribute('stroke-dasharray'),
57
+ }));
58
+ });
59
+
60
+ await browser.close();
61
+
62
+ const allOk = probe.length >= 1 && probe.every(s =>
63
+ s.linecapAttr === 'round'
64
+ && s.linecapData === 'round'
65
+ && (s.activeAttr === 'true'
66
+ ? (s.strokeWidthAttr === '2' && s.dasharrayAttr === 'none')
67
+ : (s.strokeWidthAttr === '1' && s.dasharrayAttr === '6 14'))
68
+ );
69
+
70
+ const results = {
71
+ spokes_rendered: probe.length >= 1,
72
+ all_linecap_round: probe.every(s => s.linecapAttr === 'round'),
73
+ all_data_linecap: probe.every(s => s.linecapData === 'round'),
74
+ per_state_invariants_intact: allOk,
75
+ };
76
+ const ok = Object.values(results).every(Boolean);
77
+ console.log(`${ok ? '✅' : '❌'} hub-spoke strokeLinecap='round':`, JSON.stringify(results),
78
+ '\n count:', probe.length,
79
+ '\n sample:', probe.slice(0, 3));
80
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,109 @@
1
+ /* Round 351 verification: Layout-toggle Ring|Grid buttons gain
2
+ * hover-state letter-spacing (Tailwind hover:tracking-wide =
3
+ * 0.025em). Extends the R344 (footer) / R345 (panel titles) /
4
+ * R347 (zoom-level readout) hover-letter-spacing family to a
5
+ * 4th surface — the chrome-strip Ring/Grid layout-toggle pair.
6
+ *
7
+ * Pure-CSS approach: no new React state. transition-colors
8
+ * className dropped in favour of an inline transition spec
9
+ * that bundles bg/color (150ms ease) + letter-spacing (200ms
10
+ * ease-out) so the tracking-wide hover doesn't snap. Grid
11
+ * button additionally keeps border-color in the transition
12
+ * list (R268 theme-ease).
13
+ *
14
+ * Contract (cyber):
15
+ * - Rest: both buttons computed letter-spacing === 'normal' (0).
16
+ * - Hover Ring: computed letter-spacing === '0.4px' (0.025em ×
17
+ * ~14-16 base font; we'll accept any value > 0.1px).
18
+ * - Hover Grid: same.
19
+ * - Inline transition on both contains 'letter-spacing'.
20
+ * - Pre-R351 invariants: data-topo-chrome-layout attrs intact,
21
+ * R268 border-color transition still present on Grid.
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-topo-chrome-layout="ring"]', { timeout: 15000 });
51
+ await page.waitForSelector('[data-topo-chrome-layout="grid"]', { timeout: 5000 });
52
+ await page.waitForTimeout(300);
53
+
54
+ const restProbe = await page.evaluate(() => {
55
+ const r = document.querySelector('[data-topo-chrome-layout="ring"]');
56
+ const g = document.querySelector('[data-topo-chrome-layout="grid"]');
57
+ const rcs = r ? getComputedStyle(r) : null;
58
+ const gcs = g ? getComputedStyle(g) : null;
59
+ return {
60
+ ringLs: rcs?.letterSpacing ?? null,
61
+ ringTrans: rcs?.transition ?? null,
62
+ gridLs: gcs?.letterSpacing ?? null,
63
+ gridTrans: gcs?.transition ?? null,
64
+ };
65
+ });
66
+
67
+ // Hover Ring button.
68
+ await page.hover('[data-topo-chrome-layout="ring"]');
69
+ await page.waitForTimeout(300);
70
+ const ringHover = await page.evaluate(() => {
71
+ const r = document.querySelector('[data-topo-chrome-layout="ring"]');
72
+ return { ls: r ? getComputedStyle(r).letterSpacing : null };
73
+ });
74
+
75
+ // Move to Grid button.
76
+ await page.hover('[data-topo-chrome-layout="grid"]');
77
+ await page.waitForTimeout(300);
78
+ const gridHover = await page.evaluate(() => {
79
+ const g = document.querySelector('[data-topo-chrome-layout="grid"]');
80
+ return { ls: g ? getComputedStyle(g).letterSpacing : null };
81
+ });
82
+
83
+ await browser.close();
84
+
85
+ const isRestLs = (v) => v === 'normal' || v === '0px' || v === '0' || v === null;
86
+ const isHoverLs = (v) => {
87
+ // tracking-wide = 0.025em. At base 14-16px font, that's ~0.35-0.4px.
88
+ if (!v || v === 'normal') return false;
89
+ const n = parseFloat(v);
90
+ return Number.isFinite(n) && n > 0.2 && n < 1.0;
91
+ };
92
+ const hasTrans = (s, prop) =>
93
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
94
+
95
+ const results = {
96
+ rest_ring_ls_zero: isRestLs(restProbe.ringLs),
97
+ rest_grid_ls_zero: isRestLs(restProbe.gridLs),
98
+ ring_trans_has_ls: hasTrans(restProbe.ringTrans, 'letter-spacing'),
99
+ grid_trans_has_ls: hasTrans(restProbe.gridTrans, 'letter-spacing'),
100
+ grid_trans_has_bcol: hasTrans(restProbe.gridTrans, 'border-color'), // R268
101
+ hover_ring_tracked: isHoverLs(ringHover.ls),
102
+ hover_grid_tracked: isHoverLs(gridHover.ls),
103
+ };
104
+ const ok = Object.values(results).every(Boolean);
105
+ console.log(`${ok ? '✅' : '❌'} layout-toggle hover tracking-wide:`, JSON.stringify(results),
106
+ '\n rest: ', restProbe,
107
+ '\n hover ring: ', ringHover,
108
+ '\n hover grid: ', gridHover);
109
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,87 @@
1
+ /* Round 375 verification: Layout-toggle wrapper rounded-md → rounded-
2
+ * lg (6 → 8 px). Extends the R330/R331/R332 corner-radius cascade to
3
+ * the chrome-strip layout-toggle wrapper. Pre-R375 the wrapper at
4
+ * rounded-md (6 px) was the only chrome-strip container still using
5
+ * the smaller corner radius — both R330 outer wrapper and R332
6
+ * minimap sit at ≥ 8 px; R375 brings the toggle into the rounded-lg
7
+ * tier where minimap already lives.
8
+ *
9
+ * Contract:
10
+ * - data-topo-chrome-layout-trailer element computed border-radius
11
+ * === '8px' (rounded-lg).
12
+ * - data-topo-chrome-layout-radius === 'rounded-lg'.
13
+ * - Pre-R375 invariants:
14
+ * * border still present (1 px default Tailwind)
15
+ * * R268 inline borderColor + 200ms border-color transition
16
+ * * R329 mr-0.5 spacing preserved
17
+ * * inline-flex + overflow-hidden preserved
18
+ * * Inner buttons (Ring + Grid) still mounted as flex children
19
+ */
20
+ import { chromium } from 'playwright';
21
+ import { readFileSync } from 'node:fs';
22
+
23
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
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(() => {
28
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
29
+ });
30
+ const fresh = new Date(Date.now() - 60 * 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
+ const mk = (alias) => ({
36
+ alias, status: 'working', 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: [ mk('a'), mk('b'), mk('c') ] } });
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-topo-chrome-layout-trailer]', { timeout: 15000 });
48
+ await page.waitForTimeout(300);
49
+
50
+ const probe = await page.evaluate(() => {
51
+ const w = document.querySelector('[data-topo-chrome-layout-trailer]');
52
+ const cs = w ? getComputedStyle(w) : null;
53
+ const ring = w?.querySelector('[data-topo-chrome-layout="ring"]');
54
+ const grid = w?.querySelector('[data-topo-chrome-layout="grid"]');
55
+ return {
56
+ borderRadius: cs?.borderRadius ?? null,
57
+ radiusData: w?.getAttribute('data-topo-chrome-layout-radius') ?? null,
58
+ borderWidth: cs?.borderWidth ?? null,
59
+ display: cs?.display ?? null,
60
+ overflow: cs?.overflow ?? null,
61
+ transition: cs?.transition ?? null,
62
+ cls: w?.getAttribute('class') ?? null,
63
+ ringPresent: !!ring,
64
+ gridPresent: !!grid,
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 results = {
74
+ border_radius_8px: probe.borderRadius === '8px', // rounded-lg
75
+ data_radius_lg: probe.radiusData === 'rounded-lg',
76
+ border_width_1px: probe.borderWidth === '1px',
77
+ display_inline_flex: probe.display === 'inline-flex' || probe.display === 'flex',
78
+ overflow_hidden: probe.overflow === 'hidden',
79
+ trans_has_border_color: hasTrans(probe.transition, 'border-color'), // R268
80
+ cls_has_mr_0_5: /mr-0\.5/.test(probe.cls || ''), // R329
81
+ ring_button_present: probe.ringPresent,
82
+ grid_button_present: probe.gridPresent,
83
+ };
84
+ const ok = Object.values(results).every(Boolean);
85
+ console.log(`${ok ? '✅' : '❌'} Layout-toggle wrapper rounded-md → rounded-lg:`, JSON.stringify(results),
86
+ '\n probe:', probe);
87
+ process.exit(ok ? 0 : 1);