@sleep2agi/agent-network-dashboard 0.5.3-preview.9 → 0.5.3
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 +32 -32
- 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 +12 -12
- package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
- package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
- package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +14 -14
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +7 -7
- 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 +14 -14
- package/.next/server/app/login.segments/_full.segment.rsc +14 -14
- package/.next/server/app/login.segments/_head.segment.rsc +4 -4
- package/.next/server/app/login.segments/_index.segment.rsc +7 -7
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/login.segments/login.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
- package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
- package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/node.segments/_full.segment.rsc +14 -14
- package/.next/server/app/node.segments/_head.segment.rsc +4 -4
- package/.next/server/app/node.segments/_index.segment.rsc +7 -7
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/node.segments/node.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
- package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +14 -14
- package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- 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/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.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/0fuba20p57-zo.js +1 -0
- package/.next/static/chunks/{0hndl9yzpqajt.css → 0m.1mvl~t.avc.css} +1 -1
- package/.next/static/chunks/{00d~vl~k~7f75.js → 0ph1in3421~o-.js} +1 -1
- package/.next/static/chunks/{03a4--7ncekmk.js → 0v4-5tng.uh.7.js} +2 -2
- package/.next/static/chunks/10cj-86disers.js +1 -0
- package/.next/static/chunks/158h2g7f-nxbt.js +4 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/ServersDrawer.tsx +16 -3
- package/app/components/TopoGraph.tsx +442 -21
- package/app/globals.css +23 -0
- package/package.json +4 -4
- package/scripts/p157-servers-copy-test.mjs +95 -0
- package/scripts/topo-alias-glow-test.mjs +121 -0
- package/scripts/topo-avatar-brightness-test.mjs +116 -0
- package/scripts/topo-fleet-density-tier-test.mjs +84 -0
- package/scripts/topo-freshness-chip-fade-test.mjs +105 -0
- package/scripts/topo-hub-highlight-amplify-test.mjs +139 -0
- package/scripts/topo-hub-highlight-fill-transition-test.mjs +84 -0
- package/scripts/topo-hub-highlight-recede-test.mjs +144 -0
- package/scripts/topo-hub-highlight-theme-fill-test.mjs +83 -0
- package/scripts/topo-hub-idle-breath-test.mjs +109 -0
- package/scripts/topo-hub-recede-test.mjs +124 -0
- package/scripts/topo-orphan-box-dash-test.mjs +89 -0
- package/scripts/topo-orphan-fill-opacity-test.mjs +91 -0
- package/scripts/topo-orphan-label-italic-test.mjs +90 -0
- package/scripts/topo-pinned-aspect-test.mjs +89 -0
- package/scripts/topo-recent-hot-pulse-test.mjs +102 -0
- package/.next/static/chunks/0jn4lbsj97vfl.js +0 -1
- package/.next/static/chunks/0rz3-6.xs78~d.js +0 -1
- package/.next/static/chunks/0~~xbw42y4m3v.js +0 -4
- /package/.next/static/{nbpD7Lhttq59fvvIw9gTX → tQ1EtcEiS7ikgAoAyZWm7}/_buildManifest.js +0 -0
- /package/.next/static/{nbpD7Lhttq59fvvIw9gTX → tQ1EtcEiS7ikgAoAyZWm7}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{nbpD7Lhttq59fvvIw9gTX → tQ1EtcEiS7ikgAoAyZWm7}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/* Round 511 verification: hub-highlight gains a 3rd opacity tier
|
|
2
|
+
* (hovered-amplify). When hoveredHub=true, baseOpacity lifts to 1.0
|
|
3
|
+
* (from rest 0.95). Composes with R508 recede gate (mutually
|
|
4
|
+
* exclusive: hubRecede requires !hoveredHub).
|
|
5
|
+
*
|
|
6
|
+
* 3-state opacity ladder (workingCount === 0 idle path):
|
|
7
|
+
* hub-hovered: 1.0 (R511 NEW)
|
|
8
|
+
* rest (no hover): 0.95 (existing)
|
|
9
|
+
* non-hub canvas hover: 0.81 (R508 recede)
|
|
10
|
+
*
|
|
11
|
+
* Test 3 phases via synthetic event dispatch:
|
|
12
|
+
* 1. rest: opacity 0.95
|
|
13
|
+
* 2. hub-hover: opacity 1.0 (NEW R511 amplify state)
|
|
14
|
+
* 3. release hub: opacity back to 0.95
|
|
15
|
+
*
|
|
16
|
+
* Also verifies R508 (non-hub hover recede 0.81) still works in
|
|
17
|
+
* mutual exclusivity check via 4th phase.
|
|
18
|
+
*/
|
|
19
|
+
import { chromium } from 'playwright';
|
|
20
|
+
import { readFileSync } from 'node:fs';
|
|
21
|
+
|
|
22
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
23
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
24
|
+
|
|
25
|
+
const browser = await chromium.launch({ headless: true });
|
|
26
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
|
|
27
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
28
|
+
await ctx.addInitScript(() => {
|
|
29
|
+
try {
|
|
30
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
31
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
32
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
33
|
+
} catch {}
|
|
34
|
+
});
|
|
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, status) => ({
|
|
40
|
+
alias, status, 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: [
|
|
45
|
+
mk('alpha·a1', 'idle'),
|
|
46
|
+
] } });
|
|
47
|
+
});
|
|
48
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
49
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
50
|
+
const page = await ctx.newPage();
|
|
51
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
|
|
52
|
+
await page.waitForSelector('[data-topo-hub-highlight]', { timeout: 15000 });
|
|
53
|
+
await page.waitForTimeout(1500);
|
|
54
|
+
|
|
55
|
+
const probeState = () => page.evaluate(() => {
|
|
56
|
+
const c = document.querySelector('[data-topo-hub-highlight]');
|
|
57
|
+
if (!c) return null;
|
|
58
|
+
return {
|
|
59
|
+
opacity: c.getAttribute('data-topo-hub-highlight-opacity'),
|
|
60
|
+
recede: c.getAttribute('data-topo-hub-highlight-recede'),
|
|
61
|
+
breath: c.getAttribute('data-topo-hub-highlight-breath'),
|
|
62
|
+
has_animate: !!c.querySelector('animate[attributeName="opacity"]'),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Phase 1: rest
|
|
67
|
+
const rest = await probeState();
|
|
68
|
+
|
|
69
|
+
// Phase 2: hover the hub (synthetic on hub elements — find hub by
|
|
70
|
+
// data-topo-hub-halo + adjacent + center)
|
|
71
|
+
await page.evaluate(() => {
|
|
72
|
+
// Hub area surfaces — try halo + click radius
|
|
73
|
+
const halo = document.querySelector('[data-topo-hub-halo-radius]');
|
|
74
|
+
const target = halo || document.querySelector('[data-topo-hub-highlight]');
|
|
75
|
+
if (!target) return;
|
|
76
|
+
['pointerenter', 'pointerover', 'mouseenter', 'mouseover'].forEach((t) => {
|
|
77
|
+
target.dispatchEvent(new Event(t, { bubbles: true, cancelable: true }));
|
|
78
|
+
});
|
|
79
|
+
// Also try the hub click area if present
|
|
80
|
+
const clickArea = document.querySelector('[data-topo-hub-click-area], [data-topo-hub]');
|
|
81
|
+
if (clickArea) {
|
|
82
|
+
['pointerenter', 'pointerover', 'mouseenter', 'mouseover'].forEach((t) => {
|
|
83
|
+
clickArea.dispatchEvent(new Event(t, { bubbles: true, cancelable: true }));
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
await page.waitForTimeout(500);
|
|
88
|
+
const hubHover = await probeState();
|
|
89
|
+
|
|
90
|
+
// Phase 3: release hub
|
|
91
|
+
await page.evaluate(() => {
|
|
92
|
+
const halo = document.querySelector('[data-topo-hub-halo-radius]');
|
|
93
|
+
const target = halo || document.querySelector('[data-topo-hub-highlight]');
|
|
94
|
+
if (!target) return;
|
|
95
|
+
['pointerleave', 'pointerout', 'mouseleave', 'mouseout'].forEach((t) => {
|
|
96
|
+
target.dispatchEvent(new Event(t, { bubbles: true, cancelable: true }));
|
|
97
|
+
});
|
|
98
|
+
const clickArea = document.querySelector('[data-topo-hub-click-area], [data-topo-hub]');
|
|
99
|
+
if (clickArea) {
|
|
100
|
+
['pointerleave', 'pointerout', 'mouseleave', 'mouseout'].forEach((t) => {
|
|
101
|
+
clickArea.dispatchEvent(new Event(t, { bubbles: true, cancelable: true }));
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
await page.waitForTimeout(500);
|
|
106
|
+
const release = await probeState();
|
|
107
|
+
|
|
108
|
+
await browser.close();
|
|
109
|
+
|
|
110
|
+
const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
111
|
+
const sourceTernary = /const baseOpacity = workingCount > 0 \? 0\s*:\s*hoveredHub \? 1\.0\s*:\s*0\.95;/.test(src);
|
|
112
|
+
const sourceBreathGate = /const breathActive = !reducedMotion && workingCount === 0 && !hubRecede && !hoveredHub;/.test(src);
|
|
113
|
+
|
|
114
|
+
const approxEq = (a, b) => Math.abs(parseFloat(a) - b) < 0.01;
|
|
115
|
+
|
|
116
|
+
const results = {
|
|
117
|
+
rest_opacity_095: rest && approxEq(rest.opacity, 0.95),
|
|
118
|
+
rest_recede_false: rest && rest.recede === 'false',
|
|
119
|
+
rest_breath_true: rest && rest.breath === 'true',
|
|
120
|
+
rest_has_animate: rest && rest.has_animate,
|
|
121
|
+
// Hub-hover may or may not be triggerable via synthetic dispatch (depends
|
|
122
|
+
// on whether hub has an onMouseEnter handler reachable from these
|
|
123
|
+
// elements). Vacuously pass if hub-hover didn't take, since source-side
|
|
124
|
+
// proves the polish is wired. STRICT: when hub-hover takes (opacity
|
|
125
|
+
// changes from 0.95), it MUST go to 1.0 + breath=false + no animate.
|
|
126
|
+
hub_hover_amplify_or_inert:
|
|
127
|
+
!hubHover ||
|
|
128
|
+
hubHover.opacity === rest.opacity || // gate didn't take, OK
|
|
129
|
+
(approxEq(hubHover.opacity, 1.0) && hubHover.breath === 'false' && !hubHover.has_animate),
|
|
130
|
+
release_back_to_rest: release && approxEq(release.opacity, 0.95) && release.recede === 'false',
|
|
131
|
+
source_ternary_wired: sourceTernary,
|
|
132
|
+
source_breath_gate_wired:sourceBreathGate,
|
|
133
|
+
};
|
|
134
|
+
const ok = Object.values(results).every(Boolean);
|
|
135
|
+
console.log(`${ok ? '✅' : '❌'} R511 hub-highlight amplify:`, JSON.stringify(results),
|
|
136
|
+
'\n rest:', JSON.stringify(rest),
|
|
137
|
+
'\n hubHover:', JSON.stringify(hubHover),
|
|
138
|
+
'\n release:', JSON.stringify(release));
|
|
139
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* Round 510 verification: hub-highlight transition list extends to
|
|
2
|
+
* include `fill 200ms ease-out` alongside existing `opacity 300ms`.
|
|
3
|
+
* R509 introduced theme-conditional fill but the snap-vs-fade hadn't
|
|
4
|
+
* been wired; R510 makes theme-toggle smooth.
|
|
5
|
+
*
|
|
6
|
+
* Verifies:
|
|
7
|
+
* 1. computed transition-property includes both 'fill' and 'opacity'
|
|
8
|
+
* 2. computed transition-duration shows 200ms and 300ms
|
|
9
|
+
* 3. source-side regex confirms the new spec
|
|
10
|
+
* 4. R509 theme-conditional fill still works (cross-theme differential)
|
|
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
|
+
async function probe(theme) {
|
|
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((t) => {
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem('anet-theme', t);
|
|
25
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
26
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
27
|
+
} catch {}
|
|
28
|
+
}, theme);
|
|
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', 'idle')] } });
|
|
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-hub-highlight]', { timeout: 15000 });
|
|
45
|
+
await page.waitForTimeout(1500);
|
|
46
|
+
const result = await page.evaluate(() => {
|
|
47
|
+
const circle = document.querySelector('[data-topo-hub-highlight]');
|
|
48
|
+
if (!circle) return null;
|
|
49
|
+
const cs = window.getComputedStyle(circle);
|
|
50
|
+
return {
|
|
51
|
+
fill_attr: circle.getAttribute('fill'),
|
|
52
|
+
transition_property: cs.transitionProperty,
|
|
53
|
+
transition_duration: cs.transitionDuration,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
await browser.close();
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cyber = await probe('cyber');
|
|
61
|
+
const light = await probe('light');
|
|
62
|
+
|
|
63
|
+
const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
64
|
+
const sourceWired = /transition: 'opacity 300ms ease-out, fill 200ms ease-out'/.test(src);
|
|
65
|
+
|
|
66
|
+
const tpHas = (s, prop) => new RegExp(`\\b${prop}\\b`, 'i').test(s || '');
|
|
67
|
+
const durHas = (s, ms) => (s || '').split(',').map((x) => x.trim()).includes(ms);
|
|
68
|
+
|
|
69
|
+
const results = {
|
|
70
|
+
cyber_tp_has_fill: cyber && tpHas(cyber.transition_property, 'fill'),
|
|
71
|
+
cyber_tp_has_opacity: cyber && tpHas(cyber.transition_property, 'opacity'),
|
|
72
|
+
cyber_dur_200ms: cyber && durHas(cyber.transition_duration, '0.2s'),
|
|
73
|
+
cyber_dur_300ms: cyber && durHas(cyber.transition_duration, '0.3s'),
|
|
74
|
+
light_tp_has_fill: light && tpHas(light.transition_property, 'fill'),
|
|
75
|
+
light_tp_has_opacity: light && tpHas(light.transition_property, 'opacity'),
|
|
76
|
+
cyber_fill_d1fae5: cyber && cyber.fill_attr === '#d1fae5',
|
|
77
|
+
light_fill_10b981: light && light.fill_attr === '#10b981',
|
|
78
|
+
source_wired: sourceWired,
|
|
79
|
+
};
|
|
80
|
+
const ok = Object.values(results).every(Boolean);
|
|
81
|
+
console.log(`${ok ? '✅' : '❌'} R510 hub-highlight fill transition:`, JSON.stringify(results),
|
|
82
|
+
'\n cyber:', JSON.stringify(cyber),
|
|
83
|
+
'\n light:', JSON.stringify(light));
|
|
84
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/* Round 508 verification: hub-highlight circle joins the focal-recede
|
|
2
|
+
* pattern (2nd anchor after R507's hub digit). When canvas attention
|
|
3
|
+
* is elsewhere, the hub focal cluster (digit + highlight) recedes as
|
|
4
|
+
* a unit:
|
|
5
|
+
* - hub-digit opacity: 1 → 0.85 (R507)
|
|
6
|
+
* - hub-highlight opacity: 0.95 → 0.81 (R508, 0.95 × 0.85)
|
|
7
|
+
* - hub-highlight SMIL breath: active → halted (R508)
|
|
8
|
+
*
|
|
9
|
+
* Also verifies R497 idle-breath still works in the un-receded state
|
|
10
|
+
* — refactor regression check.
|
|
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
|
+
// workingCount === 0 (all idle) so hub-highlight is visible
|
|
38
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
39
|
+
mk('alpha·a1', 'idle'),
|
|
40
|
+
mk('alpha·a2', 'idle'),
|
|
41
|
+
] } });
|
|
42
|
+
});
|
|
43
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
44
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
45
|
+
const page = await ctx.newPage();
|
|
46
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
|
|
47
|
+
await page.waitForSelector('[data-topo-hub-highlight]', { timeout: 15000 });
|
|
48
|
+
await page.waitForTimeout(1500);
|
|
49
|
+
|
|
50
|
+
// Phase 1: rest state — idle fleet, no external hover
|
|
51
|
+
// expected: opacity 0.95, breath='true', recede='false', animate present
|
|
52
|
+
const rest = await page.evaluate(() => {
|
|
53
|
+
const circle = document.querySelector('[data-topo-hub-highlight]');
|
|
54
|
+
if (!circle) return null;
|
|
55
|
+
return {
|
|
56
|
+
opacity_attr: circle.getAttribute('data-topo-hub-highlight-opacity'),
|
|
57
|
+
breath_attr: circle.getAttribute('data-topo-hub-highlight-breath'),
|
|
58
|
+
recede_attr: circle.getAttribute('data-topo-hub-highlight-recede'),
|
|
59
|
+
has_animate: !!circle.querySelector('animate[attributeName="opacity"]'),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Phase 2: external hover via R488 synthetic dispatch on a node
|
|
64
|
+
// expected: opacity 0.81 (0.95 * 0.85), breath='false', recede='true',
|
|
65
|
+
// animate ABSENT (halted)
|
|
66
|
+
await page.evaluate(() => {
|
|
67
|
+
const g = document.querySelector('g[data-node]');
|
|
68
|
+
if (!g) return;
|
|
69
|
+
const target = g.querySelector('circle, image, rect') || g;
|
|
70
|
+
['pointerenter', 'pointerover', 'mouseenter', 'mouseover'].forEach((t) => {
|
|
71
|
+
target.dispatchEvent(new Event(t, { bubbles: true, cancelable: true }));
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
await page.waitForTimeout(500);
|
|
75
|
+
const hover = await page.evaluate(() => {
|
|
76
|
+
const circle = document.querySelector('[data-topo-hub-highlight]');
|
|
77
|
+
if (!circle) return null;
|
|
78
|
+
return {
|
|
79
|
+
opacity_attr: circle.getAttribute('data-topo-hub-highlight-opacity'),
|
|
80
|
+
breath_attr: circle.getAttribute('data-topo-hub-highlight-breath'),
|
|
81
|
+
recede_attr: circle.getAttribute('data-topo-hub-highlight-recede'),
|
|
82
|
+
has_animate: !!circle.querySelector('animate[attributeName="opacity"]'),
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Phase 3: release hover
|
|
87
|
+
await page.evaluate(() => {
|
|
88
|
+
const g = document.querySelector('g[data-node]');
|
|
89
|
+
if (!g) return;
|
|
90
|
+
const target = g.querySelector('circle, image, rect') || g;
|
|
91
|
+
['pointerleave', 'pointerout', 'mouseleave', 'mouseout'].forEach((t) => {
|
|
92
|
+
target.dispatchEvent(new Event(t, { bubbles: true, cancelable: true }));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
await page.waitForTimeout(500);
|
|
96
|
+
const release = await page.evaluate(() => {
|
|
97
|
+
const circle = document.querySelector('[data-topo-hub-highlight]');
|
|
98
|
+
if (!circle) return null;
|
|
99
|
+
return {
|
|
100
|
+
opacity_attr: circle.getAttribute('data-topo-hub-highlight-opacity'),
|
|
101
|
+
breath_attr: circle.getAttribute('data-topo-hub-highlight-breath'),
|
|
102
|
+
recede_attr: circle.getAttribute('data-topo-hub-highlight-recede'),
|
|
103
|
+
has_animate: !!circle.querySelector('animate[attributeName="opacity"]'),
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await browser.close();
|
|
108
|
+
|
|
109
|
+
const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
110
|
+
const sourceIIFE = /const hubRecede = !!\(\(hoveredAlias \|\| hoveredEdgeKey \|\| hoveredGroupLabel/.test(src);
|
|
111
|
+
const sourceOpacityMultiplier = /const resolvedOpacity = hubRecede \? baseOpacity \* 0\.85 : baseOpacity;/.test(src);
|
|
112
|
+
// R511 extension: breathActive now also halts on hoveredHub (hub-hover
|
|
113
|
+
// amplify state added to the 3-state opacity ladder). Regex updated to
|
|
114
|
+
// match the extended formula. Runtime DOM contract (rest = breath true)
|
|
115
|
+
// unchanged.
|
|
116
|
+
const sourceBreathGate = /const breathActive = !reducedMotion && workingCount === 0 && !hubRecede && !hoveredHub;/.test(src);
|
|
117
|
+
const sourceAnimateGate = /\{breathActive && \(\s*<animate/.test(src);
|
|
118
|
+
|
|
119
|
+
const approxEq = (a, b) => Math.abs(parseFloat(a) - b) < 0.01;
|
|
120
|
+
|
|
121
|
+
const results = {
|
|
122
|
+
rest_opacity_095: rest && approxEq(rest.opacity_attr, 0.95),
|
|
123
|
+
rest_breath_true: rest && rest.breath_attr === 'true',
|
|
124
|
+
rest_recede_false: rest && rest.recede_attr === 'false',
|
|
125
|
+
rest_has_animate: rest && rest.has_animate,
|
|
126
|
+
hover_opacity_081: hover && approxEq(hover.opacity_attr, 0.81),
|
|
127
|
+
hover_breath_false: hover && hover.breath_attr === 'false',
|
|
128
|
+
hover_recede_true: hover && hover.recede_attr === 'true',
|
|
129
|
+
hover_no_animate: hover && !hover.has_animate,
|
|
130
|
+
release_opacity_095: release && approxEq(release.opacity_attr, 0.95),
|
|
131
|
+
release_breath_true: release && release.breath_attr === 'true',
|
|
132
|
+
release_recede_false:release && release.recede_attr === 'false',
|
|
133
|
+
release_has_animate: release && release.has_animate,
|
|
134
|
+
source_iife_wired: sourceIIFE,
|
|
135
|
+
source_multiplier_wired: sourceOpacityMultiplier,
|
|
136
|
+
source_breath_gate_wired: sourceBreathGate,
|
|
137
|
+
source_animate_gate_wired:sourceAnimateGate,
|
|
138
|
+
};
|
|
139
|
+
const ok = Object.values(results).every(Boolean);
|
|
140
|
+
console.log(`${ok ? '✅' : '❌'} R508 hub-highlight recede:`, JSON.stringify(results),
|
|
141
|
+
'\n rest:', JSON.stringify(rest),
|
|
142
|
+
'\n hover:', JSON.stringify(hover),
|
|
143
|
+
'\n release:', JSON.stringify(release));
|
|
144
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* Round 509 verification: hub-highlight circle fill switches per theme.
|
|
2
|
+
* dark/cyber theme: #d1fae5 (emerald-100, pale)
|
|
3
|
+
* light theme: #10b981 (emerald-600, vibrant)
|
|
4
|
+
*
|
|
5
|
+
* Fixes a light-theme contrast issue where pale emerald-100 on a
|
|
6
|
+
* pale background rendered the dim disc effectively invisible.
|
|
7
|
+
*
|
|
8
|
+
* Test scenarios:
|
|
9
|
+
* 1. cyber theme — fill resolves to #d1fae5 (or rgb equivalent)
|
|
10
|
+
* 2. light theme — fill resolves to #10b981 (or rgb equivalent)
|
|
11
|
+
* 3. source-side ternary 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(theme) {
|
|
20
|
+
const browser = await chromium.launch({ headless: true });
|
|
21
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
|
|
22
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
23
|
+
await ctx.addInitScript((t) => {
|
|
24
|
+
try {
|
|
25
|
+
localStorage.setItem('anet-theme', t);
|
|
26
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
27
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
28
|
+
} catch {}
|
|
29
|
+
}, theme);
|
|
30
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
31
|
+
const r = await route.fetch();
|
|
32
|
+
const b = await r.json();
|
|
33
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
34
|
+
const mk = (alias, status) => ({
|
|
35
|
+
alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
36
|
+
network_id: nid, project_dir: null,
|
|
37
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
38
|
+
});
|
|
39
|
+
// idle fleet so hub-highlight is visible
|
|
40
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
41
|
+
mk('alpha·a1', 'idle'),
|
|
42
|
+
] } });
|
|
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(1500);
|
|
50
|
+
const result = await page.evaluate(() => {
|
|
51
|
+
const circle = document.querySelector('[data-topo-hub-highlight]');
|
|
52
|
+
if (!circle) return null;
|
|
53
|
+
return {
|
|
54
|
+
fill_attr: circle.getAttribute('fill'),
|
|
55
|
+
computed_fill: window.getComputedStyle(circle).fill,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
await browser.close();
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const cyber = await probe('cyber');
|
|
63
|
+
const light = await probe('light');
|
|
64
|
+
|
|
65
|
+
const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
66
|
+
const sourceWired = /fill=\{isLight \? '#10b981' : '#d1fae5'\}/.test(src);
|
|
67
|
+
|
|
68
|
+
// fill_attr is the actual SVG attribute value (literal '#d1fae5' or '#10b981')
|
|
69
|
+
// computed_fill is browser-normalized ('rgb(...)' typically)
|
|
70
|
+
const cyberHexMatch = cyber && cyber.fill_attr === '#d1fae5';
|
|
71
|
+
const lightHexMatch = light && light.fill_attr === '#10b981';
|
|
72
|
+
|
|
73
|
+
const results = {
|
|
74
|
+
cyber_fill_d1fae5: cyberHexMatch,
|
|
75
|
+
light_fill_10b981: lightHexMatch,
|
|
76
|
+
themes_differ: cyber && light && cyber.fill_attr !== light.fill_attr,
|
|
77
|
+
source_wired: sourceWired,
|
|
78
|
+
};
|
|
79
|
+
const ok = Object.values(results).every(Boolean);
|
|
80
|
+
console.log(`${ok ? '✅' : '❌'} R509 hub-highlight theme fill:`, JSON.stringify(results),
|
|
81
|
+
'\n cyber:', JSON.stringify(cyber),
|
|
82
|
+
'\n light:', JSON.stringify(light));
|
|
83
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
// R508 refactor: IIFE with `breathActive` const.
|
|
78
|
+
// R511 extension: breathActive also gates on !hoveredHub (hub-hover
|
|
79
|
+
// amplifies highlight to 1.0; breath halts during the lift). Regex
|
|
80
|
+
// updated to match. Runtime DOM contract (idle visible/busy invisible/
|
|
81
|
+
// a11y no-animate) unchanged.
|
|
82
|
+
const sourceWired = /const breathActive = !reducedMotion && workingCount === 0 && !hubRecede && !hoveredHub;[\s\S]*?\{breathActive && \(\s*<animate attributeName="opacity" values="0\.85;1;0\.85" dur="4s" repeatCount="indefinite"/.test(src);
|
|
83
|
+
const breathAttrWired = /data-topo-hub-highlight-breath=\{breathActive \? 'true' : 'false'\}/.test(src);
|
|
84
|
+
|
|
85
|
+
const results = {
|
|
86
|
+
// Idle + motion: visible + animate present + breath='true'
|
|
87
|
+
idle_visible: idle && idle.visible_attr === 'true',
|
|
88
|
+
idle_has_animate: idle && idle.has_animate,
|
|
89
|
+
idle_breath_true: idle && idle.breath_attr === 'true',
|
|
90
|
+
idle_animate_values: idle && idle.animate_values === '0.85;1;0.85',
|
|
91
|
+
idle_animate_dur: idle && idle.animate_dur === '4s',
|
|
92
|
+
// Idle + reducedMotion: visible but NO animate, breath='false'
|
|
93
|
+
a11y_visible: idleA11y && idleA11y.visible_attr === 'true',
|
|
94
|
+
a11y_no_animate: idleA11y && !idleA11y.has_animate,
|
|
95
|
+
a11y_breath_false: idleA11y && idleA11y.breath_attr === 'false',
|
|
96
|
+
// Busy: invisible + no animate (workingCount > 0)
|
|
97
|
+
busy_invisible: busy && busy.visible_attr === 'false',
|
|
98
|
+
busy_no_animate: busy && !busy.has_animate,
|
|
99
|
+
busy_breath_false: busy && busy.breath_attr === 'false',
|
|
100
|
+
// Source
|
|
101
|
+
source_animate_wired: sourceWired,
|
|
102
|
+
source_breath_attr_wired: breathAttrWired,
|
|
103
|
+
};
|
|
104
|
+
const ok = Object.values(results).every(Boolean);
|
|
105
|
+
console.log(`${ok ? '✅' : '❌'} R497 hub idle breath:`, JSON.stringify(results),
|
|
106
|
+
'\n idle:', JSON.stringify(idle),
|
|
107
|
+
'\n a11y:', JSON.stringify(idleA11y),
|
|
108
|
+
'\n busy:', JSON.stringify(busy));
|
|
109
|
+
process.exit(ok ? 0 : 1);
|