@sleep2agi/agent-network-dashboard 0.5.1-preview.5 → 0.5.1-preview.51
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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +6 -6
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +2 -2
- package/.next/server/app/admin.rsc +2 -2
- package/.next/server/app/admin.segments/_full.segment.rsc +2 -2
- package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_index.segment.rsc +2 -2
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +3 -3
- package/.next/server/app/login.segments/_full.segment.rsc +3 -3
- package/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/server/app/login.segments/_index.segment.rsc +2 -2
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +2 -2
- package/.next/server/app/logs.rsc +2 -2
- package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
- package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +2 -2
- package/.next/server/app/messages.rsc +2 -2
- package/.next/server/app/messages.segments/_full.segment.rsc +2 -2
- package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_index.segment.rsc +2 -2
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +2 -2
- package/.next/server/app/node.rsc +2 -2
- package/.next/server/app/node.segments/_full.segment.rsc +2 -2
- package/.next/server/app/node.segments/_head.segment.rsc +1 -1
- package/.next/server/app/node.segments/_index.segment.rsc +2 -2
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/node.segments/node.segment.rsc +1 -1
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +2 -2
- package/.next/server/app/nodes.rsc +2 -2
- package/.next/server/app/nodes.segments/_full.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_index.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +2 -2
- package/.next/server/app/server-logs.rsc +2 -2
- package/.next/server/app/server-logs.segments/_full.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_index.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +2 -2
- package/.next/server/app/settings/networks.rsc +2 -2
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +2 -2
- package/.next/server/app/settings/tokens.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +3 -3
- package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
- package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +2 -2
- package/.next/server/app/tasks.rsc +2 -2
- package/.next/server/app/tasks.segments/_full.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_index.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/0bi-134kq-t5m.js +4 -0
- package/.next/static/chunks/0hwb_h953qm4d.css +2 -0
- package/.next/static/chunks/0sf4jjluqr_2t.js +1 -0
- package/.next/static/chunks/109thlclcb2ed.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/TopoGraph.tsx +986 -65
- package/package.json +1 -1
- package/scripts/topo-chip-digit-fontweight-test.mjs +105 -0
- package/scripts/topo-chrome-segmented-radius-test.mjs +100 -0
- package/scripts/topo-edge-badge-fontsize-test.mjs +90 -0
- package/scripts/topo-edge-badge-opacity-test.mjs +80 -0
- package/scripts/topo-edge-badge-stroke-test.mjs +92 -0
- package/scripts/topo-edge-visible-linecap-test.mjs +89 -0
- package/scripts/topo-filter-pill-hover-opacity-test.mjs +110 -0
- package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
- package/scripts/topo-flow-rail-linecap-test.mjs +79 -0
- package/scripts/topo-freshness-chip-tabular-test.mjs +41 -0
- package/scripts/topo-freshness-floor-lift-test.mjs +92 -0
- package/scripts/topo-freshness-suffix-tabular-test.mjs +88 -0
- package/scripts/topo-fullscreen-icon-hover-scale-test.mjs +91 -0
- package/scripts/topo-group-box-stroke-test.mjs +105 -0
- package/scripts/topo-group-label-count-fontweight-test.mjs +108 -0
- package/scripts/topo-hover-detail-body-fw-test.mjs +101 -0
- package/scripts/topo-hover-detail-model-fw-test.mjs +98 -0
- package/scripts/topo-hover-detail-opacity-test.mjs +98 -0
- package/scripts/topo-hover-detail-rx-test.mjs +81 -0
- package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
- package/scripts/topo-hub-highlight-opacity-test.mjs +88 -0
- package/scripts/topo-hub-highlight-radius-test.mjs +90 -0
- package/scripts/topo-hub-hover-ring-opacity-test.mjs +96 -0
- package/scripts/topo-hub-hover-ring-stroke-test.mjs +86 -0
- package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
- package/scripts/topo-layout-toggle-hover-tracking-test.mjs +109 -0
- package/scripts/topo-layout-toggle-radius-test.mjs +87 -0
- package/scripts/topo-legend-label-fontweight-test.mjs +94 -0
- package/scripts/topo-minimap-offline-opacity-test.mjs +90 -0
- package/scripts/topo-minimap-online-radius-test.mjs +85 -0
- package/scripts/topo-minimap-viewport-hover-test.mjs +109 -0
- package/scripts/topo-minimap-viewport-linejoin-test.mjs +75 -0
- package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
- package/scripts/topo-panel-count-letterspacing-test.mjs +89 -0
- package/scripts/topo-panel-rect-opacity-hover-test.mjs +109 -0
- package/scripts/topo-panel-title-hover-letterspacing-test.mjs +97 -0
- package/scripts/topo-pressure-bar-height-test.mjs +92 -0
- package/scripts/topo-pressure-kicker-fontweight-test.mjs +76 -0
- package/scripts/topo-recent-pip-radius-2-test.mjs +72 -0
- package/scripts/topo-recent-pip-radius-test.mjs +76 -0
- package/scripts/topo-recent-row-text-fontweight-test.mjs +90 -0
- package/scripts/topo-reset-hover-rotate-test.mjs +102 -0
- package/scripts/topo-vendor-glyph-fontweight-test.mjs +102 -0
- package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
- package/scripts/topo-zoom-icon-hover-scale-test.mjs +114 -0
- package/scripts/topo-zoom-level-hover-letterspacing-test.mjs +91 -0
- package/.next/static/chunks/020yd2d3i1yew.js +0 -1
- package/.next/static/chunks/0aauz~36q5n2a.css +0 -2
- package/.next/static/chunks/0txa4xkx0-7v-.js +0 -1
- package/.next/static/chunks/0xvj-x25qdu55.js +0 -4
- /package/.next/static/{s_gujwHXFfXWh2_yQC6Gk → KuSssxhFiQYKbTq_1BilM}/_buildManifest.js +0 -0
- /package/.next/static/{s_gujwHXFfXWh2_yQC6Gk → KuSssxhFiQYKbTq_1BilM}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{s_gujwHXFfXWh2_yQC6Gk → KuSssxhFiQYKbTq_1BilM}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* Round 355 verification: filter pin pills (status / group / vendor)
|
|
2
|
+
* gain pure-CSS group-hover lift of their inner opacity-70 spans
|
|
3
|
+
* (prefix `filter:` + count `· N`) to 100 % on pill hover. Marks the
|
|
4
|
+
* whole pill as scan-it interactive without layout reflow (opacity
|
|
5
|
+
* is paint-only). The R335 prefix-opacity tier + R323 count-opacity
|
|
6
|
+
* tier are preserved at rest; both ease to 100 % on pill hover.
|
|
7
|
+
*
|
|
8
|
+
* Contract (cyber, status filter pinned to 'working'):
|
|
9
|
+
* - Rest: filter-prefix + filter-pill-count computed opacity 0.7.
|
|
10
|
+
* - Hover pill: both spans computed opacity 1.
|
|
11
|
+
* - Inline transition contains 'opacity'.
|
|
12
|
+
* - Pre-R355 invariants:
|
|
13
|
+
* * R335 prefix span keeps `hidden sm:inline opacity-70` baseline
|
|
14
|
+
* * R323 count span keeps `opacity-70 tabular-nums` baseline
|
|
15
|
+
* * Pill click-to-clear unchanged (still has onClick / cursor)
|
|
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 {
|
|
26
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
27
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
28
|
+
// Pre-pin status='working' so the filter pill mounts on first paint.
|
|
29
|
+
localStorage.setItem('anet-pinnedStatus', JSON.stringify('working'));
|
|
30
|
+
} catch {}
|
|
31
|
+
});
|
|
32
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
33
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
34
|
+
const r = await route.fetch();
|
|
35
|
+
const b = await r.json();
|
|
36
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
37
|
+
const mk = (alias) => ({
|
|
38
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
39
|
+
network_id: nid, project_dir: null,
|
|
40
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
41
|
+
});
|
|
42
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b'), mk('c') ] } });
|
|
43
|
+
});
|
|
44
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
45
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
46
|
+
|
|
47
|
+
const page = await ctx.newPage();
|
|
48
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
49
|
+
// If pre-pin via localStorage didn't take, click the working chip to pin.
|
|
50
|
+
await page.waitForSelector('[data-working-chip]', { timeout: 15000 });
|
|
51
|
+
const pillVisible = await page.locator('[data-active-filter="status"]').count();
|
|
52
|
+
if (pillVisible === 0) {
|
|
53
|
+
await page.click('[data-working-chip]');
|
|
54
|
+
await page.waitForSelector('[data-active-filter="status"]', { timeout: 5000 });
|
|
55
|
+
}
|
|
56
|
+
await page.waitForTimeout(300);
|
|
57
|
+
|
|
58
|
+
const restProbe = await page.evaluate(() => {
|
|
59
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
60
|
+
const prefix = pill?.querySelector('[data-filter-prefix]');
|
|
61
|
+
const count = pill?.querySelector('[data-filter-pill-count]');
|
|
62
|
+
return {
|
|
63
|
+
prefixOpacity: prefix ? getComputedStyle(prefix).opacity : null,
|
|
64
|
+
countOpacity: count ? getComputedStyle(count).opacity : null,
|
|
65
|
+
prefixTrans: prefix ? getComputedStyle(prefix).transition : null,
|
|
66
|
+
countTrans: count ? getComputedStyle(count).transition : null,
|
|
67
|
+
prefixCls: prefix?.getAttribute('class') ?? null,
|
|
68
|
+
countCls: count?.getAttribute('class') ?? null,
|
|
69
|
+
pillCursor: pill ? getComputedStyle(pill).cursor : null,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Hover the pill.
|
|
74
|
+
await page.hover('[data-active-filter="status"]');
|
|
75
|
+
await page.waitForTimeout(300);
|
|
76
|
+
const hoverProbe = await page.evaluate(() => {
|
|
77
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
78
|
+
const prefix = pill?.querySelector('[data-filter-prefix]');
|
|
79
|
+
const count = pill?.querySelector('[data-filter-pill-count]');
|
|
80
|
+
return {
|
|
81
|
+
prefixOpacity: prefix ? getComputedStyle(prefix).opacity : null,
|
|
82
|
+
countOpacity: count ? getComputedStyle(count).opacity : null,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await browser.close();
|
|
87
|
+
|
|
88
|
+
const approxEq = (a, b, tol = 0.02) => {
|
|
89
|
+
const x = parseFloat(a); const y = parseFloat(b);
|
|
90
|
+
return Number.isFinite(x) && Number.isFinite(y) && Math.abs(x - y) <= tol;
|
|
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_prefix_0_7: approxEq(restProbe.prefixOpacity, 0.7),
|
|
97
|
+
rest_count_0_7: approxEq(restProbe.countOpacity, 0.7),
|
|
98
|
+
prefix_trans_has_op: hasTrans(restProbe.prefixTrans, 'opacity'),
|
|
99
|
+
count_trans_has_op: hasTrans(restProbe.countTrans, 'opacity'),
|
|
100
|
+
prefix_cls_has_op_70: /opacity-70/.test(restProbe.prefixCls || ''), // R335
|
|
101
|
+
count_cls_has_tabular: /tabular-nums/.test(restProbe.countCls || ''), // R323
|
|
102
|
+
pill_cursor_pointer: restProbe.pillCursor === 'pointer',
|
|
103
|
+
hover_prefix_1: approxEq(hoverProbe.prefixOpacity, 1),
|
|
104
|
+
hover_count_1: approxEq(hoverProbe.countOpacity, 1),
|
|
105
|
+
};
|
|
106
|
+
const ok = Object.values(results).every(Boolean);
|
|
107
|
+
console.log(`${ok ? '✅' : '❌'} filter pill hover-opacity lift:`, JSON.stringify(results),
|
|
108
|
+
'\n rest: ', restProbe,
|
|
109
|
+
'\n hover:', hoverProbe);
|
|
110
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* Round 356 verification: filter pin pill × buttons gain hover:scale-
|
|
2
|
+
* 110 (Tailwind 4 modern CSS `scale` property). Pre-R356 the × had
|
|
3
|
+
* only hover:opacity-70 — target dimmed under cursor but didn't
|
|
4
|
+
* lift. R356 adds a 10 % scale so the click-target reads as "press
|
|
5
|
+
* me" alongside the dim. Sibling polish to R354 vendor letter glyph
|
|
6
|
+
* + R350/R352/R353 chrome icon hover-scales. replace_all touched
|
|
7
|
+
* all 4 filter pin pill × buttons (status / group / vendor / edge).
|
|
8
|
+
*
|
|
9
|
+
* Tailwind 4 compiles scale-110 to the modern CSS `scale` property
|
|
10
|
+
* — probes getComputedStyle(el).scale (verified during R352).
|
|
11
|
+
*
|
|
12
|
+
* Contract (cyber, status filter pinned to 'working'):
|
|
13
|
+
* - Rest: × button computed scale 'none' (no scale applied).
|
|
14
|
+
* - Hover ×: scale === '1.1'.
|
|
15
|
+
* - Inline transition (computed) contains scale/transform.
|
|
16
|
+
* - Pre-R356 invariants: hover:opacity-70 still applies (button
|
|
17
|
+
* dims on hover), cursor: pointer, data-active-filter attr on
|
|
18
|
+
* parent pill still intact.
|
|
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 {
|
|
29
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
30
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
31
|
+
localStorage.setItem('anet-pinnedStatus', JSON.stringify('working'));
|
|
32
|
+
} catch {}
|
|
33
|
+
});
|
|
34
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
35
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
36
|
+
const r = await route.fetch();
|
|
37
|
+
const b = await r.json();
|
|
38
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
39
|
+
const mk = (alias) => ({
|
|
40
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
41
|
+
network_id: nid, project_dir: null,
|
|
42
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
43
|
+
});
|
|
44
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b'), mk('c') ] } });
|
|
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
|
+
const pillVisible = await page.locator('[data-active-filter="status"]').count();
|
|
53
|
+
if (pillVisible === 0) {
|
|
54
|
+
await page.click('[data-working-chip]');
|
|
55
|
+
await page.waitForSelector('[data-active-filter="status"]', { timeout: 5000 });
|
|
56
|
+
}
|
|
57
|
+
await page.waitForTimeout(300);
|
|
58
|
+
|
|
59
|
+
const restProbe = await page.evaluate(() => {
|
|
60
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
61
|
+
const x = pill?.querySelector('button[aria-label^="Clear "]');
|
|
62
|
+
const cs = x ? getComputedStyle(x) : null;
|
|
63
|
+
return {
|
|
64
|
+
scale: cs?.scale ?? null,
|
|
65
|
+
transition: cs?.transition ?? null,
|
|
66
|
+
cursor: cs?.cursor ?? null,
|
|
67
|
+
className: x?.getAttribute('class') ?? null,
|
|
68
|
+
pillAttr: pill?.getAttribute('data-active-filter') ?? null,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await page.hover('[data-active-filter="status"] button[aria-label^="Clear "]');
|
|
73
|
+
await page.waitForTimeout(300);
|
|
74
|
+
const hoverProbe = await page.evaluate(() => {
|
|
75
|
+
const x = document.querySelector('[data-active-filter="status"] button[aria-label^="Clear "]');
|
|
76
|
+
return { scale: x ? getComputedStyle(x).scale : null };
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await browser.close();
|
|
80
|
+
|
|
81
|
+
const isRestScale = (s) => s === 'none' || s === '1' || s === '1 1' || s === null;
|
|
82
|
+
const isHoverScale110 = (s) => s === '1.1' || s === '1.1 1.1';
|
|
83
|
+
const hasTrans = (s, prop) =>
|
|
84
|
+
new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
|
|
85
|
+
|
|
86
|
+
const results = {
|
|
87
|
+
rest_scale_1: isRestScale(restProbe.scale),
|
|
88
|
+
trans_has_scale: hasTrans(restProbe.transition, 'scale') || hasTrans(restProbe.transition, 'transform'),
|
|
89
|
+
cursor_pointer: restProbe.cursor === 'pointer',
|
|
90
|
+
cls_has_hover_op_70: /hover:opacity-70/.test(restProbe.className || ''), // R343
|
|
91
|
+
cls_has_hover_scale: /hover:scale-110/.test(restProbe.className || ''),
|
|
92
|
+
pill_attr_status: restProbe.pillAttr === 'status',
|
|
93
|
+
hover_scale_110: isHoverScale110(hoverProbe.scale),
|
|
94
|
+
};
|
|
95
|
+
const ok = Object.values(results).every(Boolean);
|
|
96
|
+
console.log(`${ok ? '✅' : '❌'} filter pill × hover-scale:`, JSON.stringify(results),
|
|
97
|
+
'\n rest: ', restProbe,
|
|
98
|
+
'\n hover: ', hoverProbe);
|
|
99
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* Round 378 verification: edge flow-path dashed-rail picks up
|
|
2
|
+
* strokeLinecap='round'. Pre-R378 '2 12' dashes painted as sharp
|
|
3
|
+
* 1×2 rectangles; R378 rounds each cap so the dashes read as soft
|
|
4
|
+
* 3-px pills.
|
|
5
|
+
*
|
|
6
|
+
* Contract:
|
|
7
|
+
* - [data-edge-flow-rail] element stroke-linecap attr === 'round'.
|
|
8
|
+
* - data-edge-flow-rail-linecap === 'round'.
|
|
9
|
+
* - Pre-R378 invariants:
|
|
10
|
+
* * strokeWidth='1' preserved
|
|
11
|
+
* * strokeDasharray='2 12' preserved
|
|
12
|
+
* * R57 / R245 / R47 transition list (opacity + stroke) preserved
|
|
13
|
+
* * data-edge-flow-rail key still surfaces the link
|
|
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('a'), mk('b') ] } });
|
|
36
|
+
});
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
|
|
39
|
+
{ id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
|
|
40
|
+
network_id: 'default', created_at: new Date(now - 5000).toISOString() },
|
|
41
|
+
] } }));
|
|
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-edge-flow-rail]', { timeout: 15000 });
|
|
47
|
+
await page.waitForTimeout(300);
|
|
48
|
+
|
|
49
|
+
const probe = await page.evaluate(() => {
|
|
50
|
+
const rail = document.querySelector('[data-edge-flow-rail]');
|
|
51
|
+
const cs = rail ? getComputedStyle(rail) : null;
|
|
52
|
+
return {
|
|
53
|
+
linecapAttr: rail?.getAttribute('stroke-linecap') ?? null,
|
|
54
|
+
linecapData: rail?.getAttribute('data-edge-flow-rail-linecap') ?? null,
|
|
55
|
+
strokeWidth: rail?.getAttribute('stroke-width') ?? null,
|
|
56
|
+
strokeDasharray: rail?.getAttribute('stroke-dasharray') ?? null,
|
|
57
|
+
transition: cs?.transition ?? null,
|
|
58
|
+
linkKey: rail?.getAttribute('data-edge-flow-rail') ?? null,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await browser.close();
|
|
63
|
+
|
|
64
|
+
const hasTrans = (s, prop) =>
|
|
65
|
+
new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
|
|
66
|
+
|
|
67
|
+
const results = {
|
|
68
|
+
linecap_round: probe.linecapAttr === 'round',
|
|
69
|
+
data_linecap_round: probe.linecapData === 'round',
|
|
70
|
+
stroke_width_1: probe.strokeWidth === '1',
|
|
71
|
+
dasharray_2_12: probe.strokeDasharray === '2 12',
|
|
72
|
+
trans_has_opacity: hasTrans(probe.transition, 'opacity'),
|
|
73
|
+
trans_has_stroke: hasTrans(probe.transition, 'stroke'),
|
|
74
|
+
link_key_present: (probe.linkKey || '').length > 0,
|
|
75
|
+
};
|
|
76
|
+
const ok = Object.values(results).every(Boolean);
|
|
77
|
+
console.log(`${ok ? '✅' : '❌'} flow-rail strokeLinecap='round':`, JSON.stringify(results),
|
|
78
|
+
'\n probe:', probe);
|
|
79
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* Round 377 verification: FreshnessChip baseClass picks up `tabular-
|
|
2
|
+
* nums`. The chip-row's last untouched chip joins the R224-R357
|
|
3
|
+
* tabular-nums sweep. `font-mono` already gave equal-width glyphs;
|
|
4
|
+
* R377 adds the explicit invariant the rest of the chip row carries.
|
|
5
|
+
*
|
|
6
|
+
* The chip only renders when stale (R275 gate: sec > 10s since
|
|
7
|
+
* last SWR sync). Test verifies the source-file invariant directly
|
|
8
|
+
* (className contains 'tabular-nums') since simulating > 10 s of
|
|
9
|
+
* no SWR refresh in a Playwright test is brittle.
|
|
10
|
+
*
|
|
11
|
+
* Contract:
|
|
12
|
+
* - Source file TopoGraph.tsx contains the new baseClass with
|
|
13
|
+
* 'tabular-nums' between 'font-medium' and 'border'.
|
|
14
|
+
* - Source file mentions R377 round marker.
|
|
15
|
+
* - All preserved invariants: font-mono, font-medium (R313/R315),
|
|
16
|
+
* transition-colors duration-300 (R187), rounded-md, border.
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
19
|
+
|
|
20
|
+
const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
21
|
+
|
|
22
|
+
const baseClassLineMatch = src.match(/const baseClass = "([^"]+)"/);
|
|
23
|
+
const baseClass = baseClassLineMatch?.[1] ?? '';
|
|
24
|
+
|
|
25
|
+
const results = {
|
|
26
|
+
source_has_baseclass: !!baseClassLineMatch,
|
|
27
|
+
baseclass_has_tabular_nums: /\btabular-nums\b/.test(baseClass),
|
|
28
|
+
baseclass_has_font_mono: /\bfont-mono\b/.test(baseClass),
|
|
29
|
+
baseclass_has_font_medium: /\bfont-medium\b/.test(baseClass),
|
|
30
|
+
baseclass_has_rounded_md: /\brounded-md\b/.test(baseClass),
|
|
31
|
+
baseclass_has_border: /\bborder\b/.test(baseClass),
|
|
32
|
+
baseclass_has_transition: /\btransition-colors\b/.test(baseClass),
|
|
33
|
+
baseclass_duration_300: /\bduration-300\b/.test(baseClass),
|
|
34
|
+
// Ensure ordering: font-medium → tabular-nums → border.
|
|
35
|
+
ordering_correct: /font-medium tabular-nums border/.test(baseClass),
|
|
36
|
+
round_marker_present: /Round 377 \/ Loop/.test(src),
|
|
37
|
+
};
|
|
38
|
+
const ok = Object.values(results).every(Boolean);
|
|
39
|
+
console.log(`${ok ? '✅' : '❌'} FreshnessChip baseClass tabular-nums:`, JSON.stringify(results),
|
|
40
|
+
'\n baseClass:', baseClass);
|
|
41
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* Round 358 verification: freshness-ramp stale floor lifted 0.25 →
|
|
2
|
+
* 0.30 across 3 coordinated scopes:
|
|
3
|
+
* - active-links chip dot (line ~3055)
|
|
4
|
+
* - recent-panel header count (line ~6524)
|
|
5
|
+
* - recent-row freshness pip (line ~7021)
|
|
6
|
+
*
|
|
7
|
+
* The ramp shape stays the same:
|
|
8
|
+
* 0-30s → 1.0 (fully fresh)
|
|
9
|
+
* 30-300s → smooth decay 1.0 → 0.30
|
|
10
|
+
* > 300s → 0.30 (stale floor)
|
|
11
|
+
*
|
|
12
|
+
* R358 polish: stale state becomes ~20 % more readable (0.30 / 0.25
|
|
13
|
+
* = 1.2x) while the fresh-vs-stale ratio stays strong (1.0 / 0.30 ≈
|
|
14
|
+
* 3.3× vs prior 4×). Joins the R317 subordinate-text-lift family
|
|
15
|
+
* at the freshness scope.
|
|
16
|
+
*
|
|
17
|
+
* Test approach: feed a STALE message (~ 600s ago, well past the 300s
|
|
18
|
+
* floor boundary) to the route mock, then read all 3 freshness-alpha
|
|
19
|
+
* data attrs. Each should be '0.30'.
|
|
20
|
+
*
|
|
21
|
+
* Contract:
|
|
22
|
+
* - Active-links chip dot data-active-links-freshness-alpha === '0.30'.
|
|
23
|
+
* - Recent-panel header count data-recent-panel-count-freshness-alpha
|
|
24
|
+
* === '0.30'.
|
|
25
|
+
* - Recent-row pip data-recent-row-freshness-alpha === '0.30'.
|
|
26
|
+
* - SVG fill colors include alpha 0.3 (rgba form).
|
|
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
|
+
const mk = (alias) => ({
|
|
44
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
45
|
+
network_id: nid, project_dir: null,
|
|
46
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
47
|
+
});
|
|
48
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b') ] } });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// STALE message: created_at ~ 600s ago + last_at ~ 600s ago.
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
const staleIso = new Date(now - 600 * 1000).toISOString();
|
|
54
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
|
|
55
|
+
{ id: 'm1', from_alias: 'a', to_alias: 'b', content: 'old ping',
|
|
56
|
+
network_id: 'default', created_at: staleIso, last_at: staleIso },
|
|
57
|
+
] } }));
|
|
58
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
59
|
+
|
|
60
|
+
const page = await ctx.newPage();
|
|
61
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
62
|
+
await page.waitForSelector('[data-active-links-freshness-dot]', { timeout: 15000 });
|
|
63
|
+
await page.waitForSelector('[data-recent-panel-count-freshness-alpha]', { timeout: 5000 });
|
|
64
|
+
await page.waitForSelector('[data-recent-row-freshness-alpha]', { timeout: 5000 });
|
|
65
|
+
await page.waitForTimeout(400);
|
|
66
|
+
|
|
67
|
+
const probe = await page.evaluate(() => {
|
|
68
|
+
const dot = document.querySelector('[data-active-links-freshness-dot]');
|
|
69
|
+
const count = document.querySelector('[data-recent-panel-count-freshness-alpha]');
|
|
70
|
+
const pip = document.querySelector('[data-recent-row-freshness-alpha]');
|
|
71
|
+
return {
|
|
72
|
+
dotAlpha: dot?.getAttribute('data-active-links-freshness-alpha') ?? null,
|
|
73
|
+
countAlpha: count?.getAttribute('data-recent-panel-count-freshness-alpha') ?? null,
|
|
74
|
+
pipAlpha: pip?.getAttribute('data-recent-row-freshness-alpha') ?? null,
|
|
75
|
+
// The pip uses a static fill + a separate `opacity` SVG attr —
|
|
76
|
+
// probe that, not the fill colour.
|
|
77
|
+
pipOpacity: pip?.getAttribute('opacity') ?? null,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await browser.close();
|
|
82
|
+
|
|
83
|
+
const results = {
|
|
84
|
+
dot_alpha_0_30: probe.dotAlpha === '0.30',
|
|
85
|
+
count_alpha_0_30: probe.countAlpha === '0.30',
|
|
86
|
+
pip_alpha_0_30: probe.pipAlpha === '0.30',
|
|
87
|
+
pip_opacity_0_3: /^0\.3$|^0\.30$/.test(probe.pipOpacity || ''),
|
|
88
|
+
};
|
|
89
|
+
const ok = Object.values(results).every(Boolean);
|
|
90
|
+
console.log(`${ok ? '✅' : '❌'} freshness floor lift 0.25 → 0.30:`, JSON.stringify(results),
|
|
91
|
+
'\n probe:', probe);
|
|
92
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* Round 357 verification: active-links chip freshness suffix wrapper
|
|
2
|
+
* picks up `tabular-nums` for digit width-lock. Pre-R357 the literal
|
|
3
|
+
* "last {rel}" text (e.g. "last 5s ago", "last 10s ago") had
|
|
4
|
+
* natural-figure digits — the ticker updates every second, so the
|
|
5
|
+
* 9→10 boundary on seconds and 59s→1m flip both jittered ~1-2 px of
|
|
6
|
+
* glyph width, propagating through the inline-flex chip row and
|
|
7
|
+
* nudging the freshness DOT + chip's left edge. Tabular-nums on the
|
|
8
|
+
* wrapper applies to all descendant digits (letters render at
|
|
9
|
+
* natural widths). Joins the R224-R232 info-density tabular-nums
|
|
10
|
+
* sweep at the chip-row freshness scope.
|
|
11
|
+
*
|
|
12
|
+
* Contract (cyber, fresh messages so the suffix renders):
|
|
13
|
+
* - Freshness wrapper element computed font-variant-numeric
|
|
14
|
+
* resolves to 'tabular-nums'.
|
|
15
|
+
* - data-active-links-freshness-wrapper attr present on the span.
|
|
16
|
+
* - The wrapper still carries text-gray-400 (R342) — invariant.
|
|
17
|
+
* - The freshness dot still has its color transition (R342) —
|
|
18
|
+
* invariant.
|
|
19
|
+
* - The active-links chip still mounts with the suffix child.
|
|
20
|
+
*/
|
|
21
|
+
import { chromium } from 'playwright';
|
|
22
|
+
import { readFileSync } from 'node:fs';
|
|
23
|
+
|
|
24
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
25
|
+
const browser = await chromium.launch({ headless: true });
|
|
26
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
27
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
28
|
+
await ctx.addInitScript(() => {
|
|
29
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
30
|
+
});
|
|
31
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
32
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
33
|
+
const r = await route.fetch();
|
|
34
|
+
const b = await r.json();
|
|
35
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
36
|
+
const mk = (alias) => ({
|
|
37
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
38
|
+
network_id: nid, project_dir: null,
|
|
39
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
40
|
+
});
|
|
41
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b'), mk('c') ] } });
|
|
42
|
+
});
|
|
43
|
+
// A fresh message so the active-links chip surfaces a suffix.
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
|
|
46
|
+
{ id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
|
|
47
|
+
network_id: 'default', created_at: new Date(now - 5000).toISOString() },
|
|
48
|
+
] } }));
|
|
49
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
50
|
+
|
|
51
|
+
const page = await ctx.newPage();
|
|
52
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
53
|
+
await page.waitForSelector('[data-active-links-freshness-wrapper]', { timeout: 15000 });
|
|
54
|
+
await page.waitForTimeout(300);
|
|
55
|
+
|
|
56
|
+
const probe = await page.evaluate(() => {
|
|
57
|
+
const wrap = document.querySelector('[data-active-links-freshness-wrapper]');
|
|
58
|
+
const dot = wrap?.querySelector('[data-active-links-freshness-dot]');
|
|
59
|
+
const cs = wrap ? getComputedStyle(wrap) : null;
|
|
60
|
+
const dotCs = dot ? getComputedStyle(dot) : null;
|
|
61
|
+
return {
|
|
62
|
+
wrapperFontVariant: cs?.fontVariantNumeric ?? null,
|
|
63
|
+
wrapperColor: cs?.color ?? null,
|
|
64
|
+
wrapperCls: wrap?.getAttribute('class') ?? null,
|
|
65
|
+
wrapperText: wrap?.textContent ?? null,
|
|
66
|
+
dotPresent: !!dot,
|
|
67
|
+
dotTransition: dotCs?.transition ?? null,
|
|
68
|
+
dotColor: dotCs?.color ?? null,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await browser.close();
|
|
73
|
+
|
|
74
|
+
const hasTrans = (s, prop) =>
|
|
75
|
+
new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
|
|
76
|
+
|
|
77
|
+
const results = {
|
|
78
|
+
tabular_nums: /tabular-nums/.test(probe.wrapperFontVariant || ''),
|
|
79
|
+
cls_has_text_gray: /text-gray-400/.test(probe.wrapperCls || ''), // R342
|
|
80
|
+
cls_has_tabular: /tabular-nums/.test(probe.wrapperCls || ''),
|
|
81
|
+
text_has_last: /last/.test(probe.wrapperText || ''),
|
|
82
|
+
dot_present: probe.dotPresent,
|
|
83
|
+
dot_has_color_trans: hasTrans(probe.dotTransition, 'color'), // R342
|
|
84
|
+
};
|
|
85
|
+
const ok = Object.values(results).every(Boolean);
|
|
86
|
+
console.log(`${ok ? '✅' : '❌'} freshness suffix tabular-nums:`, JSON.stringify(results),
|
|
87
|
+
'\n probe:', probe);
|
|
88
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* Round 353 verification: fullscreen icon (enter + exit variants)
|
|
2
|
+
* picks up group-hover:scale-110 — sibling to R352 zoom icons + R350
|
|
3
|
+
* reset hover-rotate. Closes the chrome-strip per-icon hover-
|
|
4
|
+
* affordance arc:
|
|
5
|
+
* zoom-out R352 scale 1 → 1.1
|
|
6
|
+
* zoom-in R352 scale 1 → 1.1
|
|
7
|
+
* reset R350 rotate 0 → -8°
|
|
8
|
+
* fullscreen R353 scale 1 → 1.1 (this round, both enter+exit svgs)
|
|
9
|
+
*
|
|
10
|
+
* Tailwind 4 compiles scale-110 to the modern CSS `scale` property
|
|
11
|
+
* (verified during R352 — getComputedStyle(el).scale carries the
|
|
12
|
+
* value, not transform).
|
|
13
|
+
*
|
|
14
|
+
* Contract (cyber, non-fullscreen):
|
|
15
|
+
* - Rest: enter-icon computed scale 'none' (no scale applied).
|
|
16
|
+
* - Hover button: enter-icon scale='1.1'.
|
|
17
|
+
* - Inline transition contains scale (or transform — Tailwind 4
|
|
18
|
+
* emits both transition-property entries on transition-transform).
|
|
19
|
+
* - Pre-R353 invariants: R288 strokeWidth=2.5 preserved, anet-
|
|
20
|
+
* chrome-pop class absent at rest, data-topo-chrome-fullscreen-
|
|
21
|
+
* icon="enter" attr intact.
|
|
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-fullscreen-icon="enter"]', { timeout: 15000 });
|
|
51
|
+
await page.waitForTimeout(300);
|
|
52
|
+
|
|
53
|
+
const restProbe = await page.evaluate(() => {
|
|
54
|
+
const ico = document.querySelector('[data-topo-chrome-fullscreen-icon="enter"]');
|
|
55
|
+
const cs = ico ? getComputedStyle(ico) : null;
|
|
56
|
+
return {
|
|
57
|
+
scale: cs?.scale ?? null,
|
|
58
|
+
transition: cs?.transition ?? null,
|
|
59
|
+
strokeW: ico?.getAttribute('stroke-width') ?? null,
|
|
60
|
+
className: ico?.getAttribute('class') ?? null,
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Hover the fullscreen button.
|
|
65
|
+
await page.hover('[data-topo-chrome-fullscreen]');
|
|
66
|
+
await page.waitForTimeout(300);
|
|
67
|
+
const hoverProbe = await page.evaluate(() => {
|
|
68
|
+
const ico = document.querySelector('[data-topo-chrome-fullscreen-icon="enter"]');
|
|
69
|
+
return { scale: ico ? getComputedStyle(ico).scale : null };
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await browser.close();
|
|
73
|
+
|
|
74
|
+
const isRestScale = (s) => s === 'none' || s === '1' || s === '1 1' || s === null;
|
|
75
|
+
const isHoverScale110 = (s) => s === '1.1' || s === '1.1 1.1';
|
|
76
|
+
const hasTrans = (s, prop) =>
|
|
77
|
+
new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
|
|
78
|
+
|
|
79
|
+
const results = {
|
|
80
|
+
rest_scale_1: isRestScale(restProbe.scale),
|
|
81
|
+
trans_has_scale: hasTrans(restProbe.transition, 'scale') || hasTrans(restProbe.transition, 'transform'),
|
|
82
|
+
strokew_2_5: restProbe.strokeW === '2.5', // R288
|
|
83
|
+
no_chrome_pop_rest: !/anet-chrome-pop/.test(restProbe.className || ''),
|
|
84
|
+
has_group_hover_cls: /group-hover:scale-110/.test(restProbe.className || ''),
|
|
85
|
+
hover_scale_110: isHoverScale110(hoverProbe.scale),
|
|
86
|
+
};
|
|
87
|
+
const ok = Object.values(results).every(Boolean);
|
|
88
|
+
console.log(`${ok ? '✅' : '❌'} fullscreen icon hover-scale:`, JSON.stringify(results),
|
|
89
|
+
'\n rest: ', restProbe,
|
|
90
|
+
'\n hover:', hoverProbe);
|
|
91
|
+
process.exit(ok ? 0 : 1);
|