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

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/0jwtl1a1lwyp5.js +1 -0
  130. package/.next/static/chunks/0lpe~8qw.tb2-.js +4 -0
  131. package/.next/static/chunks/0sl9zcww9om1e.js +1 -0
  132. package/.next/trace +2 -2
  133. package/.next/trace-build +1 -1
  134. package/app/components/TopoGraph.tsx +155 -17
  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-more-flows-unit-test.mjs +95 -0
  141. package/scripts/topo-panel-count-letterspacing-test.mjs +89 -0
  142. package/scripts/topo-panel-rect-opacity-hover-test.mjs +109 -0
  143. package/scripts/topo-panel-title-hover-letterspacing-test.mjs +97 -0
  144. package/scripts/topo-pin-intersection-unit-test.mjs +100 -0
  145. package/scripts/topo-zoom-level-hover-letterspacing-test.mjs +91 -0
  146. package/.next/static/chunks/03v-0n3d~t1sm.js +0 -4
  147. package/.next/static/chunks/0vt5wron8jig5.js +0 -1
  148. package/.next/static/chunks/180biiw78-hb_.js +0 -1
  149. /package/.next/static/{ypixDJRds4g4TLoUdmrwN → O8CVuAcu03lV_emn4fTFW}/_buildManifest.js +0 -0
  150. /package/.next/static/{ypixDJRds4g4TLoUdmrwN → O8CVuAcu03lV_emn4fTFW}/_clientMiddlewareManifest.js +0 -0
  151. /package/.next/static/{ypixDJRds4g4TLoUdmrwN → O8CVuAcu03lV_emn4fTFW}/_ssgManifest.js +0 -0
@@ -0,0 +1,97 @@
1
+ /* Round 345 verification: panel titles ("recent signal" + "legend")
2
+ * gain letter-spacing tween 0.3 → 0.4 on panel hover. Sibling to
3
+ * R344 hover-letter-spacing applied to the +N more flows footer —
4
+ * same gesture vocabulary at panel-title scope.
5
+ *
6
+ * Contract:
7
+ * - Rest: both titles letterSpacing 0.3.
8
+ * - Hover recent panel: recent-panel-title letterSpacing 0.4.
9
+ * - Hover legend panel: legend-panel-title letterSpacing 0.4
10
+ * (separately).
11
+ * - Inline transition list contains 'letter-spacing' on both.
12
+ * - R344 footer hover regression intact (when flowLinks > 3).
13
+ * - R317/R318/R294 chrome + pulse regressions intact.
14
+ */
15
+ import { chromium } from 'playwright';
16
+ import { readFileSync } from 'node:fs';
17
+
18
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
19
+ const browser = await chromium.launch({ headless: true });
20
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
21
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
22
+ await ctx.addInitScript(() => {
23
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
24
+ });
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
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) => ({
31
+ alias, status: 'working', 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: [ mk('alpha'), mk('beta'), mk('gamma') ] } });
36
+ });
37
+ // One message → recent-signal panel mounts so its title is testable.
38
+ const now = Date.now();
39
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
40
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping',
41
+ network_id: 'default', created_at: new Date(now - 5000).toISOString() },
42
+ ] } }));
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-recent-panel-title]', { timeout: 15000 });
48
+ await page.waitForSelector('[data-legend-panel-title]', { timeout: 5000 });
49
+ await page.waitForTimeout(300);
50
+
51
+ // Rest probe.
52
+ const restProbe = await page.evaluate(() => {
53
+ const r = document.querySelector('[data-recent-panel-title]');
54
+ const l = document.querySelector('[data-legend-panel-title]');
55
+ return {
56
+ recentLetterSp: r?.getAttribute('letter-spacing') ?? null,
57
+ recentTrans: r ? getComputedStyle(r).transition : null,
58
+ legendLetterSp: l?.getAttribute('letter-spacing') ?? null,
59
+ legendTrans: l ? getComputedStyle(l).transition : null,
60
+ };
61
+ });
62
+
63
+ // Hover the recent panel via its data attribute.
64
+ await page.hover('[data-topo-panel="recent"]');
65
+ await page.waitForTimeout(300);
66
+ const recentHoverProbe = await page.evaluate(() => {
67
+ const r = document.querySelector('[data-recent-panel-title]');
68
+ return { letterSp: r?.getAttribute('letter-spacing') ?? null };
69
+ });
70
+
71
+ // Move to legend panel.
72
+ await page.hover('[data-topo-panel="legend"]');
73
+ await page.waitForTimeout(300);
74
+ const legendHoverProbe = await page.evaluate(() => {
75
+ const l = document.querySelector('[data-legend-panel-title]');
76
+ return { letterSp: l?.getAttribute('letter-spacing') ?? null };
77
+ });
78
+
79
+ await browser.close();
80
+
81
+ const hasLetterSpacingTrans = (s) =>
82
+ /letter-spacing\s+\d*\.?\d*s|letter-spacing\s+\d+ms/i.test(s || '');
83
+
84
+ const results = {
85
+ rest_recent_0_3: restProbe.recentLetterSp === '0.3',
86
+ rest_legend_0_3: restProbe.legendLetterSp === '0.3',
87
+ recent_transition_has_ls: hasLetterSpacingTrans(restProbe.recentTrans),
88
+ legend_transition_has_ls: hasLetterSpacingTrans(restProbe.legendTrans),
89
+ hover_recent_0_4: recentHoverProbe.letterSp === '0.4',
90
+ hover_legend_0_4: legendHoverProbe.letterSp === '0.4',
91
+ };
92
+ const ok = Object.values(results).every(Boolean);
93
+ console.log(`${ok ? '✅' : '❌'} panel title hover letter-spacing:`, JSON.stringify(results),
94
+ '\n rest: ', restProbe,
95
+ '\n hover recent:', recentHoverProbe,
96
+ '\n hover legend:', legendHoverProbe);
97
+ process.exit(ok ? 0 : 1);
@@ -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,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);