@sleep2agi/agent-network-dashboard 0.4.6-preview.285 → 0.4.6-preview.287
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 +4 -4
- 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.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- 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 +1 -1
- package/.next/server/app/admin.html +1 -1
- package/.next/server/app/admin.rsc +1 -1
- package/.next/server/app/admin.segments/_full.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_index.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_tree.segment.rsc +1 -1
- 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 +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- 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 +2 -2
- package/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/server/app/login.segments/_index.segment.rsc +1 -1
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- 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.html +1 -1
- package/.next/server/app/logs.rsc +1 -1
- package/.next/server/app/logs.segments/_full.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_index.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_tree.segment.rsc +1 -1
- 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.html +1 -1
- package/.next/server/app/messages.rsc +1 -1
- package/.next/server/app/messages.segments/_full.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_index.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_tree.segment.rsc +1 -1
- 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.html +1 -1
- package/.next/server/app/node.rsc +1 -1
- package/.next/server/app/node.segments/_full.segment.rsc +1 -1
- package/.next/server/app/node.segments/_head.segment.rsc +1 -1
- package/.next/server/app/node.segments/_index.segment.rsc +1 -1
- package/.next/server/app/node.segments/_tree.segment.rsc +1 -1
- 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.html +1 -1
- package/.next/server/app/nodes.rsc +1 -1
- package/.next/server/app/nodes.segments/_full.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_index.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_tree.segment.rsc +1 -1
- 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.html +1 -1
- package/.next/server/app/server-logs.rsc +1 -1
- package/.next/server/app/server-logs.segments/_full.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_index.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +1 -1
- 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.html +1 -1
- package/.next/server/app/settings/networks.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +1 -1
- 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.html +1 -1
- package/.next/server/app/settings/tokens.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +1 -1
- 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 +2 -2
- package/.next/server/app/settings.segments/_full.segment.rsc +2 -2
- package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- 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.html +1 -1
- package/.next/server/app/tasks.rsc +1 -1
- package/.next/server/app/tasks.segments/_full.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_index.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_tree.segment.rsc +1 -1
- 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 +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/0ekjn6t9ysdjv.js +4 -0
- package/.next/static/chunks/{06bp-k~o~6spf.js → 0urgynasq-ypz.js} +1 -1
- package/.next/static/chunks/{07z52_2x.wi84.js → 17wqzvdgxok57.js} +1 -1
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/TopoGraph.tsx +37 -5
- package/package.json +1 -1
- package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
- package/scripts/topo-spokes-retired-test.mjs +93 -0
- package/.next/static/chunks/00-nc7g-hqsat.js +0 -4
- package/scripts/topo-arrival-ping-test.mjs +0 -87
- package/scripts/topo-dispatch-pulse-test.mjs +0 -83
- package/scripts/topo-edge-smil-sync-test.mjs +0 -148
- package/scripts/topo-edge-smil-theme-ease-test.mjs +0 -97
- package/scripts/topo-ping-pulse-ease-test.mjs +0 -96
- package/scripts/topo-spoke-busyness-test.mjs +0 -110
- package/scripts/topo-spoke-tint-test.mjs +0 -96
- /package/.next/static/{u9Cwn8Wi8dRb7UmkteBWa → HBbB3A7-O2LxL9zMNYHXa}/_buildManifest.js +0 -0
- /package/.next/static/{u9Cwn8Wi8dRb7UmkteBWa → HBbB3A7-O2LxL9zMNYHXa}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{u9Cwn8Wi8dRb7UmkteBWa → HBbB3A7-O2LxL9zMNYHXa}/_ssgManifest.js +0 -0
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/* Round 231 verification: edge SMIL trio (R103 particle motion +
|
|
2
|
-
* R76 dispatch pulse + R75 arrival ping) now share a single per-edge
|
|
3
|
-
* stagger so all three fire phase-coherently with the particle:
|
|
4
|
-
*
|
|
5
|
-
* particle.animateMotion.begin = -stagger
|
|
6
|
-
* dispatch.animate.begin = -stagger (= particle's)
|
|
7
|
-
* arrival.animate.begin = -(stagger + 0.92*dur) mod dur
|
|
8
|
-
*
|
|
9
|
-
* where stagger = (edgeIndex * 0.37) % duration,
|
|
10
|
-
* duration = max(0.9, 2.6 / sqrt(link.count)).
|
|
11
|
-
*
|
|
12
|
-
* Scenario: 4 working agents + 2 flows (alpha→beta count=5, gamma→
|
|
13
|
-
* delta count=4) — both flows have count ≥ 3 so dispatch pulse
|
|
14
|
-
* fires on both, and fresh > 0.5 so arrival ping fires on both.
|
|
15
|
-
*
|
|
16
|
-
* Per edge:
|
|
17
|
-
* - probe particle's animateMotion.begin
|
|
18
|
-
* - probe dispatch's two <animate>.begin (r + opacity, must match)
|
|
19
|
-
* - probe arrival's two <animate>.begin (r + opacity, must match)
|
|
20
|
-
* - verify all three follow the formula given index + duration
|
|
21
|
-
*/
|
|
22
|
-
import { chromium } from 'playwright';
|
|
23
|
-
import { readFileSync } from 'node:fs';
|
|
24
|
-
|
|
25
|
-
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
26
|
-
const browser = await chromium.launch({ headless: true });
|
|
27
|
-
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
28
|
-
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
29
|
-
await ctx.addInitScript(() => {
|
|
30
|
-
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } 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: [
|
|
43
|
-
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
44
|
-
] } });
|
|
45
|
-
});
|
|
46
|
-
const now = Date.now();
|
|
47
|
-
const msgs = [];
|
|
48
|
-
// flow 0: alpha→beta count=5 — sorted by recent activity, this will be
|
|
49
|
-
// the more-recent flow; edge index depends on flowLinks.sort by last_at desc.
|
|
50
|
-
for (let i = 0; i < 5; i++) {
|
|
51
|
-
msgs.push({
|
|
52
|
-
id: `ab${i}`, from_alias: 'alpha', to_alias: 'beta', content: 'hi',
|
|
53
|
-
network_id: 'default', created_at: new Date(now - (500 + i * 50)).toISOString(),
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
// flow 1: gamma→delta count=4 — older
|
|
57
|
-
for (let i = 0; i < 4; i++) {
|
|
58
|
-
msgs.push({
|
|
59
|
-
id: `gd${i}`, from_alias: 'gamma', to_alias: 'delta', content: 'hi',
|
|
60
|
-
network_id: 'default', created_at: new Date(now - (5000 + i * 50)).toISOString(),
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: msgs } }));
|
|
64
|
-
await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
|
|
65
|
-
|
|
66
|
-
const page = await ctx.newPage();
|
|
67
|
-
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
68
|
-
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
69
|
-
await page.waitForSelector('[data-arrival-ping]', { timeout: 10000, state: 'attached' });
|
|
70
|
-
await page.waitForSelector('[data-dispatch-pulse]', { timeout: 10000, state: 'attached' });
|
|
71
|
-
await page.waitForTimeout(400);
|
|
72
|
-
|
|
73
|
-
const probe = await page.evaluate(() => {
|
|
74
|
-
// Edges keyed by link.key ("from→to"). We grab pairs of (arrival, dispatch)
|
|
75
|
-
// by data attr, plus particle's animateMotion inside the edge <g>.
|
|
76
|
-
const pings = Array.from(document.querySelectorAll('[data-arrival-ping]'));
|
|
77
|
-
const pulses = Array.from(document.querySelectorAll('[data-dispatch-pulse]'));
|
|
78
|
-
// Each ping/pulse lives inside an edge <g> that also contains the animateMotion
|
|
79
|
-
// for the particle. We walk up to find the shared parent.
|
|
80
|
-
return pings.map((ping) => {
|
|
81
|
-
const key = ping.getAttribute('data-arrival-ping');
|
|
82
|
-
const pulse = pulses.find(p => p.getAttribute('data-dispatch-pulse') === key);
|
|
83
|
-
// animateMotion lives in a sibling <circle> within the same parent <g>
|
|
84
|
-
const edgeG = ping.closest('g[data-edge-visible-parent]') || ping.parentElement;
|
|
85
|
-
const motion = edgeG?.querySelector('animateMotion');
|
|
86
|
-
const pingAnims = Array.from(ping.querySelectorAll('animate'));
|
|
87
|
-
const pulseAnims = pulse ? Array.from(pulse.querySelectorAll('animate')) : [];
|
|
88
|
-
return {
|
|
89
|
-
key,
|
|
90
|
-
pingBeginR: pingAnims.find(a => a.getAttribute('attributeName') === 'r')?.getAttribute('begin') || null,
|
|
91
|
-
pingBeginOpacity: pingAnims.find(a => a.getAttribute('attributeName') === 'opacity')?.getAttribute('begin') || null,
|
|
92
|
-
pulseBeginR: pulseAnims.find(a => a.getAttribute('attributeName') === 'r')?.getAttribute('begin') || null,
|
|
93
|
-
pulseBeginOpacity: pulseAnims.find(a => a.getAttribute('attributeName') === 'opacity')?.getAttribute('begin') || null,
|
|
94
|
-
motionBegin: motion?.getAttribute('begin') || null,
|
|
95
|
-
motionDur: motion?.getAttribute('dur') || null,
|
|
96
|
-
};
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
await browser.close();
|
|
100
|
-
|
|
101
|
-
const parseS = (s) => {
|
|
102
|
-
if (!s) return null;
|
|
103
|
-
const m = String(s).match(/^-?(\d+(?:\.\d+)?)s$/);
|
|
104
|
-
if (!m) return null;
|
|
105
|
-
return s.startsWith('-') ? -parseFloat(m[1]) : parseFloat(m[1]);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const close = (a, b, eps = 0.005) => Math.abs(a - b) < eps;
|
|
109
|
-
|
|
110
|
-
const validate = (edge) => {
|
|
111
|
-
const dur = parseS(edge.motionDur);
|
|
112
|
-
const motionBegin = parseS(edge.motionBegin); // -stagger
|
|
113
|
-
const pingR = parseS(edge.pingBeginR);
|
|
114
|
-
const pingOp = parseS(edge.pingBeginOpacity);
|
|
115
|
-
const pulseR = parseS(edge.pulseBeginR);
|
|
116
|
-
const pulseOp = parseS(edge.pulseBeginOpacity);
|
|
117
|
-
if (dur === null || motionBegin === null || motionBegin > 0) return null;
|
|
118
|
-
const stagger = -motionBegin; // positive
|
|
119
|
-
// Expected ping begin = -(stagger + 0.92*dur) mod dur → in [-dur, 0]
|
|
120
|
-
const wrapped = (stagger + 0.92 * dur) % dur;
|
|
121
|
-
const expectedPing = -wrapped;
|
|
122
|
-
return {
|
|
123
|
-
dur,
|
|
124
|
-
motionBegin,
|
|
125
|
-
stagger,
|
|
126
|
-
expectedPing,
|
|
127
|
-
pulseR,
|
|
128
|
-
pulseOp,
|
|
129
|
-
pingR,
|
|
130
|
-
pingOp,
|
|
131
|
-
dispatch_matches_particle: close(pulseR, motionBegin) && close(pulseOp, motionBegin),
|
|
132
|
-
arrival_matches_formula: close(pingR, expectedPing) && close(pingOp, expectedPing),
|
|
133
|
-
};
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const edges = probe.map(validate).filter(e => e !== null);
|
|
137
|
-
|
|
138
|
-
const results = {
|
|
139
|
-
at_least_one_edge: edges.length >= 1,
|
|
140
|
-
all_dispatch_match_particle: edges.every(e => e.dispatch_matches_particle),
|
|
141
|
-
all_arrival_match_formula: edges.every(e => e.arrival_matches_formula),
|
|
142
|
-
pulse_r_op_consistent: edges.every(e => Math.abs(e.pulseR - e.pulseOp) < 0.005),
|
|
143
|
-
ping_r_op_consistent: edges.every(e => Math.abs(e.pingR - e.pingOp) < 0.005),
|
|
144
|
-
};
|
|
145
|
-
const ok = Object.values(results).every(Boolean);
|
|
146
|
-
console.log(`${ok ? '✅' : '❌'} edge SMIL sync:`, JSON.stringify(results),
|
|
147
|
-
'\n edges:', edges);
|
|
148
|
-
process.exit(ok ? 0 : 1);
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/* Round 252 verification: three SMIL edge elements (particle,
|
|
2
|
-
* arrival ping, dispatch pulse) pick up theme-toggle transitions
|
|
3
|
-
* on their static color attributes, closing the last per-edge
|
|
4
|
-
* theme-toggle snaps.
|
|
5
|
-
*
|
|
6
|
-
* Pre-R252 each element had inline style with only pointerEvents
|
|
7
|
-
* (or no style for particle). Theme-driven fill/stroke snapped:
|
|
8
|
-
* · particle: fill=pal.flowParticle (cyber yellow ↔ light amber)
|
|
9
|
-
* · arrival ping: stroke=pal.flowEdge (cyber cyan ↔ light emerald)
|
|
10
|
-
* · dispatch pulse: stroke=pal.flowEdge (same as ping)
|
|
11
|
-
*
|
|
12
|
-
* R252 adds 200ms transitions:
|
|
13
|
-
* · particle: transition: fill 200ms, opacity 200ms
|
|
14
|
-
* · arrival ping: transition: stroke 200ms
|
|
15
|
-
* · dispatch pulse: transition: stroke 200ms
|
|
16
|
-
*
|
|
17
|
-
* The SMIL animateMotion (particle path) + R228 spline-eased animates
|
|
18
|
-
* (ping/pulse r + opacity) run independently of these CSS transitions
|
|
19
|
-
* — no conflict; CSS handles static-color theme transitions, SMIL
|
|
20
|
-
* handles animation values.
|
|
21
|
-
*
|
|
22
|
-
* Scenario: 4 working agents + 5-msg alpha→beta (count≥3, fresh>0.5)
|
|
23
|
-
* → particle + arrival ping + dispatch pulse all rendered.
|
|
24
|
-
*/
|
|
25
|
-
import { chromium } from 'playwright';
|
|
26
|
-
import { readFileSync } from 'node:fs';
|
|
27
|
-
|
|
28
|
-
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
29
|
-
const browser = await chromium.launch({ headless: true });
|
|
30
|
-
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
31
|
-
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
32
|
-
await ctx.addInitScript(() => {
|
|
33
|
-
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
34
|
-
});
|
|
35
|
-
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
36
|
-
await ctx.route('**/api/hub/status*', async (route) => {
|
|
37
|
-
const r = await route.fetch();
|
|
38
|
-
const b = await r.json();
|
|
39
|
-
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
40
|
-
const mk = (alias) => ({
|
|
41
|
-
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
42
|
-
network_id: nid, project_dir: null,
|
|
43
|
-
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
44
|
-
});
|
|
45
|
-
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
46
|
-
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
47
|
-
] } });
|
|
48
|
-
});
|
|
49
|
-
const now = Date.now();
|
|
50
|
-
const msgs = [];
|
|
51
|
-
for (let i = 0; i < 5; i++) {
|
|
52
|
-
msgs.push({
|
|
53
|
-
id: `m${i}`, from_alias: 'alpha', to_alias: 'beta', content: 'hi',
|
|
54
|
-
network_id: 'default', created_at: new Date(now - (1000 + i * 50)).toISOString(),
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: msgs } }));
|
|
58
|
-
await ctx.route('**/api/hub/tasks*', (r2) => r2.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.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
63
|
-
await page.waitForSelector('[data-edge-particle]', { timeout: 10000, state: 'attached' });
|
|
64
|
-
await page.waitForSelector('[data-arrival-ping]', { timeout: 10000, state: 'attached' });
|
|
65
|
-
await page.waitForSelector('[data-dispatch-pulse]', { timeout: 10000, state: 'attached' });
|
|
66
|
-
await page.waitForTimeout(300);
|
|
67
|
-
|
|
68
|
-
const probe = await page.evaluate(() => {
|
|
69
|
-
const grab = (sel) => {
|
|
70
|
-
const el = document.querySelector(sel);
|
|
71
|
-
return el ? { transition: el.style.transition, fill: el.getAttribute('fill'), stroke: el.getAttribute('stroke') } : null;
|
|
72
|
-
};
|
|
73
|
-
return {
|
|
74
|
-
particle: grab('[data-edge-particle]'),
|
|
75
|
-
arrivalPing: grab('[data-arrival-ping]'),
|
|
76
|
-
dispatchPulse: grab('[data-dispatch-pulse]'),
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
await browser.close();
|
|
80
|
-
|
|
81
|
-
const has = (s, prop) => new RegExp(`${prop}\\s+(?:200ms|0\\.2s)`).test(s || '');
|
|
82
|
-
|
|
83
|
-
const results = {
|
|
84
|
-
particle_present: probe.particle !== null,
|
|
85
|
-
particle_has_fill_200: has(probe.particle?.transition, 'fill'),
|
|
86
|
-
particle_has_opacity_200: has(probe.particle?.transition, 'opacity'),
|
|
87
|
-
|
|
88
|
-
arrival_ping_present: probe.arrivalPing !== null,
|
|
89
|
-
arrival_ping_has_stroke_200: has(probe.arrivalPing?.transition, 'stroke'),
|
|
90
|
-
|
|
91
|
-
dispatch_pulse_present: probe.dispatchPulse !== null,
|
|
92
|
-
dispatch_pulse_has_stroke_200: has(probe.dispatchPulse?.transition, 'stroke'),
|
|
93
|
-
};
|
|
94
|
-
const ok = Object.values(results).every(Boolean);
|
|
95
|
-
console.log(`${ok ? '✅' : '❌'} edge SMIL theme ease:`, JSON.stringify(results),
|
|
96
|
-
'\n probe:', probe);
|
|
97
|
-
process.exit(ok ? 0 : 1);
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/* Round 228 verification: arrival-ping (R75) and dispatch-pulse
|
|
2
|
-
* (R76) SMIL <animate>s get pulse-pop ease curves via calcMode/
|
|
3
|
-
* keyTimes/keySplines.
|
|
4
|
-
*
|
|
5
|
-
* Coverage:
|
|
6
|
-
* - [data-arrival-ping] r → ease-out across both segments
|
|
7
|
-
* - [data-arrival-ping] opacity → ease-out rise, ease-in fall
|
|
8
|
-
* - [data-dispatch-pulse] r → ease-out across both segments
|
|
9
|
-
* - [data-dispatch-pulse] opacity → ease-out rise, ease-in fall
|
|
10
|
-
*
|
|
11
|
-
* Scenario: 4 working agents + 5 messages alpha→beta so:
|
|
12
|
-
* - link.count = 5 ≥ 3 (dispatch pulse fires)
|
|
13
|
-
* - fresh > 0.5 (both ping + pulse gate active)
|
|
14
|
-
* - reducedMotion off (cyber-themed cookie default)
|
|
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 browser = await chromium.launch({ headless: true });
|
|
21
|
-
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
22
|
-
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
23
|
-
await ctx.addInitScript(() => {
|
|
24
|
-
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
25
|
-
});
|
|
26
|
-
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
27
|
-
await ctx.route('**/api/hub/status*', async (route) => {
|
|
28
|
-
const r = await route.fetch();
|
|
29
|
-
const b = await r.json();
|
|
30
|
-
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
31
|
-
const mk = (alias) => ({
|
|
32
|
-
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
33
|
-
network_id: nid, project_dir: null,
|
|
34
|
-
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
35
|
-
});
|
|
36
|
-
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
37
|
-
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
38
|
-
] } });
|
|
39
|
-
});
|
|
40
|
-
const now = Date.now();
|
|
41
|
-
const msgs = [];
|
|
42
|
-
for (let i = 0; i < 5; i++) {
|
|
43
|
-
msgs.push({
|
|
44
|
-
id: `m${i}`, from_alias: 'alpha', to_alias: 'beta', content: 'hi',
|
|
45
|
-
network_id: 'default', created_at: new Date(now - (1000 + i * 50)).toISOString(),
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: msgs } }));
|
|
49
|
-
await ctx.route('**/api/hub/tasks*', (r2) => r2.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.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
54
|
-
await page.waitForSelector('[data-arrival-ping]', { timeout: 10000, state: 'attached' });
|
|
55
|
-
await page.waitForSelector('[data-dispatch-pulse]', { timeout: 10000, state: 'attached' });
|
|
56
|
-
await page.waitForTimeout(400);
|
|
57
|
-
|
|
58
|
-
const out = await page.evaluate(() => {
|
|
59
|
-
const probe = (parentSelector) => {
|
|
60
|
-
const el = document.querySelector(parentSelector);
|
|
61
|
-
if (!el) return null;
|
|
62
|
-
const animates = Array.from(el.querySelectorAll('animate'));
|
|
63
|
-
return animates.map((a) => ({
|
|
64
|
-
attr: a.getAttribute('attributeName'),
|
|
65
|
-
calcMode: a.getAttribute('calcMode'),
|
|
66
|
-
keyTimes: a.getAttribute('keyTimes'),
|
|
67
|
-
keySplines: a.getAttribute('keySplines'),
|
|
68
|
-
}));
|
|
69
|
-
};
|
|
70
|
-
return {
|
|
71
|
-
arrival: probe('[data-arrival-ping]'),
|
|
72
|
-
dispatch: probe('[data-dispatch-pulse]'),
|
|
73
|
-
};
|
|
74
|
-
});
|
|
75
|
-
await browser.close();
|
|
76
|
-
|
|
77
|
-
const easeOutBoth = '0.25 0.1 0.25 1;0.25 0.1 0.25 1';
|
|
78
|
-
const pulsePop = '0.25 0.1 0.25 1;0.42 0 1 1';
|
|
79
|
-
const keyTimes = '0;0.5;1';
|
|
80
|
-
|
|
81
|
-
const find = (arr, attr) => arr?.find(a => a.attr === attr);
|
|
82
|
-
const matches = (a, splines) => a && a.calcMode === 'spline' && a.keyTimes === keyTimes && a.keySplines === splines;
|
|
83
|
-
|
|
84
|
-
const results = {
|
|
85
|
-
arrival_present: Array.isArray(out.arrival) && out.arrival.length === 2,
|
|
86
|
-
arrival_r_easeout: matches(find(out.arrival, 'r'), easeOutBoth),
|
|
87
|
-
arrival_op_pulse: matches(find(out.arrival, 'opacity'), pulsePop),
|
|
88
|
-
dispatch_present: Array.isArray(out.dispatch) && out.dispatch.length === 2,
|
|
89
|
-
dispatch_r_easeout: matches(find(out.dispatch, 'r'), easeOutBoth),
|
|
90
|
-
dispatch_op_pulse: matches(find(out.dispatch, 'opacity'), pulsePop),
|
|
91
|
-
};
|
|
92
|
-
const ok = Object.values(results).every(Boolean);
|
|
93
|
-
console.log(`${ok ? '✅' : '❌'} ping+pulse ease:`, JSON.stringify(results),
|
|
94
|
-
'\n arrival:', out.arrival,
|
|
95
|
-
'\n dispatch:', out.dispatch);
|
|
96
|
-
process.exit(ok ? 0 : 1);
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/* Round 145 verification: R46 idle-spoke flow rate buckets on
|
|
2
|
-
* workingCount, completing the 4-layer busyness-driven motion
|
|
3
|
-
* family:
|
|
4
|
-
* R84 hub breath (centre)
|
|
5
|
-
* R131 outer-ring orbit (periphery)
|
|
6
|
-
* R132 groupbox march (per-team, grid layout)
|
|
7
|
-
* R145 idle-spoke flow (ring layout, hub→nodes) — new
|
|
8
|
-
*
|
|
9
|
-
* Bucket ladder shared across the four layers (same R84
|
|
10
|
-
* thresholds):
|
|
11
|
-
* workingCount === 0 → bucket 0
|
|
12
|
-
* workingCount in [1, 2] → bucket 1
|
|
13
|
-
* workingCount in [3, 5] → bucket 2
|
|
14
|
-
* workingCount >= 6 → bucket 3
|
|
15
|
-
*
|
|
16
|
-
* Idle-spoke durations per bucket: 2.8 / 2.4 / 2.0 / 1.6 seconds
|
|
17
|
-
* (R46 baseline 2.4s is bucket 1).
|
|
18
|
-
*
|
|
19
|
-
* Spokes only render in ring layout. Active spokes (carrying flow
|
|
20
|
-
* traffic) don't get the animation — they're solid bright strokes.
|
|
21
|
-
* Only idle spokes carry the .anet-topo-spoke-flow class and the
|
|
22
|
-
* --spoke-dur var. The test seeds 0 messages so all spokes are idle.
|
|
23
|
-
*/
|
|
24
|
-
import { chromium } from 'playwright';
|
|
25
|
-
import { readFileSync } from 'node:fs';
|
|
26
|
-
|
|
27
|
-
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
28
|
-
|
|
29
|
-
async function probe(workingCount, totalCount) {
|
|
30
|
-
const browser = await chromium.launch({ headless: true });
|
|
31
|
-
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
32
|
-
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
33
|
-
await ctx.addInitScript(() => {
|
|
34
|
-
try {
|
|
35
|
-
localStorage.setItem('anet-theme', 'cyber');
|
|
36
|
-
sessionStorage.setItem('anet_v3_auth', '1');
|
|
37
|
-
// ring layout is R145's target — it's also the default, but
|
|
38
|
-
// pinning explicitly defends against future default flips.
|
|
39
|
-
localStorage.setItem('anet-topo-layout', 'ring');
|
|
40
|
-
} catch {}
|
|
41
|
-
});
|
|
42
|
-
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
43
|
-
await ctx.route('**/api/hub/status*', async (route) => {
|
|
44
|
-
const r = await route.fetch();
|
|
45
|
-
const b = await r.json();
|
|
46
|
-
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
47
|
-
const sessions = [];
|
|
48
|
-
for (let i = 0; i < totalCount; i++) {
|
|
49
|
-
sessions.push({
|
|
50
|
-
alias: `node${i}`,
|
|
51
|
-
status: i < workingCount ? 'working' : 'idle',
|
|
52
|
-
model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
53
|
-
network_id: nid, project_dir: null,
|
|
54
|
-
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
58
|
-
});
|
|
59
|
-
// 0 messages → no active spokes → every spoke is idle (.anet-topo-spoke-flow)
|
|
60
|
-
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
61
|
-
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
62
|
-
|
|
63
|
-
const page = await ctx.newPage();
|
|
64
|
-
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
65
|
-
await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n, totalCount, { timeout: 30000 });
|
|
66
|
-
await page.waitForTimeout(400);
|
|
67
|
-
|
|
68
|
-
const out = await page.evaluate(() => {
|
|
69
|
-
const spokes = [...document.querySelectorAll('path.anet-topo-spoke-flow')];
|
|
70
|
-
return spokes.map(p => ({
|
|
71
|
-
bucket: p.getAttribute('data-topo-spoke-bucket'),
|
|
72
|
-
dur: p.getAttribute('data-topo-spoke-dur'),
|
|
73
|
-
// Resolved animation-duration from computed style — confirms
|
|
74
|
-
// the CSS var bridge through to the cascade.
|
|
75
|
-
cssDur: getComputedStyle(p).getPropertyValue('animation-duration').trim(),
|
|
76
|
-
}));
|
|
77
|
-
});
|
|
78
|
-
await browser.close();
|
|
79
|
-
return out;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// One probe per bucket
|
|
83
|
-
const b0 = await probe(0, 4); // 0 working / 4 idle → bucket 0 (2.8s)
|
|
84
|
-
const b1 = await probe(2, 4); // 2 working / 2 idle → bucket 1 (2.4s)
|
|
85
|
-
const b2 = await probe(4, 5); // 4 working / 1 idle → bucket 2 (2.0s)
|
|
86
|
-
const b3 = await probe(7, 8); // 7 working / 1 idle → bucket 3 (1.6s)
|
|
87
|
-
|
|
88
|
-
const consistent = (spokes, bucket, dur) =>
|
|
89
|
-
spokes.length > 0 &&
|
|
90
|
-
spokes.every(s => s.bucket === String(bucket) && s.dur === String(dur) && s.cssDur === `${dur}s`);
|
|
91
|
-
|
|
92
|
-
const results = {
|
|
93
|
-
b0_someSpokes: b0.length > 0,
|
|
94
|
-
b0_bucket0_2_8s: consistent(b0, 0, 2.8),
|
|
95
|
-
b1_bucket1_2_4s: consistent(b1, 1, 2.4),
|
|
96
|
-
b2_bucket2_2_0s: consistent(b2, 2, 2.0),
|
|
97
|
-
b3_bucket3_1_6s: consistent(b3, 3, 1.6),
|
|
98
|
-
// dur strictly decreases as busy climbs
|
|
99
|
-
dur_monotonic:
|
|
100
|
-
Number(b0[0]?.dur) > Number(b1[0]?.dur) &&
|
|
101
|
-
Number(b1[0]?.dur) > Number(b2[0]?.dur) &&
|
|
102
|
-
Number(b2[0]?.dur) > Number(b3[0]?.dur),
|
|
103
|
-
};
|
|
104
|
-
const ok = Object.values(results).every(Boolean);
|
|
105
|
-
console.log(`${ok ? '✅' : '❌'} spoke busyness:`, JSON.stringify(results),
|
|
106
|
-
`\n b0(0w/4)=`, b0.slice(0,1),
|
|
107
|
-
`\n b1(2w/4)=`, b1.slice(0,1),
|
|
108
|
-
`\n b2(4w/5)=`, b2.slice(0,1),
|
|
109
|
-
`\n b3(7w/8)=`, b3.slice(0,1));
|
|
110
|
-
process.exit(ok ? 0 : 1);
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/* Round 240 verification: backdrop spokes (6 radar-style lines)
|
|
2
|
-
* tint to pal.legendAccent on any pinned filter, mirroring R93's
|
|
3
|
-
* tier-ring treatment. Background scaffolding now signals
|
|
4
|
-
* 'filtered mode' as a unified set instead of half-and-half.
|
|
5
|
-
*
|
|
6
|
-
* Cyber theme palette:
|
|
7
|
-
* pal.ringStroke = #164e63 = rgb(22, 78, 99)
|
|
8
|
-
* pal.legendAccent = #67e8f9 = rgb(103, 232, 249)
|
|
9
|
-
*
|
|
10
|
-
* Test scope:
|
|
11
|
-
* - rest state (no pin): all 6 spokes stroke = ringStroke
|
|
12
|
-
* - pinned state: all 6 spokes stroke = legendAccent
|
|
13
|
-
* - transition wiring: style.transition contains 'stroke 200ms'
|
|
14
|
-
* (browser may normalise to '0.2s' — accept either)
|
|
15
|
-
* - data-topo-spoke-tinted attr flips false ↔ true
|
|
16
|
-
*
|
|
17
|
-
* Scenario: 4 working agents (single tier, simple geometry).
|
|
18
|
-
* Click the working pressure-bar segment to pin status=working.
|
|
19
|
-
*/
|
|
20
|
-
import { chromium } from 'playwright';
|
|
21
|
-
import { readFileSync } from 'node:fs';
|
|
22
|
-
|
|
23
|
-
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
24
|
-
const browser = await chromium.launch({ headless: true });
|
|
25
|
-
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
26
|
-
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
27
|
-
await ctx.addInitScript(() => {
|
|
28
|
-
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
29
|
-
});
|
|
30
|
-
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
31
|
-
await ctx.route('**/api/hub/status*', async (route) => {
|
|
32
|
-
const r = await route.fetch();
|
|
33
|
-
const b = await r.json();
|
|
34
|
-
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
35
|
-
const mk = (alias) => ({
|
|
36
|
-
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
37
|
-
network_id: nid, project_dir: null,
|
|
38
|
-
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
39
|
-
});
|
|
40
|
-
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
41
|
-
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
42
|
-
] } });
|
|
43
|
-
});
|
|
44
|
-
await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
|
|
45
|
-
await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
|
|
46
|
-
|
|
47
|
-
const page = await ctx.newPage();
|
|
48
|
-
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
49
|
-
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
50
|
-
await page.waitForSelector('[data-topo-spoke-angle]', { timeout: 10000, state: 'attached' });
|
|
51
|
-
await page.waitForTimeout(400);
|
|
52
|
-
|
|
53
|
-
const probeSpokes = (page) => page.evaluate(() => {
|
|
54
|
-
const spokes = Array.from(document.querySelectorAll('[data-topo-spoke-angle]'));
|
|
55
|
-
return spokes.map((s) => ({
|
|
56
|
-
angle: s.getAttribute('data-topo-spoke-angle'),
|
|
57
|
-
tinted: s.getAttribute('data-topo-spoke-tinted'),
|
|
58
|
-
fill: getComputedStyle(s).stroke,
|
|
59
|
-
transition: getComputedStyle(s).transition,
|
|
60
|
-
}));
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const rest = await probeSpokes(page);
|
|
64
|
-
|
|
65
|
-
// Pin working status via pressure-bar segment click
|
|
66
|
-
await page.locator('[data-pressure-seg="working"]').first().click();
|
|
67
|
-
await page.waitForTimeout(400);
|
|
68
|
-
const pinned = await probeSpokes(page);
|
|
69
|
-
|
|
70
|
-
await browser.close();
|
|
71
|
-
|
|
72
|
-
const RING_RGB = 'rgb(22, 78, 99)';
|
|
73
|
-
const ACCENT_RGB = 'rgb(103, 232, 249)';
|
|
74
|
-
// Browser normalises 200ms → 0.2s
|
|
75
|
-
const hasTransition = (s) => /stroke\s+(?:200ms|0\.2s)/.test(s || '');
|
|
76
|
-
|
|
77
|
-
const results = {
|
|
78
|
-
six_spokes: rest.length === 6,
|
|
79
|
-
rest_all_untinted: rest.every(s => s.tinted === 'false'),
|
|
80
|
-
rest_all_ring_stroke: rest.every(s => s.fill === RING_RGB),
|
|
81
|
-
rest_all_has_transition: rest.every(s => hasTransition(s.transition)),
|
|
82
|
-
|
|
83
|
-
pinned_six_spokes: pinned.length === 6,
|
|
84
|
-
pinned_all_tinted: pinned.every(s => s.tinted === 'true'),
|
|
85
|
-
pinned_all_accent: pinned.every(s => s.fill === ACCENT_RGB),
|
|
86
|
-
pinned_all_has_transition: pinned.every(s => hasTransition(s.transition)),
|
|
87
|
-
|
|
88
|
-
// Angles match the original 0/30/60/90/120/150 pattern (sanity)
|
|
89
|
-
angles_correct: JSON.stringify(rest.map(s => s.angle).sort())
|
|
90
|
-
=== JSON.stringify(['0','120','150','30','60','90']),
|
|
91
|
-
};
|
|
92
|
-
const ok = Object.values(results).every(Boolean);
|
|
93
|
-
console.log(`${ok ? '✅' : '❌'} spoke tint:`, JSON.stringify(results),
|
|
94
|
-
'\n rest: ', rest.map(s => ({ a: s.angle, t: s.tinted, fill: s.fill })),
|
|
95
|
-
'\n pinned:', pinned.map(s => ({ a: s.angle, t: s.tinted, fill: s.fill })));
|
|
96
|
-
process.exit(ok ? 0 : 1);
|
|
File without changes
|
/package/.next/static/{u9Cwn8Wi8dRb7UmkteBWa → HBbB3A7-O2LxL9zMNYHXa}/_clientMiddlewareManifest.js
RENAMED
|
File without changes
|
|
File without changes
|