@sleep2agi/agent-network-dashboard 0.5.1-preview.7 → 0.5.1-preview.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +6 -6
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +2 -2
- package/.next/server/app/admin.rsc +2 -2
- package/.next/server/app/admin.segments/_full.segment.rsc +2 -2
- package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_index.segment.rsc +2 -2
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +3 -3
- package/.next/server/app/login.segments/_full.segment.rsc +3 -3
- package/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/server/app/login.segments/_index.segment.rsc +2 -2
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +2 -2
- package/.next/server/app/logs.rsc +2 -2
- package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
- package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +2 -2
- package/.next/server/app/messages.rsc +2 -2
- package/.next/server/app/messages.segments/_full.segment.rsc +2 -2
- package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_index.segment.rsc +2 -2
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +2 -2
- package/.next/server/app/node.rsc +2 -2
- package/.next/server/app/node.segments/_full.segment.rsc +2 -2
- package/.next/server/app/node.segments/_head.segment.rsc +1 -1
- package/.next/server/app/node.segments/_index.segment.rsc +2 -2
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/node.segments/node.segment.rsc +1 -1
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +2 -2
- package/.next/server/app/nodes.rsc +2 -2
- package/.next/server/app/nodes.segments/_full.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_index.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +2 -2
- package/.next/server/app/server-logs.rsc +2 -2
- package/.next/server/app/server-logs.segments/_full.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_index.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +2 -2
- package/.next/server/app/settings/networks.rsc +2 -2
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +2 -2
- package/.next/server/app/settings/tokens.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +3 -3
- package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
- package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +2 -2
- package/.next/server/app/tasks.rsc +2 -2
- package/.next/server/app/tasks.segments/_full.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_index.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/04.j~y8e~sbs4.js +1 -0
- package/.next/static/chunks/06llamqb4jsu..js +4 -0
- package/.next/static/chunks/0_p8jkzdw5x2_.css +2 -0
- package/.next/static/chunks/12heglqfrp1bm.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/TopoGraph.tsx +1426 -84
- package/package.json +1 -1
- package/scripts/topo-active-links-chip-hover-lift-test.mjs +93 -0
- package/scripts/topo-chip-digit-fontweight-test.mjs +105 -0
- package/scripts/topo-chip-row-hover-lift-test.mjs +95 -0
- package/scripts/topo-chrome-button-hover-lift-test.mjs +94 -0
- package/scripts/topo-chrome-segmented-radius-test.mjs +100 -0
- package/scripts/topo-click-ripple-opacity-test.mjs +99 -0
- package/scripts/topo-edge-badge-fontsize-test.mjs +90 -0
- package/scripts/topo-edge-badge-hover-opacity-test.mjs +94 -0
- package/scripts/topo-edge-badge-hover-stroke-test.mjs +92 -0
- package/scripts/topo-edge-badge-opacity-test.mjs +80 -0
- package/scripts/topo-edge-badge-pin-opacity-test.mjs +86 -0
- package/scripts/topo-edge-badge-stroke-test.mjs +92 -0
- package/scripts/topo-edge-freshness-floor-test.mjs +99 -0
- package/scripts/topo-edge-visible-linecap-test.mjs +89 -0
- package/scripts/topo-filter-pill-hover-lift-test.mjs +101 -0
- package/scripts/topo-filter-pill-hover-opacity-test.mjs +110 -0
- package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
- package/scripts/topo-flow-rail-linecap-test.mjs +79 -0
- package/scripts/topo-freshness-chip-hierarchy-test.mjs +93 -0
- package/scripts/topo-freshness-chip-tabular-test.mjs +41 -0
- package/scripts/topo-freshness-floor-lift-test.mjs +92 -0
- package/scripts/topo-freshness-suffix-tabular-test.mjs +88 -0
- package/scripts/topo-fullscreen-icon-hover-scale-test.mjs +91 -0
- package/scripts/topo-group-box-stroke-test.mjs +105 -0
- package/scripts/topo-group-label-count-fontweight-test.mjs +108 -0
- package/scripts/topo-hover-detail-body-fw-test.mjs +101 -0
- package/scripts/topo-hover-detail-model-fw-test.mjs +98 -0
- package/scripts/topo-hover-detail-opacity-test.mjs +98 -0
- package/scripts/topo-hover-detail-rx-test.mjs +81 -0
- package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
- package/scripts/topo-hub-halo-light-trough-test.mjs +88 -0
- package/scripts/topo-hub-halo-radius-test.mjs +86 -0
- package/scripts/topo-hub-halo-trough-test.mjs +83 -0
- package/scripts/topo-hub-highlight-opacity-test.mjs +88 -0
- package/scripts/topo-hub-highlight-radius-test.mjs +90 -0
- package/scripts/topo-hub-hover-ring-opacity-test.mjs +96 -0
- package/scripts/topo-hub-hover-ring-stroke-test.mjs +86 -0
- package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
- package/scripts/topo-layout-toggle-hover-tracking-test.mjs +109 -0
- package/scripts/topo-layout-toggle-radius-test.mjs +87 -0
- package/scripts/topo-legend-label-fontweight-test.mjs +94 -0
- package/scripts/topo-legend-pin-ring-stroke-test.mjs +101 -0
- package/scripts/topo-minimap-offline-opacity-test.mjs +90 -0
- package/scripts/topo-minimap-online-opacity-test.mjs +93 -0
- package/scripts/topo-minimap-online-radius-test.mjs +85 -0
- package/scripts/topo-minimap-viewport-linejoin-test.mjs +75 -0
- package/scripts/topo-minimap-viewport-rx-test.mjs +85 -0
- package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
- package/scripts/topo-node-halo-offline-opacity-test.mjs +87 -0
- package/scripts/topo-node-pulse-peak-test.mjs +89 -0
- package/scripts/topo-panel-count-letterspacing-test.mjs +89 -0
- package/scripts/topo-panel-rect-opacity-hover-test.mjs +109 -0
- package/scripts/topo-pressure-bar-height-test.mjs +92 -0
- package/scripts/topo-pressure-kicker-fontweight-test.mjs +76 -0
- package/scripts/topo-recent-pip-radius-2-test.mjs +72 -0
- package/scripts/topo-recent-pip-radius-test.mjs +76 -0
- package/scripts/topo-recent-row-text-fontweight-test.mjs +90 -0
- package/scripts/topo-reset-hover-rotate-test.mjs +102 -0
- package/scripts/topo-spoke-active-opacity-test.mjs +104 -0
- package/scripts/topo-vendor-chip-hover-lift-test.mjs +87 -0
- package/scripts/topo-vendor-glyph-fontweight-test.mjs +102 -0
- package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
- package/scripts/topo-zoom-icon-hover-scale-test.mjs +114 -0
- package/scripts/topo-zoom-level-hover-letterspacing-test.mjs +91 -0
- package/.next/static/chunks/0aauz~36q5n2a.css +0 -2
- package/.next/static/chunks/0bja1amnrg3li.js +0 -1
- package/.next/static/chunks/0k~uc0~~19hyy.js +0 -4
- package/.next/static/chunks/0wtq_6dnzems6.js +0 -1
- /package/.next/static/{x9zCCrMkHsIYlXNY791KF → gaK6yNvVjshUCmKR9qrPn}/_buildManifest.js +0 -0
- /package/.next/static/{x9zCCrMkHsIYlXNY791KF → gaK6yNvVjshUCmKR9qrPn}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{x9zCCrMkHsIYlXNY791KF → gaK6yNvVjshUCmKR9qrPn}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/* Round 381 verification: edge visible flow path strokeLinecap='round'.
|
|
2
|
+
* Sibling SVG stroke-softening polish to R378 flow-rail linecap — both
|
|
3
|
+
* flow-element paths now share 'round' linecap. R380 group box closed
|
|
4
|
+
* dash-cap + corner softening; R381 closes the flow-path family at the
|
|
5
|
+
* visible primary path.
|
|
6
|
+
*
|
|
7
|
+
* Stroke-softening family snapshot post-R381:
|
|
8
|
+
* R288 chrome icons strokeLinecap='round'
|
|
9
|
+
* R378 flow-rail dashes strokeLinecap='round'
|
|
10
|
+
* R380 group box dashes strokeLinecap='round'
|
|
11
|
+
* R381 flow visible path strokeLinecap='round' (this round)
|
|
12
|
+
* R379 viewport rect strokeLinejoin='round'
|
|
13
|
+
* R380 group box corners strokeLinejoin='round'
|
|
14
|
+
*
|
|
15
|
+
* Contract:
|
|
16
|
+
* - [data-edge-visible] element stroke-linecap attr === 'round'.
|
|
17
|
+
* - data-edge-visible-linecap === 'round'.
|
|
18
|
+
* - Pre-R381 invariants:
|
|
19
|
+
* * fill='none', stroke=pal.flowEdge
|
|
20
|
+
* * markerEnd referencing arrow id
|
|
21
|
+
* * opacity-based freshness preserved (any value in 0..1)
|
|
22
|
+
* * transition list contains opacity + stroke-width + stroke
|
|
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
|
+
const browser = await chromium.launch({ headless: true });
|
|
29
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
30
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
31
|
+
await ctx.addInitScript(() => {
|
|
32
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
33
|
+
});
|
|
34
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
35
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
36
|
+
const r = await route.fetch();
|
|
37
|
+
const b = await r.json();
|
|
38
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
39
|
+
const mk = (alias) => ({
|
|
40
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
41
|
+
network_id: nid, project_dir: null,
|
|
42
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
43
|
+
});
|
|
44
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b') ] } });
|
|
45
|
+
});
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
|
|
48
|
+
{ id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
|
|
49
|
+
network_id: 'default', created_at: new Date(now - 5000).toISOString() },
|
|
50
|
+
] } }));
|
|
51
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
52
|
+
|
|
53
|
+
const page = await ctx.newPage();
|
|
54
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
55
|
+
await page.waitForSelector('[data-edge-visible]', { timeout: 15000 });
|
|
56
|
+
await page.waitForTimeout(300);
|
|
57
|
+
|
|
58
|
+
const probe = await page.evaluate(() => {
|
|
59
|
+
const p = document.querySelector('[data-edge-visible]');
|
|
60
|
+
const cs = p ? getComputedStyle(p) : null;
|
|
61
|
+
return {
|
|
62
|
+
linecapAttr: p?.getAttribute('stroke-linecap') ?? null,
|
|
63
|
+
linecapData: p?.getAttribute('data-edge-visible-linecap') ?? null,
|
|
64
|
+
fillAttr: p?.getAttribute('fill') ?? null,
|
|
65
|
+
markerEndAttr: p?.getAttribute('marker-end') ?? null,
|
|
66
|
+
transition: cs?.transition ?? null,
|
|
67
|
+
linkKey: p?.getAttribute('data-edge-visible') ?? null,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await browser.close();
|
|
72
|
+
|
|
73
|
+
const hasTrans = (s, prop) =>
|
|
74
|
+
new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
|
|
75
|
+
|
|
76
|
+
const results = {
|
|
77
|
+
linecap_round: probe.linecapAttr === 'round',
|
|
78
|
+
data_linecap_round: probe.linecapData === 'round',
|
|
79
|
+
fill_none: probe.fillAttr === 'none',
|
|
80
|
+
marker_end_arrow: /^url\(#topo-arrow-/.test(probe.markerEndAttr || ''),
|
|
81
|
+
trans_has_opacity: hasTrans(probe.transition, 'opacity'),
|
|
82
|
+
trans_has_stroke_w: hasTrans(probe.transition, 'stroke-width'),
|
|
83
|
+
trans_has_stroke: hasTrans(probe.transition, 'stroke'),
|
|
84
|
+
link_key_present: (probe.linkKey || '').length > 0,
|
|
85
|
+
};
|
|
86
|
+
const ok = Object.values(results).every(Boolean);
|
|
87
|
+
console.log(`${ok ? '✅' : '❌'} edge visible path strokeLinecap='round':`, JSON.stringify(results),
|
|
88
|
+
'\n probe:', probe);
|
|
89
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Round 397 verification: filter pin pills gain hover lift
|
|
2
|
+
* translateY(-1px). Sibling to R143 recent-row lift idiom —
|
|
3
|
+
* extends the "rising on hover" gesture from SVG canvas surfaces
|
|
4
|
+
* to the HTML filter chip strip. All 4 filter pill variants
|
|
5
|
+
* (status / group / vendor / edge) carry the lift.
|
|
6
|
+
*
|
|
7
|
+
* Contract:
|
|
8
|
+
* - Source-file verification of the new className tokens
|
|
9
|
+
* (transition-transform + hover:-translate-y-px + transform-gpu)
|
|
10
|
+
* - The data-topo-filter-pill-hover-lift="true" sentinel attr
|
|
11
|
+
* is mounted when any filter pill is rendered (pinned filter
|
|
12
|
+
* present). Test mounts a status filter by clicking a status
|
|
13
|
+
* chip, then probes the resulting pill.
|
|
14
|
+
* - Pre-R397 invariants preserved on the pill className:
|
|
15
|
+
* * group / rounded-md / font-mono / font-medium / text-xs
|
|
16
|
+
* / border / anet-fade-in / anet-topo-chip-focus
|
|
17
|
+
*
|
|
18
|
+
* Note: hover dispatch on HTML chips in headless can fire but
|
|
19
|
+
* computing the post-hover translate-y reliably requires a mouse
|
|
20
|
+
* sweep. Test asserts the className tokens are present (which
|
|
21
|
+
* is what Tailwind compiles into `:hover` CSS) plus the sentinel
|
|
22
|
+
* attr — both deterministic from source.
|
|
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
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
29
|
+
|
|
30
|
+
const browser = await chromium.launch({ headless: true });
|
|
31
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, 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 { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
35
|
+
});
|
|
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, status) => ({
|
|
41
|
+
alias, status, 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', 'working'),
|
|
47
|
+
mk('beta', 'idle'),
|
|
48
|
+
mk('gamma', 'idle'),
|
|
49
|
+
] } });
|
|
50
|
+
});
|
|
51
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
52
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
53
|
+
|
|
54
|
+
const page = await ctx.newPage();
|
|
55
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
56
|
+
await page.waitForSelector('g[data-node]', { timeout: 15000 });
|
|
57
|
+
await page.waitForTimeout(400);
|
|
58
|
+
|
|
59
|
+
// Click a legend status row to pin the status filter → mounts the
|
|
60
|
+
// filter pill. Use the alpha node since `working` row exists in fixture.
|
|
61
|
+
await page.click('[data-legend-status="working"]');
|
|
62
|
+
await page.waitForSelector('[data-topo-filter-pill-hover-lift="true"]', { timeout: 5000 });
|
|
63
|
+
|
|
64
|
+
const probe = await page.evaluate(() => {
|
|
65
|
+
const pill = document.querySelector('[data-topo-filter-pill-hover-lift="true"]');
|
|
66
|
+
return pill ? {
|
|
67
|
+
className: pill.className,
|
|
68
|
+
activeFilter: pill.getAttribute('data-active-filter'),
|
|
69
|
+
} : null;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
73
|
+
const occurrences = (fileText.match(/hover:-translate-y-px/g) || []).length;
|
|
74
|
+
const sentinelOccurrences = (fileText.match(/data-topo-filter-pill-hover-lift="true"/g) || []).length;
|
|
75
|
+
|
|
76
|
+
await browser.close();
|
|
77
|
+
|
|
78
|
+
const cls = probe?.className || '';
|
|
79
|
+
const results = {
|
|
80
|
+
// Sentinel attr mounted on the filter pill
|
|
81
|
+
pill_mounted: !!probe,
|
|
82
|
+
// R397: hover-lift tokens present in className
|
|
83
|
+
has_transition_transform: cls.includes('transition-transform'),
|
|
84
|
+
has_hover_translate: cls.includes('hover:-translate-y-px'),
|
|
85
|
+
has_transform_gpu: cls.includes('transform-gpu'),
|
|
86
|
+
has_duration_200: cls.includes('duration-200'),
|
|
87
|
+
has_ease_out: cls.includes('ease-out'),
|
|
88
|
+
// Pre-R397 className tokens preserved
|
|
89
|
+
has_rounded_md: cls.includes('rounded-md'),
|
|
90
|
+
has_font_mono: cls.includes('font-mono'),
|
|
91
|
+
has_anet_topo_chip_focus: cls.includes('anet-topo-chip-focus'),
|
|
92
|
+
// Source-file replace_all coverage: 3 "group ..." pills + 1 plain pill = 4 occurrences
|
|
93
|
+
source_4_pills_lifted: occurrences === 4,
|
|
94
|
+
source_4_sentinels: sentinelOccurrences === 4,
|
|
95
|
+
};
|
|
96
|
+
const ok = Object.values(results).every(Boolean);
|
|
97
|
+
console.log(`${ok ? '✅' : '❌'} filter pin pills hover lift translateY(-1px):`, JSON.stringify(results),
|
|
98
|
+
'\n className:', cls,
|
|
99
|
+
'\n activeFilter:', probe?.activeFilter,
|
|
100
|
+
'\n occurrences:', occurrences, '/ sentinels:', sentinelOccurrences);
|
|
101
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* Round 355 verification: filter pin pills (status / group / vendor)
|
|
2
|
+
* gain pure-CSS group-hover lift of their inner opacity-70 spans
|
|
3
|
+
* (prefix `filter:` + count `· N`) to 100 % on pill hover. Marks the
|
|
4
|
+
* whole pill as scan-it interactive without layout reflow (opacity
|
|
5
|
+
* is paint-only). The R335 prefix-opacity tier + R323 count-opacity
|
|
6
|
+
* tier are preserved at rest; both ease to 100 % on pill hover.
|
|
7
|
+
*
|
|
8
|
+
* Contract (cyber, status filter pinned to 'working'):
|
|
9
|
+
* - Rest: filter-prefix + filter-pill-count computed opacity 0.7.
|
|
10
|
+
* - Hover pill: both spans computed opacity 1.
|
|
11
|
+
* - Inline transition contains 'opacity'.
|
|
12
|
+
* - Pre-R355 invariants:
|
|
13
|
+
* * R335 prefix span keeps `hidden sm:inline opacity-70` baseline
|
|
14
|
+
* * R323 count span keeps `opacity-70 tabular-nums` baseline
|
|
15
|
+
* * Pill click-to-clear unchanged (still has onClick / cursor)
|
|
16
|
+
*/
|
|
17
|
+
import { chromium } from 'playwright';
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
19
|
+
|
|
20
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
21
|
+
const browser = await chromium.launch({ headless: true });
|
|
22
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
23
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
24
|
+
await ctx.addInitScript(() => {
|
|
25
|
+
try {
|
|
26
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
27
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
28
|
+
// Pre-pin status='working' so the filter pill mounts on first paint.
|
|
29
|
+
localStorage.setItem('anet-pinnedStatus', JSON.stringify('working'));
|
|
30
|
+
} catch {}
|
|
31
|
+
});
|
|
32
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
33
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
34
|
+
const r = await route.fetch();
|
|
35
|
+
const b = await r.json();
|
|
36
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
37
|
+
const mk = (alias) => ({
|
|
38
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
39
|
+
network_id: nid, project_dir: null,
|
|
40
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
41
|
+
});
|
|
42
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b'), mk('c') ] } });
|
|
43
|
+
});
|
|
44
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
45
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
46
|
+
|
|
47
|
+
const page = await ctx.newPage();
|
|
48
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
49
|
+
// If pre-pin via localStorage didn't take, click the working chip to pin.
|
|
50
|
+
await page.waitForSelector('[data-working-chip]', { timeout: 15000 });
|
|
51
|
+
const pillVisible = await page.locator('[data-active-filter="status"]').count();
|
|
52
|
+
if (pillVisible === 0) {
|
|
53
|
+
await page.click('[data-working-chip]');
|
|
54
|
+
await page.waitForSelector('[data-active-filter="status"]', { timeout: 5000 });
|
|
55
|
+
}
|
|
56
|
+
await page.waitForTimeout(300);
|
|
57
|
+
|
|
58
|
+
const restProbe = await page.evaluate(() => {
|
|
59
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
60
|
+
const prefix = pill?.querySelector('[data-filter-prefix]');
|
|
61
|
+
const count = pill?.querySelector('[data-filter-pill-count]');
|
|
62
|
+
return {
|
|
63
|
+
prefixOpacity: prefix ? getComputedStyle(prefix).opacity : null,
|
|
64
|
+
countOpacity: count ? getComputedStyle(count).opacity : null,
|
|
65
|
+
prefixTrans: prefix ? getComputedStyle(prefix).transition : null,
|
|
66
|
+
countTrans: count ? getComputedStyle(count).transition : null,
|
|
67
|
+
prefixCls: prefix?.getAttribute('class') ?? null,
|
|
68
|
+
countCls: count?.getAttribute('class') ?? null,
|
|
69
|
+
pillCursor: pill ? getComputedStyle(pill).cursor : null,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Hover the pill.
|
|
74
|
+
await page.hover('[data-active-filter="status"]');
|
|
75
|
+
await page.waitForTimeout(300);
|
|
76
|
+
const hoverProbe = await page.evaluate(() => {
|
|
77
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
78
|
+
const prefix = pill?.querySelector('[data-filter-prefix]');
|
|
79
|
+
const count = pill?.querySelector('[data-filter-pill-count]');
|
|
80
|
+
return {
|
|
81
|
+
prefixOpacity: prefix ? getComputedStyle(prefix).opacity : null,
|
|
82
|
+
countOpacity: count ? getComputedStyle(count).opacity : null,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await browser.close();
|
|
87
|
+
|
|
88
|
+
const approxEq = (a, b, tol = 0.02) => {
|
|
89
|
+
const x = parseFloat(a); const y = parseFloat(b);
|
|
90
|
+
return Number.isFinite(x) && Number.isFinite(y) && Math.abs(x - y) <= tol;
|
|
91
|
+
};
|
|
92
|
+
const hasTrans = (s, prop) =>
|
|
93
|
+
new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
|
|
94
|
+
|
|
95
|
+
const results = {
|
|
96
|
+
rest_prefix_0_7: approxEq(restProbe.prefixOpacity, 0.7),
|
|
97
|
+
rest_count_0_7: approxEq(restProbe.countOpacity, 0.7),
|
|
98
|
+
prefix_trans_has_op: hasTrans(restProbe.prefixTrans, 'opacity'),
|
|
99
|
+
count_trans_has_op: hasTrans(restProbe.countTrans, 'opacity'),
|
|
100
|
+
prefix_cls_has_op_70: /opacity-70/.test(restProbe.prefixCls || ''), // R335
|
|
101
|
+
count_cls_has_tabular: /tabular-nums/.test(restProbe.countCls || ''), // R323
|
|
102
|
+
pill_cursor_pointer: restProbe.pillCursor === 'pointer',
|
|
103
|
+
hover_prefix_1: approxEq(hoverProbe.prefixOpacity, 1),
|
|
104
|
+
hover_count_1: approxEq(hoverProbe.countOpacity, 1),
|
|
105
|
+
};
|
|
106
|
+
const ok = Object.values(results).every(Boolean);
|
|
107
|
+
console.log(`${ok ? '✅' : '❌'} filter pill hover-opacity lift:`, JSON.stringify(results),
|
|
108
|
+
'\n rest: ', restProbe,
|
|
109
|
+
'\n hover:', hoverProbe);
|
|
110
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* Round 356 verification: filter pin pill × buttons gain hover:scale-
|
|
2
|
+
* 110 (Tailwind 4 modern CSS `scale` property). Pre-R356 the × had
|
|
3
|
+
* only hover:opacity-70 — target dimmed under cursor but didn't
|
|
4
|
+
* lift. R356 adds a 10 % scale so the click-target reads as "press
|
|
5
|
+
* me" alongside the dim. Sibling polish to R354 vendor letter glyph
|
|
6
|
+
* + R350/R352/R353 chrome icon hover-scales. replace_all touched
|
|
7
|
+
* all 4 filter pin pill × buttons (status / group / vendor / edge).
|
|
8
|
+
*
|
|
9
|
+
* Tailwind 4 compiles scale-110 to the modern CSS `scale` property
|
|
10
|
+
* — probes getComputedStyle(el).scale (verified during R352).
|
|
11
|
+
*
|
|
12
|
+
* Contract (cyber, status filter pinned to 'working'):
|
|
13
|
+
* - Rest: × button computed scale 'none' (no scale applied).
|
|
14
|
+
* - Hover ×: scale === '1.1'.
|
|
15
|
+
* - Inline transition (computed) contains scale/transform.
|
|
16
|
+
* - Pre-R356 invariants: hover:opacity-70 still applies (button
|
|
17
|
+
* dims on hover), cursor: pointer, data-active-filter attr on
|
|
18
|
+
* parent pill still intact.
|
|
19
|
+
*/
|
|
20
|
+
import { chromium } from 'playwright';
|
|
21
|
+
import { readFileSync } from 'node:fs';
|
|
22
|
+
|
|
23
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
24
|
+
const browser = await chromium.launch({ headless: true });
|
|
25
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
26
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
27
|
+
await ctx.addInitScript(() => {
|
|
28
|
+
try {
|
|
29
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
30
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
31
|
+
localStorage.setItem('anet-pinnedStatus', JSON.stringify('working'));
|
|
32
|
+
} catch {}
|
|
33
|
+
});
|
|
34
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
35
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
36
|
+
const r = await route.fetch();
|
|
37
|
+
const b = await r.json();
|
|
38
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
39
|
+
const mk = (alias) => ({
|
|
40
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
41
|
+
network_id: nid, project_dir: null,
|
|
42
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
43
|
+
});
|
|
44
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b'), mk('c') ] } });
|
|
45
|
+
});
|
|
46
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
47
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
48
|
+
|
|
49
|
+
const page = await ctx.newPage();
|
|
50
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
51
|
+
await page.waitForSelector('[data-working-chip]', { timeout: 15000 });
|
|
52
|
+
const pillVisible = await page.locator('[data-active-filter="status"]').count();
|
|
53
|
+
if (pillVisible === 0) {
|
|
54
|
+
await page.click('[data-working-chip]');
|
|
55
|
+
await page.waitForSelector('[data-active-filter="status"]', { timeout: 5000 });
|
|
56
|
+
}
|
|
57
|
+
await page.waitForTimeout(300);
|
|
58
|
+
|
|
59
|
+
const restProbe = await page.evaluate(() => {
|
|
60
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
61
|
+
const x = pill?.querySelector('button[aria-label^="Clear "]');
|
|
62
|
+
const cs = x ? getComputedStyle(x) : null;
|
|
63
|
+
return {
|
|
64
|
+
scale: cs?.scale ?? null,
|
|
65
|
+
transition: cs?.transition ?? null,
|
|
66
|
+
cursor: cs?.cursor ?? null,
|
|
67
|
+
className: x?.getAttribute('class') ?? null,
|
|
68
|
+
pillAttr: pill?.getAttribute('data-active-filter') ?? null,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await page.hover('[data-active-filter="status"] button[aria-label^="Clear "]');
|
|
73
|
+
await page.waitForTimeout(300);
|
|
74
|
+
const hoverProbe = await page.evaluate(() => {
|
|
75
|
+
const x = document.querySelector('[data-active-filter="status"] button[aria-label^="Clear "]');
|
|
76
|
+
return { scale: x ? getComputedStyle(x).scale : null };
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await browser.close();
|
|
80
|
+
|
|
81
|
+
const isRestScale = (s) => s === 'none' || s === '1' || s === '1 1' || s === null;
|
|
82
|
+
const isHoverScale110 = (s) => s === '1.1' || s === '1.1 1.1';
|
|
83
|
+
const hasTrans = (s, prop) =>
|
|
84
|
+
new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
|
|
85
|
+
|
|
86
|
+
const results = {
|
|
87
|
+
rest_scale_1: isRestScale(restProbe.scale),
|
|
88
|
+
trans_has_scale: hasTrans(restProbe.transition, 'scale') || hasTrans(restProbe.transition, 'transform'),
|
|
89
|
+
cursor_pointer: restProbe.cursor === 'pointer',
|
|
90
|
+
cls_has_hover_op_70: /hover:opacity-70/.test(restProbe.className || ''), // R343
|
|
91
|
+
cls_has_hover_scale: /hover:scale-110/.test(restProbe.className || ''),
|
|
92
|
+
pill_attr_status: restProbe.pillAttr === 'status',
|
|
93
|
+
hover_scale_110: isHoverScale110(hoverProbe.scale),
|
|
94
|
+
};
|
|
95
|
+
const ok = Object.values(results).every(Boolean);
|
|
96
|
+
console.log(`${ok ? '✅' : '❌'} filter pill × hover-scale:`, JSON.stringify(results),
|
|
97
|
+
'\n rest: ', restProbe,
|
|
98
|
+
'\n hover: ', hoverProbe);
|
|
99
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* Round 378 verification: edge flow-path dashed-rail picks up
|
|
2
|
+
* strokeLinecap='round'. Pre-R378 '2 12' dashes painted as sharp
|
|
3
|
+
* 1×2 rectangles; R378 rounds each cap so the dashes read as soft
|
|
4
|
+
* 3-px pills.
|
|
5
|
+
*
|
|
6
|
+
* Contract:
|
|
7
|
+
* - [data-edge-flow-rail] element stroke-linecap attr === 'round'.
|
|
8
|
+
* - data-edge-flow-rail-linecap === 'round'.
|
|
9
|
+
* - Pre-R378 invariants:
|
|
10
|
+
* * strokeWidth='1' preserved
|
|
11
|
+
* * strokeDasharray='2 12' preserved
|
|
12
|
+
* * R57 / R245 / R47 transition list (opacity + stroke) preserved
|
|
13
|
+
* * data-edge-flow-rail key still surfaces the link
|
|
14
|
+
*/
|
|
15
|
+
import { chromium } from 'playwright';
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
|
|
18
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
19
|
+
const browser = await chromium.launch({ headless: true });
|
|
20
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
21
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
22
|
+
await ctx.addInitScript(() => {
|
|
23
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
24
|
+
});
|
|
25
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
26
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
27
|
+
const r = await route.fetch();
|
|
28
|
+
const b = await r.json();
|
|
29
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
30
|
+
const mk = (alias) => ({
|
|
31
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
32
|
+
network_id: nid, project_dir: null,
|
|
33
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
34
|
+
});
|
|
35
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b') ] } });
|
|
36
|
+
});
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
|
|
39
|
+
{ id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
|
|
40
|
+
network_id: 'default', created_at: new Date(now - 5000).toISOString() },
|
|
41
|
+
] } }));
|
|
42
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
43
|
+
|
|
44
|
+
const page = await ctx.newPage();
|
|
45
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
46
|
+
await page.waitForSelector('[data-edge-flow-rail]', { timeout: 15000 });
|
|
47
|
+
await page.waitForTimeout(300);
|
|
48
|
+
|
|
49
|
+
const probe = await page.evaluate(() => {
|
|
50
|
+
const rail = document.querySelector('[data-edge-flow-rail]');
|
|
51
|
+
const cs = rail ? getComputedStyle(rail) : null;
|
|
52
|
+
return {
|
|
53
|
+
linecapAttr: rail?.getAttribute('stroke-linecap') ?? null,
|
|
54
|
+
linecapData: rail?.getAttribute('data-edge-flow-rail-linecap') ?? null,
|
|
55
|
+
strokeWidth: rail?.getAttribute('stroke-width') ?? null,
|
|
56
|
+
strokeDasharray: rail?.getAttribute('stroke-dasharray') ?? null,
|
|
57
|
+
transition: cs?.transition ?? null,
|
|
58
|
+
linkKey: rail?.getAttribute('data-edge-flow-rail') ?? null,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await browser.close();
|
|
63
|
+
|
|
64
|
+
const hasTrans = (s, prop) =>
|
|
65
|
+
new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
|
|
66
|
+
|
|
67
|
+
const results = {
|
|
68
|
+
linecap_round: probe.linecapAttr === 'round',
|
|
69
|
+
data_linecap_round: probe.linecapData === 'round',
|
|
70
|
+
stroke_width_1: probe.strokeWidth === '1',
|
|
71
|
+
dasharray_2_12: probe.strokeDasharray === '2 12',
|
|
72
|
+
trans_has_opacity: hasTrans(probe.transition, 'opacity'),
|
|
73
|
+
trans_has_stroke: hasTrans(probe.transition, 'stroke'),
|
|
74
|
+
link_key_present: (probe.linkKey || '').length > 0,
|
|
75
|
+
};
|
|
76
|
+
const ok = Object.values(results).every(Boolean);
|
|
77
|
+
console.log(`${ok ? '✅' : '❌'} flow-rail strokeLinecap='round':`, JSON.stringify(results),
|
|
78
|
+
'\n probe:', probe);
|
|
79
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* Round 410 verification: FreshnessChip body picks up chip-internal-
|
|
2
|
+
* hierarchy — `{sec}` digit (fw=600 data tier) + `s` unit (opacity
|
|
3
|
+
* 0.7 label tier). Extends R333-R341/R362/R369/R389 arc to 10th
|
|
4
|
+
* anchor.
|
|
5
|
+
*
|
|
6
|
+
* Contract:
|
|
7
|
+
* - R275 stale-only render: chip only mounts when sec > 10. Test
|
|
8
|
+
* waits for /api/hub/status to settle, then advances lastSyncRef
|
|
9
|
+
* by 11+ seconds via a long wait (the FreshnessChip ticks every
|
|
10
|
+
* 1s via setInterval). To avoid the slow wait we instead probe
|
|
11
|
+
* the SOURCE-FILE for the wire change, then probe the chip if
|
|
12
|
+
* it happens to mount during the test window.
|
|
13
|
+
* - Source-file:
|
|
14
|
+
* * <span className="font-semibold" data-freshness-chip-digit>
|
|
15
|
+
* * <span className="opacity-70" data-freshness-chip-unit>
|
|
16
|
+
* - If chip mounts at runtime: data-freshness-chip-digit + -unit
|
|
17
|
+
* spans both present
|
|
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: 1500 } });
|
|
27
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
28
|
+
await ctx.addInitScript(() => {
|
|
29
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
30
|
+
});
|
|
31
|
+
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: 'idle', 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: [ mk('alpha') ] } });
|
|
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
|
+
|
|
45
|
+
const page = await ctx.newPage();
|
|
46
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
47
|
+
await page.waitForSelector('[data-topo-hub]', { timeout: 15000 });
|
|
48
|
+
|
|
49
|
+
// Wait for FreshnessChip to mount (R275 stale-only: sec > 10). The
|
|
50
|
+
// chip ticks every 1s. Wait 12s so lastSyncRef-based sec crosses 10.
|
|
51
|
+
await page.waitForTimeout(12000);
|
|
52
|
+
|
|
53
|
+
const probe = await page.evaluate(() => {
|
|
54
|
+
const chip = document.querySelector('[data-freshness-chip]');
|
|
55
|
+
if (!chip) return { mounted: false };
|
|
56
|
+
const digit = chip.querySelector('[data-freshness-chip-digit]');
|
|
57
|
+
const unit = chip.querySelector('[data-freshness-chip-unit]');
|
|
58
|
+
return {
|
|
59
|
+
mounted: true,
|
|
60
|
+
chipStale: chip.getAttribute('data-freshness-chip-stale'),
|
|
61
|
+
digitClass: digit?.className ?? null,
|
|
62
|
+
digitText: digit?.textContent ?? null,
|
|
63
|
+
unitClass: unit?.className ?? null,
|
|
64
|
+
unitText: unit?.textContent ?? null,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
69
|
+
const sourceHasDigitSpan = /<span className="font-semibold" data-freshness-chip-digit>/.test(fileText);
|
|
70
|
+
const sourceHasUnitSpan = /<span className="opacity-70" data-freshness-chip-unit>s<\/span>/.test(fileText);
|
|
71
|
+
|
|
72
|
+
await browser.close();
|
|
73
|
+
|
|
74
|
+
// Either runtime probe catches the chip OR source-file verification
|
|
75
|
+
// alone establishes the wire change.
|
|
76
|
+
const results = {
|
|
77
|
+
// Source-file canonical wire (deterministic)
|
|
78
|
+
source_digit_span: sourceHasDigitSpan,
|
|
79
|
+
source_unit_span: sourceHasUnitSpan,
|
|
80
|
+
// Runtime: chip might be present if test wait crossed 10s; assertions
|
|
81
|
+
// are conditional on mounted (only enforce structure when chip exists).
|
|
82
|
+
runtime_check_passed: !probe.mounted || (
|
|
83
|
+
probe.chipStale === 'true' &&
|
|
84
|
+
probe.digitClass?.includes('font-semibold') === true &&
|
|
85
|
+
/^\d+$/.test(probe.digitText || '') &&
|
|
86
|
+
probe.unitClass?.includes('opacity-70') === true &&
|
|
87
|
+
probe.unitText === 's'
|
|
88
|
+
),
|
|
89
|
+
};
|
|
90
|
+
const ok = Object.values(results).every(Boolean);
|
|
91
|
+
console.log(`${ok ? '✅' : '❌'} FreshnessChip chip-internal-hierarchy:`, JSON.stringify(results),
|
|
92
|
+
'\n probe:', probe);
|
|
93
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* Round 377 verification: FreshnessChip baseClass picks up `tabular-
|
|
2
|
+
* nums`. The chip-row's last untouched chip joins the R224-R357
|
|
3
|
+
* tabular-nums sweep. `font-mono` already gave equal-width glyphs;
|
|
4
|
+
* R377 adds the explicit invariant the rest of the chip row carries.
|
|
5
|
+
*
|
|
6
|
+
* The chip only renders when stale (R275 gate: sec > 10s since
|
|
7
|
+
* last SWR sync). Test verifies the source-file invariant directly
|
|
8
|
+
* (className contains 'tabular-nums') since simulating > 10 s of
|
|
9
|
+
* no SWR refresh in a Playwright test is brittle.
|
|
10
|
+
*
|
|
11
|
+
* Contract:
|
|
12
|
+
* - Source file TopoGraph.tsx contains the new baseClass with
|
|
13
|
+
* 'tabular-nums' between 'font-medium' and 'border'.
|
|
14
|
+
* - Source file mentions R377 round marker.
|
|
15
|
+
* - All preserved invariants: font-mono, font-medium (R313/R315),
|
|
16
|
+
* transition-colors duration-300 (R187), rounded-md, border.
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
19
|
+
|
|
20
|
+
const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
21
|
+
|
|
22
|
+
const baseClassLineMatch = src.match(/const baseClass = "([^"]+)"/);
|
|
23
|
+
const baseClass = baseClassLineMatch?.[1] ?? '';
|
|
24
|
+
|
|
25
|
+
const results = {
|
|
26
|
+
source_has_baseclass: !!baseClassLineMatch,
|
|
27
|
+
baseclass_has_tabular_nums: /\btabular-nums\b/.test(baseClass),
|
|
28
|
+
baseclass_has_font_mono: /\bfont-mono\b/.test(baseClass),
|
|
29
|
+
baseclass_has_font_medium: /\bfont-medium\b/.test(baseClass),
|
|
30
|
+
baseclass_has_rounded_md: /\brounded-md\b/.test(baseClass),
|
|
31
|
+
baseclass_has_border: /\bborder\b/.test(baseClass),
|
|
32
|
+
baseclass_has_transition: /\btransition-colors\b/.test(baseClass),
|
|
33
|
+
baseclass_duration_300: /\bduration-300\b/.test(baseClass),
|
|
34
|
+
// Ensure ordering: font-medium → tabular-nums → border.
|
|
35
|
+
ordering_correct: /font-medium tabular-nums border/.test(baseClass),
|
|
36
|
+
round_marker_present: /Round 377 \/ Loop/.test(src),
|
|
37
|
+
};
|
|
38
|
+
const ok = Object.values(results).every(Boolean);
|
|
39
|
+
console.log(`${ok ? '✅' : '❌'} FreshnessChip baseClass tabular-nums:`, JSON.stringify(results),
|
|
40
|
+
'\n baseClass:', baseClass);
|
|
41
|
+
process.exit(ok ? 0 : 1);
|