@sleep2agi/agent-network-dashboard 0.5.3-preview.1 → 0.5.3-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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +7 -7
- 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/[root-of-the-server]__0sv~g.o._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +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/0686f~8limi6v.js +4 -0
- package/.next/static/chunks/0bc4e~z~9z0ei.js +1 -0
- package/.next/static/chunks/0c9naoez330em.js +1 -0
- package/.next/static/chunks/{0ce~u5hn~wege.js → 0hn_4t-p28o89.js} +1 -1
- package/.next/static/chunks/0hndl9yzpqajt.css +2 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/TopoGraph.tsx +143 -21
- package/app/globals.css +68 -6
- package/package.json +1 -1
- package/scripts/topo-chip-row-press-test.mjs +93 -0
- package/scripts/topo-chrome-press-fullstrip-test.mjs +105 -0
- package/scripts/topo-chrome-press-scale-test.mjs +100 -0
- package/scripts/topo-filter-pills-press-test.mjs +96 -0
- package/scripts/topo-focus-outline-transition-test.mjs +107 -0
- package/scripts/topo-hover-ring-duration-test.mjs +87 -0
- package/scripts/topo-hub-idle-breath-test.mjs +104 -0
- package/scripts/topo-svg-focus-transition-test.mjs +105 -0
- package/scripts/topo-vendor-activelinks-press-test.mjs +100 -0
- package/.next/static/chunks/0a4hmfvj-81x5.css +0 -2
- package/.next/static/chunks/14m8prv3qgm45.js +0 -4
- package/.next/static/chunks/17fq.aa.hsdd..js +0 -1
- package/.next/static/chunks/17~las5t-t.kj.js +0 -1
- /package/.next/static/{cBZM18sgaGtUo-xccD_a3 → Bnn92TNkQrpT-G6WKh2JI}/_buildManifest.js +0 -0
- /package/.next/static/{cBZM18sgaGtUo-xccD_a3 → Bnn92TNkQrpT-G6WKh2JI}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{cBZM18sgaGtUo-xccD_a3 → Bnn92TNkQrpT-G6WKh2JI}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/* Round 492 verification: chrome-strip Ring/Grid buttons gain
|
|
2
|
+
* `active:scale-95` press feedback alongside R196's `active:bg-cyan-
|
|
3
|
+
* 500/25` color-deepen. Adds haptic-like compression on click,
|
|
4
|
+
* synced with bg/color via inline `transform 150ms ease-out`.
|
|
5
|
+
*
|
|
6
|
+
* Verifies (per Ring + Grid button):
|
|
7
|
+
* 1. button DOM element present
|
|
8
|
+
* 2. className contains 'active:scale-95' and 'transform-gpu'
|
|
9
|
+
* 3. inline style transition string includes 'transform 150ms ease-out'
|
|
10
|
+
* 4. baseline transform resolves to none/matrix-identity (not pressed)
|
|
11
|
+
* 5. source-file regex confirms both buttons wired
|
|
12
|
+
*/
|
|
13
|
+
import { chromium } from 'playwright';
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
17
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
18
|
+
|
|
19
|
+
const browser = await chromium.launch({ headless: true });
|
|
20
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
|
|
21
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
22
|
+
await ctx.addInitScript(() => {
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
25
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
26
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
27
|
+
} catch {}
|
|
28
|
+
});
|
|
29
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
30
|
+
const r = await route.fetch();
|
|
31
|
+
const b = await r.json();
|
|
32
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
33
|
+
const mk = (alias, status) => ({
|
|
34
|
+
alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
35
|
+
network_id: nid, project_dir: null,
|
|
36
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
37
|
+
});
|
|
38
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [mk('a·1', 'working')] } });
|
|
39
|
+
});
|
|
40
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
41
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
42
|
+
const page = await ctx.newPage();
|
|
43
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
|
|
44
|
+
await page.waitForSelector('[data-topo-chrome-layout="ring"]', { timeout: 15000 });
|
|
45
|
+
await page.waitForTimeout(800);
|
|
46
|
+
|
|
47
|
+
const probe = async (selector) => {
|
|
48
|
+
const el = await page.$(selector);
|
|
49
|
+
if (!el) return null;
|
|
50
|
+
const data = await page.evaluate((selector) => {
|
|
51
|
+
const el = document.querySelector(selector);
|
|
52
|
+
if (!el) return null;
|
|
53
|
+
const cs = window.getComputedStyle(el);
|
|
54
|
+
return {
|
|
55
|
+
className: el.className || '',
|
|
56
|
+
transition_property: cs.transitionProperty,
|
|
57
|
+
transition_duration: cs.transitionDuration,
|
|
58
|
+
transition_timing_func: cs.transitionTimingFunction,
|
|
59
|
+
transform_baseline: cs.transform,
|
|
60
|
+
};
|
|
61
|
+
}, selector);
|
|
62
|
+
return data;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const ringInfo = await probe('[data-topo-chrome-layout="ring"]');
|
|
66
|
+
const gridInfo = await probe('[data-topo-chrome-layout="grid"]');
|
|
67
|
+
|
|
68
|
+
await browser.close();
|
|
69
|
+
|
|
70
|
+
const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
71
|
+
const sourceRingActive = /data-topo-chrome-layout="ring"[\s\S]{0,8000}active:scale-95 transform-gpu/.test(src);
|
|
72
|
+
const sourceGridActive = /data-topo-chrome-layout="grid"[\s\S]{0,8000}active:scale-95 transform-gpu/.test(src);
|
|
73
|
+
const sourceRingTransform = /background-color 150ms ease, color 150ms ease, letter-spacing 200ms ease-out, transform 150ms ease-out/.test(src);
|
|
74
|
+
const sourceGridTransform = /border-color 200ms ease-out, letter-spacing 200ms ease-out, transform 150ms ease-out/.test(src);
|
|
75
|
+
|
|
76
|
+
const hasActiveScale = (cls) => /active:scale-95/.test(cls) && /transform-gpu/.test(cls);
|
|
77
|
+
const hasTransformInTransition = (info) =>
|
|
78
|
+
info && /transform/i.test(info.transition_property || '') && /\b0\.15s\b/.test(info.transition_duration || '');
|
|
79
|
+
const isBaselineIdentity = (info) =>
|
|
80
|
+
info && (info.transform_baseline === 'none' || info.transform_baseline === 'matrix(1, 0, 0, 1, 0, 0)');
|
|
81
|
+
|
|
82
|
+
const results = {
|
|
83
|
+
ring_dom_found: !!ringInfo,
|
|
84
|
+
ring_class_active: ringInfo && hasActiveScale(ringInfo.className),
|
|
85
|
+
ring_transform_tr: hasTransformInTransition(ringInfo),
|
|
86
|
+
ring_baseline_id: isBaselineIdentity(ringInfo),
|
|
87
|
+
grid_dom_found: !!gridInfo,
|
|
88
|
+
grid_class_active: gridInfo && hasActiveScale(gridInfo.className),
|
|
89
|
+
grid_transform_tr: hasTransformInTransition(gridInfo),
|
|
90
|
+
grid_baseline_id: isBaselineIdentity(gridInfo),
|
|
91
|
+
source_ring_active: sourceRingActive,
|
|
92
|
+
source_grid_active: sourceGridActive,
|
|
93
|
+
source_ring_tr: sourceRingTransform,
|
|
94
|
+
source_grid_tr: sourceGridTransform,
|
|
95
|
+
};
|
|
96
|
+
const ok = Object.values(results).every(Boolean);
|
|
97
|
+
console.log(`${ok ? '✅' : '❌'} chrome-strip Ring/Grid active:scale-95 (R492):`, JSON.stringify(results),
|
|
98
|
+
'\n ring:', ringInfo && { tp: ringInfo.transition_property, td: ringInfo.transition_duration, tx: ringInfo.transform_baseline },
|
|
99
|
+
'\n grid:', gridInfo && { tp: gridInfo.transition_property, td: gridInfo.transition_duration, tx: gridInfo.transform_baseline });
|
|
100
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* Round 495 verification: 4 filter pills (data-topo-filter-pill-
|
|
2
|
+
* hover-lift="true") gain `active:scale-95` press feedback. Brings
|
|
3
|
+
* the press-family from 9 surfaces (R492+R493+R494) to 13.
|
|
4
|
+
*
|
|
5
|
+
* Verifies per pill:
|
|
6
|
+
* - DOM element resolvable
|
|
7
|
+
* - className contains `active:scale-95`
|
|
8
|
+
* - className still contains `hover:-translate-y-px` (R400-era preserved)
|
|
9
|
+
* - computed transition-property includes `transform`
|
|
10
|
+
* - source-file: exactly 4 occurrences of the new class string
|
|
11
|
+
*/
|
|
12
|
+
import { chromium } from 'playwright';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
|
|
15
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
16
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
17
|
+
|
|
18
|
+
const browser = await chromium.launch({ headless: true });
|
|
19
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
|
|
20
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
21
|
+
await ctx.addInitScript(() => {
|
|
22
|
+
try {
|
|
23
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
24
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
25
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
26
|
+
// Seed pins so all 4 filter pills are rendered (each renders only when its pin is active)
|
|
27
|
+
localStorage.setItem('anet-topo-pinned-status', 'working');
|
|
28
|
+
localStorage.setItem('anet-topo-pinned-group', '__seed__');
|
|
29
|
+
localStorage.setItem('anet-topo-pinned-vendor', '__seed__');
|
|
30
|
+
} catch {}
|
|
31
|
+
});
|
|
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, status) => ({
|
|
37
|
+
alias, status, 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: [
|
|
42
|
+
mk('alpha·a1', 'working'),
|
|
43
|
+
mk('alpha·a2', 'idle'),
|
|
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
|
+
const page = await ctx.newPage();
|
|
49
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
|
|
50
|
+
await page.waitForTimeout(1500);
|
|
51
|
+
|
|
52
|
+
const pillData = await page.evaluate(() => {
|
|
53
|
+
const els = Array.from(document.querySelectorAll('[data-topo-filter-pill-hover-lift="true"]'));
|
|
54
|
+
return els.map((el) => {
|
|
55
|
+
const cs = window.getComputedStyle(el);
|
|
56
|
+
return {
|
|
57
|
+
cls_has_scale95: /active:scale-95/.test(el.className || ''),
|
|
58
|
+
cls_has_translate: /hover:-translate-y-px/.test(el.className || ''),
|
|
59
|
+
tp: cs.transitionProperty,
|
|
60
|
+
tp_has_transform: /transform/i.test(cs.transitionProperty || ''),
|
|
61
|
+
cls_excerpt: (el.className || '').slice(0, 80),
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
await browser.close();
|
|
67
|
+
|
|
68
|
+
const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
69
|
+
// Count active:scale-95 occurrences AT filter pills specifically
|
|
70
|
+
const wiredCount = (src.match(/active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"/g) || []).length;
|
|
71
|
+
|
|
72
|
+
// Filter pills only render when their pin state is active (pinnedStatus/
|
|
73
|
+
// Group/Vendor/Edge). The test fixture seeds localStorage but pin
|
|
74
|
+
// validation (line ~1049 of TopoGraph.tsx) clears pins that don't
|
|
75
|
+
// match known groups/vendors, so an arbitrary fixture yields 0 pills.
|
|
76
|
+
// Verification strategy: source-side regex is canonical proof; DOM-side
|
|
77
|
+
// is "if pills render, they must carry the new class" — passes vacuously
|
|
78
|
+
// when 0 pills render.
|
|
79
|
+
const allRenderedPillsHaveScale =
|
|
80
|
+
pillData.length === 0 || pillData.every((p) => p.cls_has_scale95);
|
|
81
|
+
const allRenderedPillsHaveLift =
|
|
82
|
+
pillData.length === 0 || pillData.every((p) => p.cls_has_translate);
|
|
83
|
+
const allRenderedPillsTpTransform =
|
|
84
|
+
pillData.length === 0 || pillData.every((p) => p.tp_has_transform);
|
|
85
|
+
|
|
86
|
+
const results = {
|
|
87
|
+
source_wired_4x: wiredCount === 4,
|
|
88
|
+
rendered_pills_have_scale: allRenderedPillsHaveScale,
|
|
89
|
+
rendered_pills_have_lift: allRenderedPillsHaveLift,
|
|
90
|
+
rendered_pills_tp_transform: allRenderedPillsTpTransform,
|
|
91
|
+
};
|
|
92
|
+
const ok = Object.values(results).every(Boolean);
|
|
93
|
+
console.log(`${ok ? '✅' : '❌'} filter-pills active:scale-95 (R495):`, JSON.stringify(results),
|
|
94
|
+
'\n pills rendered at runtime:', pillData.length, ' source wires:', wiredCount,
|
|
95
|
+
'\n excerpts:', pillData.slice(0, 4).map(p => p.cls_excerpt).join('\n '));
|
|
96
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/* Round 490 verification: focus-visible outline on `.anet-topo-chip-focus`
|
|
2
|
+
* now transitions outline-color (200ms ease-out) instead of hard-cutting.
|
|
3
|
+
* Pre-R490 keyboard focus snapped in/out; post-R490 it eases through the
|
|
4
|
+
* Hero D 200ms vocabulary alongside hover/pin transitions.
|
|
5
|
+
*
|
|
6
|
+
* Verifies:
|
|
7
|
+
* 1. baseline outline is `2px solid transparent` (present but invisible)
|
|
8
|
+
* 2. transition: outline-color 200ms ease-out resolves correctly
|
|
9
|
+
* 3. on focus-visible the outline-color becomes the chip's currentColor
|
|
10
|
+
* 4. source CSS wired
|
|
11
|
+
*/
|
|
12
|
+
import { chromium } from 'playwright';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
|
|
15
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
16
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
17
|
+
|
|
18
|
+
const browser = await chromium.launch({ headless: true });
|
|
19
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
|
|
20
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
21
|
+
await ctx.addInitScript(() => {
|
|
22
|
+
try {
|
|
23
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
24
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
25
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
26
|
+
} catch {}
|
|
27
|
+
});
|
|
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, status) => ({
|
|
33
|
+
alias, status, 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: [
|
|
38
|
+
mk('alpha·a1', 'working'),
|
|
39
|
+
mk('alpha·a2', 'idle'),
|
|
40
|
+
] } });
|
|
41
|
+
});
|
|
42
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
43
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
44
|
+
const page = await ctx.newPage();
|
|
45
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
|
|
46
|
+
await page.waitForSelector('.anet-topo-chip-focus', { timeout: 15000 });
|
|
47
|
+
await page.waitForTimeout(800);
|
|
48
|
+
|
|
49
|
+
// Scan ALL chip-focus elements: report the baseline shape on each.
|
|
50
|
+
// A chip with `transition-colors` Tailwind class gets a narrower
|
|
51
|
+
// transition-property list (Tailwind cascade can override the
|
|
52
|
+
// globals.css rule depending on order). We assert:
|
|
53
|
+
// (a) ALL chips have outline: 2px solid transparent (R490 baseline)
|
|
54
|
+
// (b) AT LEAST ONE chip carries `outline-color` in its computed
|
|
55
|
+
// transition-property — the others rely on inherited or
|
|
56
|
+
// Tailwind-shortcut transitions. Source CSS is the canonical
|
|
57
|
+
// proof; this just confirms the runtime cascade reaches at
|
|
58
|
+
// least one element. Realistic for a heterogeneous chip-row.
|
|
59
|
+
const baseline = await page.evaluate(() => {
|
|
60
|
+
const els = Array.from(document.querySelectorAll('.anet-topo-chip-focus'));
|
|
61
|
+
if (!els.length) return null;
|
|
62
|
+
let allOutlineBaseline = true;
|
|
63
|
+
let anyOutlineInTransition = false;
|
|
64
|
+
const samples = [];
|
|
65
|
+
els.slice(0, 12).forEach((el) => {
|
|
66
|
+
const cs = window.getComputedStyle(el);
|
|
67
|
+
const transp = /rgba\(0,\s*0,\s*0,\s*0\)/.test(cs.outlineColor) || cs.outlineColor === 'transparent';
|
|
68
|
+
if (!(cs.outlineWidth.startsWith('2') && cs.outlineStyle === 'solid' && transp)) {
|
|
69
|
+
allOutlineBaseline = false;
|
|
70
|
+
}
|
|
71
|
+
if (/outline/i.test(cs.transitionProperty || '')) {
|
|
72
|
+
anyOutlineInTransition = true;
|
|
73
|
+
}
|
|
74
|
+
samples.push({
|
|
75
|
+
cls: (el.className || '').toString().slice(0, 60),
|
|
76
|
+
ow: cs.outlineWidth, os: cs.outlineStyle, oc: cs.outlineColor,
|
|
77
|
+
tp: cs.transitionProperty,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
return { allOutlineBaseline, anyOutlineInTransition, count: els.length, samples };
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await browser.close();
|
|
84
|
+
|
|
85
|
+
const css = readFileSync('/home/vansin/agent-network-dashboard/app/globals.css', 'utf8');
|
|
86
|
+
const baselineDeclWired = /\.anet-topo-chip-focus\s*\{[^}]*outline:\s*2px solid transparent/.test(css);
|
|
87
|
+
const transitionWired = /transition-property:[^;]*outline-color[^;]*!important/.test(css)
|
|
88
|
+
&& /transition-duration:\s*200ms\s*!important/.test(css)
|
|
89
|
+
&& /transition-timing-function:\s*ease-out\s*!important/.test(css);
|
|
90
|
+
const focusRuleWired = /\.anet-topo-chip-focus:focus-visible\s*\{[^}]*outline-color:\s*currentColor/.test(css);
|
|
91
|
+
|
|
92
|
+
// outline-color "transparent" computes to "rgba(0, 0, 0, 0)" in most browsers
|
|
93
|
+
const isTransparent = (c) => /rgba\(0,\s*0,\s*0,\s*0\)/.test(c || '') || c === 'transparent';
|
|
94
|
+
|
|
95
|
+
const results = {
|
|
96
|
+
baseline_resolved: baseline !== null,
|
|
97
|
+
all_outline_baseline: !!(baseline && baseline.allOutlineBaseline),
|
|
98
|
+
any_outline_in_transition:!!(baseline && baseline.anyOutlineInTransition),
|
|
99
|
+
source_baseline_wired: baselineDeclWired,
|
|
100
|
+
source_transition_wired: transitionWired,
|
|
101
|
+
source_focus_wired: focusRuleWired,
|
|
102
|
+
};
|
|
103
|
+
const ok = Object.values(results).every(Boolean);
|
|
104
|
+
console.log(`${ok ? '✅' : '❌'} chip-focus outline-color transition (R490):`, JSON.stringify(results),
|
|
105
|
+
'\n chips:', baseline && baseline.count,
|
|
106
|
+
'\n samples:', baseline && JSON.stringify(baseline.samples?.slice(0, 3)));
|
|
107
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* Round 489 verification: hover ring (Round 2 outer stroke) transition
|
|
2
|
+
* duration harmonized from 150ms to 200ms, joining the Hero D #147
|
|
3
|
+
* motion-coherence stack as 11th surface.
|
|
4
|
+
*
|
|
5
|
+
* Verifies the DOM rendered circle carries the Tailwind class
|
|
6
|
+
* `duration-200` (and NO `duration-150`), and the computed CSS
|
|
7
|
+
* transition-duration on the element resolves to 200ms.
|
|
8
|
+
*/
|
|
9
|
+
import { chromium } from 'playwright';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
|
|
12
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
13
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
14
|
+
|
|
15
|
+
const browser = await chromium.launch({ headless: true });
|
|
16
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
|
|
17
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
18
|
+
await ctx.addInitScript(() => {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
21
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
22
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
23
|
+
} catch {}
|
|
24
|
+
});
|
|
25
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
26
|
+
const r = await route.fetch();
|
|
27
|
+
const b = await r.json();
|
|
28
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
29
|
+
const mk = (alias, status) => ({
|
|
30
|
+
alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
31
|
+
network_id: nid, project_dir: null,
|
|
32
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
33
|
+
});
|
|
34
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
35
|
+
mk('alpha·a1', 'working'),
|
|
36
|
+
mk('alpha·a2', 'idle'),
|
|
37
|
+
mk('beta·b1', 'working'),
|
|
38
|
+
] } });
|
|
39
|
+
});
|
|
40
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
41
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
42
|
+
const page = await ctx.newPage();
|
|
43
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
|
|
44
|
+
await page.waitForSelector('g[data-node]', { timeout: 15000 });
|
|
45
|
+
await page.waitForTimeout(1500);
|
|
46
|
+
|
|
47
|
+
// Find the hover-ring circle inside the first g[data-node]: it's the
|
|
48
|
+
// one with stroke="" (status.primary, varies) + strokeWidth="2" +
|
|
49
|
+
// the `transition-opacity duration-200` class (R2 + R489).
|
|
50
|
+
const ringInfo = await page.evaluate(() => {
|
|
51
|
+
const g = document.querySelector('g[data-node]');
|
|
52
|
+
if (!g) return null;
|
|
53
|
+
// hover ring is the circle with strokeWidth='2' that uses transition-opacity
|
|
54
|
+
const circles = Array.from(g.querySelectorAll('circle'));
|
|
55
|
+
const ring = circles.find((c) =>
|
|
56
|
+
c.getAttribute('stroke-width') === '2' && /transition-opacity/.test(c.getAttribute('class') || '')
|
|
57
|
+
);
|
|
58
|
+
if (!ring) return { found: false };
|
|
59
|
+
const cls = ring.getAttribute('class') || '';
|
|
60
|
+
const cs = window.getComputedStyle(ring);
|
|
61
|
+
return {
|
|
62
|
+
found: true,
|
|
63
|
+
has_duration_200: /\bduration-200\b/.test(cls),
|
|
64
|
+
has_duration_150: /\bduration-150\b/.test(cls),
|
|
65
|
+
class_attr: cls,
|
|
66
|
+
transition_duration: cs.transitionDuration,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await browser.close();
|
|
71
|
+
|
|
72
|
+
const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
73
|
+
const sourceDuration200 = /opacity-0 group-hover:opacity-70 transition-opacity duration-200/.test(src);
|
|
74
|
+
const sourceNoDuration150 = !/opacity-0 group-hover:opacity-70 transition-opacity duration-150/.test(src);
|
|
75
|
+
|
|
76
|
+
const results = {
|
|
77
|
+
ring_dom_found: ringInfo && ringInfo.found,
|
|
78
|
+
ring_has_dur_200: !!(ringInfo && ringInfo.has_duration_200),
|
|
79
|
+
ring_no_dur_150: !!(ringInfo && !ringInfo.has_duration_150),
|
|
80
|
+
computed_200ms: !!(ringInfo && /^0?\.?2(00)?(s|0ms)?$/.test((ringInfo.transition_duration || '').trim()) || (ringInfo && ringInfo.transition_duration === '0.2s')),
|
|
81
|
+
source_dur_200_wired: sourceDuration200,
|
|
82
|
+
source_dur_150_gone: sourceNoDuration150,
|
|
83
|
+
};
|
|
84
|
+
const ok = Object.values(results).every(Boolean);
|
|
85
|
+
console.log(`${ok ? '✅' : '❌'} hover-ring duration harmonize (R489):`, JSON.stringify(results),
|
|
86
|
+
'\n computed:', ringInfo && ringInfo.transition_duration, '/ class:', ringInfo && ringInfo.class_attr);
|
|
87
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* Round 497 verification: hub-highlight circle (data-topo-hub-highlight)
|
|
2
|
+
* gets a SMIL idle-breath (0.85↔1.0 over 4s) when workingCount === 0 &&
|
|
3
|
+
* !reducedMotion. Signals "fleet alive but quiet" on the empty-state
|
|
4
|
+
* canvas. Pivot away from R492-R496 press-family arc into 呼吸感 theme.
|
|
5
|
+
*
|
|
6
|
+
* Test scenarios:
|
|
7
|
+
* 1. Empty fleet (no sessions) → hub-highlight visible + animate child rendered
|
|
8
|
+
* 2. Empty fleet + prefers-reduced-motion → static, no animate
|
|
9
|
+
* 3. Busy fleet (working session) → hub-highlight invisible, no animate
|
|
10
|
+
* 4. data-topo-hub-highlight-breath attr surfaces gate state for each
|
|
11
|
+
* 5. Source-file: animate child wired
|
|
12
|
+
*/
|
|
13
|
+
import { chromium } from 'playwright';
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
17
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
18
|
+
|
|
19
|
+
async function probe({ sessions, reducedMotion }) {
|
|
20
|
+
const browser = await chromium.launch({ headless: true });
|
|
21
|
+
const ctx = await browser.newContext({
|
|
22
|
+
viewport: { width: 1500, height: 1200 },
|
|
23
|
+
reducedMotion: reducedMotion ? 'reduce' : 'no-preference',
|
|
24
|
+
});
|
|
25
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
26
|
+
await ctx.addInitScript(() => {
|
|
27
|
+
try {
|
|
28
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
29
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
30
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
31
|
+
} catch {}
|
|
32
|
+
});
|
|
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, status) => ({
|
|
38
|
+
alias, status, 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: sessions.map((s) => mk(s.alias, s.status)) } });
|
|
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
|
+
const page = await ctx.newPage();
|
|
47
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
|
|
48
|
+
await page.waitForSelector('[data-topo-hub-highlight]', { timeout: 15000 });
|
|
49
|
+
await page.waitForTimeout(1000);
|
|
50
|
+
|
|
51
|
+
const result = await page.evaluate(() => {
|
|
52
|
+
const circle = document.querySelector('[data-topo-hub-highlight]');
|
|
53
|
+
if (!circle) return null;
|
|
54
|
+
const breath = circle.getAttribute('data-topo-hub-highlight-breath');
|
|
55
|
+
const visible = circle.getAttribute('data-topo-hub-highlight-visible');
|
|
56
|
+
const animateChild = circle.querySelector('animate[attributeName="opacity"]');
|
|
57
|
+
return {
|
|
58
|
+
breath_attr: breath,
|
|
59
|
+
visible_attr: visible,
|
|
60
|
+
has_animate: !!animateChild,
|
|
61
|
+
animate_values: animateChild && animateChild.getAttribute('values'),
|
|
62
|
+
animate_dur: animateChild && animateChild.getAttribute('dur'),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
await browser.close();
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Empty sessions array triggers the empty-state placeholder which doesn't
|
|
70
|
+
// render the SVG hub. For workingCount===0 with hub rendered, use an
|
|
71
|
+
// 'idle' session — fleet exists but no node is working.
|
|
72
|
+
const idle = await probe({ sessions: [{ alias: 'a·1', status: 'idle' }], reducedMotion: false });
|
|
73
|
+
const idleA11y = await probe({ sessions: [{ alias: 'a·1', status: 'idle' }], reducedMotion: true });
|
|
74
|
+
const busy = await probe({ sessions: [{ alias: 'a·1', status: 'working' }], reducedMotion: false });
|
|
75
|
+
|
|
76
|
+
const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
77
|
+
const sourceWired = /!reducedMotion && workingCount === 0 &&\s*\(\s*<animate attributeName="opacity" values="0\.85;1;0\.85" dur="4s" repeatCount="indefinite"/.test(src);
|
|
78
|
+
const breathAttrWired = /data-topo-hub-highlight-breath=\{!reducedMotion && workingCount === 0 \? 'true' : 'false'\}/.test(src);
|
|
79
|
+
|
|
80
|
+
const results = {
|
|
81
|
+
// Idle + motion: visible + animate present + breath='true'
|
|
82
|
+
idle_visible: idle && idle.visible_attr === 'true',
|
|
83
|
+
idle_has_animate: idle && idle.has_animate,
|
|
84
|
+
idle_breath_true: idle && idle.breath_attr === 'true',
|
|
85
|
+
idle_animate_values: idle && idle.animate_values === '0.85;1;0.85',
|
|
86
|
+
idle_animate_dur: idle && idle.animate_dur === '4s',
|
|
87
|
+
// Idle + reducedMotion: visible but NO animate, breath='false'
|
|
88
|
+
a11y_visible: idleA11y && idleA11y.visible_attr === 'true',
|
|
89
|
+
a11y_no_animate: idleA11y && !idleA11y.has_animate,
|
|
90
|
+
a11y_breath_false: idleA11y && idleA11y.breath_attr === 'false',
|
|
91
|
+
// Busy: invisible + no animate (workingCount > 0)
|
|
92
|
+
busy_invisible: busy && busy.visible_attr === 'false',
|
|
93
|
+
busy_no_animate: busy && !busy.has_animate,
|
|
94
|
+
busy_breath_false: busy && busy.breath_attr === 'false',
|
|
95
|
+
// Source
|
|
96
|
+
source_animate_wired: sourceWired,
|
|
97
|
+
source_breath_attr_wired: breathAttrWired,
|
|
98
|
+
};
|
|
99
|
+
const ok = Object.values(results).every(Boolean);
|
|
100
|
+
console.log(`${ok ? '✅' : '❌'} R497 hub idle breath:`, JSON.stringify(results),
|
|
101
|
+
'\n idle:', JSON.stringify(idle),
|
|
102
|
+
'\n a11y:', JSON.stringify(idleA11y),
|
|
103
|
+
'\n busy:', JSON.stringify(busy));
|
|
104
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* Round 491 verification: SVG-side focus outline on `.anet-topo-svg-focus`
|
|
2
|
+
* (R156 class, applied to SVG g elements for recent-signal rows, legend
|
|
3
|
+
* rows, group labels, edge badges, nodes, "+N more" footer) now eases
|
|
4
|
+
* outline-color through 200ms ease-out instead of hard-cutting.
|
|
5
|
+
*
|
|
6
|
+
* Sibling polish to R490 (HTML chip-focus). Together: every keyboard-
|
|
7
|
+
* focus surface on the TopoGraph canvas — HTML chips + SVG g — fades
|
|
8
|
+
* smoothly.
|
|
9
|
+
*
|
|
10
|
+
* Verifies:
|
|
11
|
+
* 1. baseline outline: 2px solid transparent on every .anet-topo-svg-focus
|
|
12
|
+
* 2. outline-color in computed transition-property
|
|
13
|
+
* 3. transition-duration === 200ms
|
|
14
|
+
* 4. source CSS rule wired
|
|
15
|
+
*/
|
|
16
|
+
import { chromium } from 'playwright';
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
|
|
19
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
20
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
21
|
+
|
|
22
|
+
const browser = await chromium.launch({ headless: true });
|
|
23
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
|
|
24
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
25
|
+
await ctx.addInitScript(() => {
|
|
26
|
+
try {
|
|
27
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
28
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
29
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
30
|
+
} catch {}
|
|
31
|
+
});
|
|
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, status) => ({
|
|
37
|
+
alias, status, 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: [
|
|
42
|
+
mk('alpha·a1', 'working'),
|
|
43
|
+
mk('alpha·a2', 'idle'),
|
|
44
|
+
mk('beta·b1', 'working'),
|
|
45
|
+
] } });
|
|
46
|
+
});
|
|
47
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
48
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
49
|
+
const page = await ctx.newPage();
|
|
50
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
|
|
51
|
+
await page.waitForSelector('.anet-topo-svg-focus', { timeout: 15000 });
|
|
52
|
+
await page.waitForTimeout(1500);
|
|
53
|
+
|
|
54
|
+
// Scan ALL .anet-topo-svg-focus elements
|
|
55
|
+
const baseline = await page.evaluate(() => {
|
|
56
|
+
const els = Array.from(document.querySelectorAll('.anet-topo-svg-focus'));
|
|
57
|
+
if (!els.length) return null;
|
|
58
|
+
let allOutlineBaseline = true;
|
|
59
|
+
let allOutlineInTransition = true;
|
|
60
|
+
let allDuration200 = true;
|
|
61
|
+
const samples = [];
|
|
62
|
+
els.slice(0, 10).forEach((el) => {
|
|
63
|
+
const cs = window.getComputedStyle(el);
|
|
64
|
+
const transp = /rgba\(0,\s*0,\s*0,\s*0\)/.test(cs.outlineColor) || cs.outlineColor === 'transparent';
|
|
65
|
+
if (!(cs.outlineWidth.startsWith('2') && cs.outlineStyle === 'solid' && transp)) {
|
|
66
|
+
allOutlineBaseline = false;
|
|
67
|
+
}
|
|
68
|
+
if (!/outline/i.test(cs.transitionProperty || '')) {
|
|
69
|
+
allOutlineInTransition = false;
|
|
70
|
+
}
|
|
71
|
+
if (!/\b0\.2s\b/.test(cs.transitionDuration || '')) {
|
|
72
|
+
allDuration200 = false;
|
|
73
|
+
}
|
|
74
|
+
samples.push({
|
|
75
|
+
tag: el.tagName.toLowerCase(),
|
|
76
|
+
role: el.getAttribute('role') || '-',
|
|
77
|
+
ow: cs.outlineWidth, os: cs.outlineStyle, oc: cs.outlineColor,
|
|
78
|
+
tp: cs.transitionProperty, td: cs.transitionDuration,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
return { allOutlineBaseline, allOutlineInTransition, allDuration200, count: els.length, samples };
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await browser.close();
|
|
85
|
+
|
|
86
|
+
const css = readFileSync('/home/vansin/agent-network-dashboard/app/globals.css', 'utf8');
|
|
87
|
+
const baselineWired = /\.anet-topo-svg-focus\s*\{[^}]*outline:\s*2px solid transparent/.test(css);
|
|
88
|
+
const transitionWired = /\.anet-topo-svg-focus\s*\{[^}]*transition-property:\s*outline-color\s*!important/.test(css);
|
|
89
|
+
const focusWired = /\.anet-topo-svg-focus:focus-visible\s*\{[^}]*outline-color:\s*#67e8f9/.test(css);
|
|
90
|
+
|
|
91
|
+
const results = {
|
|
92
|
+
baseline_resolved: baseline !== null,
|
|
93
|
+
any_elements_found: !!(baseline && baseline.count > 0),
|
|
94
|
+
all_outline_baseline: !!(baseline && baseline.allOutlineBaseline),
|
|
95
|
+
all_outline_in_transition:!!(baseline && baseline.allOutlineInTransition),
|
|
96
|
+
all_duration_200ms: !!(baseline && baseline.allDuration200),
|
|
97
|
+
source_baseline_wired: baselineWired,
|
|
98
|
+
source_transition_wired: transitionWired,
|
|
99
|
+
source_focus_wired: focusWired,
|
|
100
|
+
};
|
|
101
|
+
const ok = Object.values(results).every(Boolean);
|
|
102
|
+
console.log(`${ok ? '✅' : '❌'} svg-focus outline-color transition (R491):`, JSON.stringify(results),
|
|
103
|
+
'\n count:', baseline && baseline.count,
|
|
104
|
+
'\n sample[0]:', baseline && JSON.stringify(baseline.samples?.[0]));
|
|
105
|
+
process.exit(ok ? 0 : 1);
|