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

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 (151) 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.html +1 -1
  13. package/.next/server/app/_not-found.rsc +1 -1
  14. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  15. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  20. package/.next/server/app/admin.html +1 -1
  21. package/.next/server/app/admin.rsc +1 -1
  22. package/.next/server/app/admin.segments/_full.segment.rsc +1 -1
  23. package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  24. package/.next/server/app/admin.segments/_index.segment.rsc +1 -1
  25. package/.next/server/app/admin.segments/_tree.segment.rsc +1 -1
  26. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
  27. package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
  28. package/.next/server/app/index.html +2 -2
  29. package/.next/server/app/index.rsc +2 -2
  30. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  31. package/.next/server/app/index.segments/_full.segment.rsc +2 -2
  32. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  33. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  34. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  35. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  36. package/.next/server/app/login.html +2 -2
  37. package/.next/server/app/login.rsc +2 -2
  38. package/.next/server/app/login.segments/_full.segment.rsc +2 -2
  39. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  40. package/.next/server/app/login.segments/_index.segment.rsc +1 -1
  41. package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  42. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  43. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  44. package/.next/server/app/logs.html +1 -1
  45. package/.next/server/app/logs.rsc +1 -1
  46. package/.next/server/app/logs.segments/_full.segment.rsc +1 -1
  47. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  48. package/.next/server/app/logs.segments/_index.segment.rsc +1 -1
  49. package/.next/server/app/logs.segments/_tree.segment.rsc +1 -1
  50. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
  51. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  52. package/.next/server/app/messages.html +1 -1
  53. package/.next/server/app/messages.rsc +1 -1
  54. package/.next/server/app/messages.segments/_full.segment.rsc +1 -1
  55. package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
  56. package/.next/server/app/messages.segments/_index.segment.rsc +1 -1
  57. package/.next/server/app/messages.segments/_tree.segment.rsc +1 -1
  58. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
  59. package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
  60. package/.next/server/app/node.html +1 -1
  61. package/.next/server/app/node.rsc +1 -1
  62. package/.next/server/app/node.segments/_full.segment.rsc +1 -1
  63. package/.next/server/app/node.segments/_head.segment.rsc +1 -1
  64. package/.next/server/app/node.segments/_index.segment.rsc +1 -1
  65. package/.next/server/app/node.segments/_tree.segment.rsc +1 -1
  66. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
  67. package/.next/server/app/node.segments/node.segment.rsc +1 -1
  68. package/.next/server/app/nodes.html +1 -1
  69. package/.next/server/app/nodes.rsc +1 -1
  70. package/.next/server/app/nodes.segments/_full.segment.rsc +1 -1
  71. package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
  72. package/.next/server/app/nodes.segments/_index.segment.rsc +1 -1
  73. package/.next/server/app/nodes.segments/_tree.segment.rsc +1 -1
  74. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
  75. package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
  76. package/.next/server/app/page_client-reference-manifest.js +1 -1
  77. package/.next/server/app/server-logs.html +1 -1
  78. package/.next/server/app/server-logs.rsc +1 -1
  79. package/.next/server/app/server-logs.segments/_full.segment.rsc +1 -1
  80. package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
  81. package/.next/server/app/server-logs.segments/_index.segment.rsc +1 -1
  82. package/.next/server/app/server-logs.segments/_tree.segment.rsc +1 -1
  83. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
  84. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
  85. package/.next/server/app/settings/networks.html +1 -1
  86. package/.next/server/app/settings/networks.rsc +1 -1
  87. package/.next/server/app/settings/networks.segments/_full.segment.rsc +1 -1
  88. package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
  89. package/.next/server/app/settings/networks.segments/_index.segment.rsc +1 -1
  90. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +1 -1
  91. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
  92. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
  93. package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
  94. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  95. package/.next/server/app/settings/tokens.html +1 -1
  96. package/.next/server/app/settings/tokens.rsc +1 -1
  97. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +1 -1
  98. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
  99. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +1 -1
  100. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +1 -1
  101. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
  102. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
  103. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
  104. package/.next/server/app/settings.html +2 -2
  105. package/.next/server/app/settings.rsc +2 -2
  106. package/.next/server/app/settings.segments/_full.segment.rsc +2 -2
  107. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  108. package/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  109. package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  110. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  111. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  112. package/.next/server/app/tasks.html +1 -1
  113. package/.next/server/app/tasks.rsc +1 -1
  114. package/.next/server/app/tasks.segments/_full.segment.rsc +1 -1
  115. package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
  116. package/.next/server/app/tasks.segments/_index.segment.rsc +1 -1
  117. package/.next/server/app/tasks.segments/_tree.segment.rsc +1 -1
  118. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
  119. package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
  120. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  121. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  122. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  123. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  124. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  125. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  126. package/.next/server/middleware-build-manifest.js +3 -3
  127. package/.next/server/pages/404.html +1 -1
  128. package/.next/server/pages/500.html +1 -1
  129. package/.next/static/chunks/0qtk58rfhepeo.js +1 -0
  130. package/.next/static/chunks/0s2jh27ig6a.q.js +4 -0
  131. package/.next/static/chunks/17q6k~a.gl31q.js +1 -0
  132. package/.next/trace +2 -2
  133. package/.next/trace-build +1 -1
  134. package/app/components/TopoGraph.tsx +171 -16
  135. package/package.json +1 -1
  136. package/scripts/topo-active-links-freshness-color-test.mjs +86 -0
  137. package/scripts/topo-filter-pill-weight-test.mjs +75 -0
  138. package/scripts/topo-minimap-viewport-hover-test.mjs +109 -0
  139. package/scripts/topo-more-flows-hover-letterspacing-test.mjs +98 -0
  140. package/scripts/topo-panel-count-letterspacing-test.mjs +89 -0
  141. package/scripts/topo-panel-rect-opacity-hover-test.mjs +109 -0
  142. package/scripts/topo-panel-title-hover-letterspacing-test.mjs +97 -0
  143. package/scripts/topo-pin-intersection-unit-test.mjs +100 -0
  144. package/scripts/topo-reset-hover-rotate-test.mjs +102 -0
  145. package/scripts/topo-zoom-level-hover-letterspacing-test.mjs +91 -0
  146. package/.next/static/chunks/0wratil0li9xw.js +0 -4
  147. package/.next/static/chunks/10paf4uz-_0g1.js +0 -1
  148. package/.next/static/chunks/12wcm9omjb_x6.js +0 -1
  149. /package/.next/static/{4t8OH-lNFieRsHEC2eZfD → Ac38WRM1JjpuTYJKsfcUB}/_buildManifest.js +0 -0
  150. /package/.next/static/{4t8OH-lNFieRsHEC2eZfD → Ac38WRM1JjpuTYJKsfcUB}/_clientMiddlewareManifest.js +0 -0
  151. /package/.next/static/{4t8OH-lNFieRsHEC2eZfD → Ac38WRM1JjpuTYJKsfcUB}/_ssgManifest.js +0 -0
@@ -0,0 +1,100 @@
1
+ /* Round 341 verification: pin-intersection chip's middle " pins"
2
+ * unit word gets wrapped in an opacity-0.7 span. Completes the
3
+ * chip-internal hierarchy on this 3-tier chip:
4
+ * pinDimCount (prominent value, tabular-nums)
5
+ * " pins" (recessive unit, opacity-0.7) ← R341
6
+ * " · matchAliases" (recessive count, opacity-0.7 + tabular-nums)
7
+ *
8
+ * 7th surface in the R333-R340 chip-internal-hierarchy arc.
9
+ *
10
+ * Contract:
11
+ * - [data-pin-intersection-unit] present, className contains
12
+ * 'opacity-70', textContent contains 'pins'.
13
+ * - Computed opacity reads 0.7.
14
+ * - Sibling spans (count-dims, count-matches) preserved.
15
+ * - R340 +more flows unit + R317/R318 chrome regressions intact.
16
+ *
17
+ * Fixture: pin TWO filter dimensions (status + vendor) so the
18
+ * pin-intersection chip mounts.
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, model) => ({
36
+ alias, status: 'working', model, 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', 'claude-opus-4'),
42
+ mk('beta', 'gpt-4o'),
43
+ mk('gamma', 'internlm/internlm2'),
44
+ ] } });
45
+ });
46
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
47
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
48
+
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForSelector('[data-working-chip]', { timeout: 15000 });
52
+ await page.waitForTimeout(200);
53
+
54
+ // Pin status (working) + vendor (any letter) to trigger pin-intersection.
55
+ await page.click('[data-working-chip]', { delay: 50 }).catch(() => {});
56
+ await page.waitForTimeout(200);
57
+ await page.click('[data-vendor-letter]', { delay: 50 }).catch(() => {});
58
+ await page.waitForTimeout(300);
59
+
60
+ const probe = await page.evaluate(() => {
61
+ const unit = document.querySelector('[data-pin-intersection-unit]');
62
+ const dimsSpan = document.querySelector('[data-pin-intersection-count-dims]');
63
+ const matchSpan = document.querySelector('[data-pin-intersection-count-matches]');
64
+ const moreUnit = document.querySelector('[data-recent-panel-more-unit]');
65
+ return {
66
+ unitClass: unit?.className ?? '',
67
+ unitText: unit?.textContent ?? null,
68
+ unitOpacity: unit ? getComputedStyle(unit).opacity : null,
69
+ dimsPresent: dimsSpan !== null,
70
+ matchesClass: matchSpan?.className ?? '',
71
+ matchesOpacity: matchSpan ? getComputedStyle(matchSpan).opacity : null,
72
+ r340MoreUnitTag: moreUnit?.getAttribute('opacity') ?? null,
73
+ layoutInactiveCls: document.querySelector('[data-topo-chrome-layout="grid"]')?.className ?? '',
74
+ layoutActiveCls: document.querySelector('[data-topo-chrome-layout="ring"]')?.className ?? '',
75
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
76
+ };
77
+ });
78
+ await browser.close();
79
+
80
+ const opacityClose = (val) => val !== null && Math.abs(parseFloat(val) - 0.7) < 0.01;
81
+
82
+ const results = {
83
+ unit_present: probe.unitText !== null,
84
+ unit_has_opacity70: /opacity-70/.test(probe.unitClass),
85
+ unit_text_pins: (probe.unitText || '').trim() === 'pins',
86
+ unit_computed_0_7: opacityClose(probe.unitOpacity),
87
+ dims_sibling_present: probe.dimsPresent,
88
+ matches_sibling_opacity: /opacity-70/.test(probe.matchesClass),
89
+ // R340 regression — only when recent-signal panel mounted (>3 flows).
90
+ // No messages in fixture → accept null.
91
+ r340_more_unit_or_absent: probe.r340MoreUnitTag === null || probe.r340MoreUnitTag === '0.7',
92
+ // R317 / R318 chrome regression.
93
+ r317_inactive_gray_400: probe.layoutInactiveCls.includes('text-gray-400'),
94
+ r318_active_font_medium: probe.layoutActiveCls.includes('font-medium'),
95
+ r294_pulse_absent: probe.pulseCount === 0,
96
+ };
97
+ const ok = Object.values(results).every(Boolean);
98
+ console.log(`${ok ? '✅' : '❌'} pin-intersection " pins" unit:`, JSON.stringify(results),
99
+ '\n unit text:', JSON.stringify(probe.unitText), 'opacity:', probe.unitOpacity);
100
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,102 @@
1
+ /* Round 350 verification: chrome reset-button icon gains a hover-
2
+ * rotate preview of the R184 click-spin. Pre-R350 hovering the reset
3
+ * button only changed the button bg (white/5); the icon stayed still.
4
+ * R350 nudges the icon -8° on hover — tactile hint that this button
5
+ * rotates the icon on click. Gated on !resetSpinning so anet-reset-
6
+ * spin keyframe still owns transform during its 450 ms run.
7
+ * 350th-round milestone polish.
8
+ *
9
+ * Contract:
10
+ * - Rest: icon transform 'rotate(0deg)' (no rotation), transformOrigin
11
+ * 'center'.
12
+ * - Hover: icon transform 'rotate(-8deg)'.
13
+ * - Inline transition contains 'transform'.
14
+ * - data-topo-chrome-reset-hover toggles on container; data-topo-
15
+ * chrome-reset-icon-hover mirrors on the icon (only when !spinning).
16
+ * - Pre-R350 invariants: R288 strokeWidth=2.5 + R184 anet-reset-spin
17
+ * className gate intact (no spin running at rest).
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-chrome-reset]', { timeout: 15000 });
47
+ await page.waitForTimeout(300);
48
+
49
+ // Rest probe.
50
+ const restProbe = await page.evaluate(() => {
51
+ const btn = document.querySelector('[data-topo-chrome-reset]');
52
+ const ico = document.querySelector('[data-topo-chrome-reset-icon]');
53
+ const cs = ico ? getComputedStyle(ico) : null;
54
+ return {
55
+ btnHover: btn?.getAttribute('data-topo-chrome-reset-hover') ?? null,
56
+ icoHover: ico?.getAttribute('data-topo-chrome-reset-icon-hover') ?? null,
57
+ icoTransform: cs?.transform ?? null,
58
+ icoTransition: cs?.transition ?? null,
59
+ icoStrokeW: ico?.getAttribute('stroke-width') ?? null,
60
+ spinClass: ico?.getAttribute('class') ?? null,
61
+ };
62
+ });
63
+
64
+ // Hover the reset button.
65
+ await page.hover('[data-topo-chrome-reset]');
66
+ await page.waitForTimeout(300);
67
+ const hoverProbe = await page.evaluate(() => {
68
+ const btn = document.querySelector('[data-topo-chrome-reset]');
69
+ const ico = document.querySelector('[data-topo-chrome-reset-icon]');
70
+ const cs = ico ? getComputedStyle(ico) : null;
71
+ return {
72
+ btnHover: btn?.getAttribute('data-topo-chrome-reset-hover') ?? null,
73
+ icoHover: ico?.getAttribute('data-topo-chrome-reset-icon-hover') ?? null,
74
+ icoTransform: cs?.transform ?? null,
75
+ };
76
+ });
77
+
78
+ await browser.close();
79
+
80
+ const hasTrans = (s) => /transform\s+\d*\.?\d*s|transform\s+\d+ms/i.test(s || '');
81
+ // computed transform 'none' or a matrix near identity at rest.
82
+ const isRestTransform = (t) => t === 'none' || /matrix\(1, 0, 0, 1, 0, 0\)/.test(t || '') ||
83
+ /matrix\(0\.999/i.test(t || '') /* tiny rounding tolerance */;
84
+ // computed transform for -8deg ≈ matrix(0.990, -0.139, 0.139, 0.990, 0, 0).
85
+ const isHoverTransform = (t) => /matrix\(0\.99/i.test(t || '') && /-0\.13/i.test(t || '');
86
+
87
+ const results = {
88
+ rest_btn_false: restProbe.btnHover === 'false',
89
+ rest_ico_false: restProbe.icoHover === 'false',
90
+ rest_no_rotation: isRestTransform(restProbe.icoTransform),
91
+ trans_has_transform: hasTrans(restProbe.icoTransition),
92
+ rest_strokew_2_5: restProbe.icoStrokeW === '2.5', // R288
93
+ rest_no_spin_class: !/anet-reset-spin/.test(restProbe.spinClass || ''),
94
+ hover_btn_true: hoverProbe.btnHover === 'true',
95
+ hover_ico_true: hoverProbe.icoHover === 'true',
96
+ hover_rotated: isHoverTransform(hoverProbe.icoTransform),
97
+ };
98
+ const ok = Object.values(results).every(Boolean);
99
+ console.log(`${ok ? '✅' : '❌'} reset icon hover-rotate:`, JSON.stringify(results),
100
+ '\n rest: ', restProbe,
101
+ '\n hover:', hoverProbe);
102
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,91 @@
1
+ /* Round 347 verification: zoom-level readout in the chrome strip
2
+ * gains a hover-state letter-spacing tween 0 → 0.5 px. Extends the
3
+ * R344/R345 hover-letter-spacing family from panel/footer surfaces
4
+ * into the HTML chrome strip. The readout is sandwiched between
5
+ * zoom-out / zoom-in buttons; hovering it spreads digits 0.5 px,
6
+ * signalling "this is alive". tabular-nums + minWidth: 46 from R225
7
+ * lock the column so the tween doesn't shove neighbouring controls.
8
+ *
9
+ * Contract:
10
+ * - Rest: zoom-level letter-spacing 0 (or 'normal' / '0px').
11
+ * - Hover: letter-spacing 0.5px.
12
+ * - data-topo-chrome-zoom-level-hover toggles false → true.
13
+ * - transition list contains 'letter-spacing'.
14
+ * - Pre-R347 invariants intact: R264 color + border-color transitions
15
+ * preserved; tabular-nums + minWidth=46 still applied.
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-chrome-zoom-level]', { timeout: 15000 });
45
+ await page.waitForTimeout(400);
46
+
47
+ const restProbe = await page.evaluate(() => {
48
+ const el = document.querySelector('[data-topo-chrome-zoom-level]');
49
+ const cs = el ? getComputedStyle(el) : null;
50
+ return {
51
+ ls: cs?.letterSpacing ?? null,
52
+ transition: cs?.transition ?? null,
53
+ hoverAttr: el?.getAttribute('data-topo-chrome-zoom-level-hover') ?? null,
54
+ minWidth: cs?.minWidth ?? null,
55
+ fontVariant: cs?.fontVariantNumeric ?? null,
56
+ };
57
+ });
58
+
59
+ await page.hover('[data-topo-chrome-zoom-level]');
60
+ await page.waitForTimeout(250);
61
+ const hoverProbe = await page.evaluate(() => {
62
+ const el = document.querySelector('[data-topo-chrome-zoom-level]');
63
+ const cs = el ? getComputedStyle(el) : null;
64
+ return {
65
+ ls: cs?.letterSpacing ?? null,
66
+ hoverAttr: el?.getAttribute('data-topo-chrome-zoom-level-hover') ?? null,
67
+ };
68
+ });
69
+
70
+ await browser.close();
71
+
72
+ const hasTrans = (s, prop) =>
73
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
74
+ const isRestLs = (v) => v === 'normal' || v === '0px' || v === '0' || v === null;
75
+
76
+ const results = {
77
+ rest_ls_zero: isRestLs(restProbe.ls),
78
+ rest_hover_attr_false: restProbe.hoverAttr === 'false',
79
+ trans_has_letterspacing: hasTrans(restProbe.transition, 'letter-spacing'),
80
+ trans_has_color: hasTrans(restProbe.transition, 'color'), // R264 preserved
81
+ trans_has_border: hasTrans(restProbe.transition, 'border-color'),
82
+ min_width_46: restProbe.minWidth === '46px',
83
+ tabular_nums: /tabular-nums/.test(restProbe.fontVariant || ''),
84
+ hover_ls_0_5: hoverProbe.ls === '0.5px',
85
+ hover_attr_true: hoverProbe.hoverAttr === 'true',
86
+ };
87
+ const ok = Object.values(results).every(Boolean);
88
+ console.log(`${ok ? '✅' : '❌'} zoom level hover letter-spacing:`, JSON.stringify(results),
89
+ '\n rest: ', restProbe,
90
+ '\n hover:', hoverProbe);
91
+ process.exit(ok ? 0 : 1);