@sleep2agi/agent-network-dashboard 0.5.1-preview.9 → 0.5.1-preview.91
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/017hq2-5l~_98.css +2 -0
- package/.next/static/chunks/07t9_p5h7da1u.js +1 -0
- package/.next/static/chunks/0ahff_xzbgbg4.js +4 -0
- package/.next/static/chunks/10qa7z9iocn6t.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/TopoGraph.tsx +1777 -111
- 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-digit-hover-bold-test.mjs +94 -0
- package/scripts/topo-chip-row-group-hover-brighten-test.mjs +107 -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-digit-fw-test.mjs +103 -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-particle-radius-test.mjs +76 -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-value-fw-test.mjs +88 -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-digit-fw-hover-test.mjs +102 -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-hover-opacity-test.mjs +119 -0
- package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
- package/scripts/topo-label-card-opacity-hover-test.mjs +99 -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-hover-opacity-test.mjs +92 -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-alias-letter-spacing-test.mjs +112 -0
- package/scripts/topo-node-halo-offline-opacity-test.mjs +87 -0
- package/scripts/topo-node-label-card-rx-test.mjs +85 -0
- package/scripts/topo-node-pulse-peak-test.mjs +89 -0
- package/scripts/topo-node-pulse-trough-test.mjs +91 -0
- package/scripts/topo-node-sub-text-letter-spacing-test.mjs +115 -0
- package/scripts/topo-panel-count-fw-hover-test.mjs +105 -0
- package/scripts/topo-panel-count-letterspacing-test.mjs +89 -0
- package/scripts/topo-panel-stroke-hover-test.mjs +110 -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-content-opacity-test.mjs +81 -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-spoke-active-stroke-test.mjs +95 -0
- package/scripts/topo-spoke-idle-opacity-test.mjs +91 -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-vendor-suffix-hover-brighten-test.mjs +87 -0
- package/scripts/topo-zoom-icon-hover-scale-test.mjs +114 -0
- package/scripts/topo-zoom-level-hover-fw-test.mjs +95 -0
- package/.next/static/chunks/08fc_cz1nk7b9.js +0 -1
- package/.next/static/chunks/0aauz~36q5n2a.css +0 -2
- package/.next/static/chunks/0e0okm.affulg.js +0 -1
- package/.next/static/chunks/0s3vtwfo26_t6.js +0 -4
- /package/.next/static/{egukPz1ctU--4WnT2FpEU → i0drwZtW8h-M1ML2C5VZF}/_buildManifest.js +0 -0
- /package/.next/static/{egukPz1ctU--4WnT2FpEU → i0drwZtW8h-M1ML2C5VZF}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{egukPz1ctU--4WnT2FpEU → i0drwZtW8h-M1ML2C5VZF}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* Round 396 verification: extend the R395 opacity → 1.0 lift to
|
|
2
|
+
* the pinned state. Pin (sticky selection) was reading softer
|
|
3
|
+
* than hover (transient preview) at rest 0.85 cyber / 0.95 light
|
|
4
|
+
* despite being the stronger commitment. R396 unifies hover + pin
|
|
5
|
+
* at opacity 1.0 so the data-edge-badge-lifted='true' surface
|
|
6
|
+
* uniformly carries full alpha.
|
|
7
|
+
*
|
|
8
|
+
* Contract:
|
|
9
|
+
* - Source-file verification of the unified gate (the wire
|
|
10
|
+
* change is mechanical; SVG hover/pin dispatch in headless
|
|
11
|
+
* is fragile but the source is the canonical contract).
|
|
12
|
+
* - data-edge-badge-opacity-active='1' attr present on cold rest
|
|
13
|
+
* - data-edge-badge-opacity-rest === '0.85' (cyber) — R371 invariant
|
|
14
|
+
* - data-edge-badge-opacity-hover === '1' — R395 invariant
|
|
15
|
+
* - Cold rest opacity attr === '0.85' (cyber) — neither hover nor pin
|
|
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
22
|
+
|
|
23
|
+
const browser = await chromium.launch({ headless: true });
|
|
24
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
25
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
26
|
+
await ctx.addInitScript(() => {
|
|
27
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
28
|
+
});
|
|
29
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
30
|
+
const r = await route.fetch();
|
|
31
|
+
const b = await r.json();
|
|
32
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
33
|
+
const mk = (alias) => ({
|
|
34
|
+
alias, status: 'idle', 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('alpha'), mk('beta'), mk('gamma') ] } });
|
|
39
|
+
});
|
|
40
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
|
|
41
|
+
json: { messages: [
|
|
42
|
+
{ id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
|
|
43
|
+
{ id: 'm2', from_alias: 'alpha', to_alias: 'beta', content: 'pong', created_at: fresh, network_id: 'default' },
|
|
44
|
+
{ id: 'm3', from_alias: 'alpha', to_alias: 'beta', content: 'ack', created_at: fresh, network_id: 'default' },
|
|
45
|
+
] },
|
|
46
|
+
}));
|
|
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-edge-badge-stroke-width-rest]', { timeout: 15000 });
|
|
52
|
+
await page.waitForTimeout(500);
|
|
53
|
+
|
|
54
|
+
const restProbe = await page.evaluate(() => {
|
|
55
|
+
const badge = document.querySelector('[data-edge-badge-stroke-width-rest]');
|
|
56
|
+
return badge ? {
|
|
57
|
+
opacityAttr: badge.getAttribute('opacity'),
|
|
58
|
+
restData: badge.getAttribute('data-edge-badge-opacity-rest'),
|
|
59
|
+
hoverData: badge.getAttribute('data-edge-badge-opacity-hover'),
|
|
60
|
+
activeData: badge.getAttribute('data-edge-badge-opacity-active'),
|
|
61
|
+
lifted: badge.getAttribute('data-edge-badge-lifted'),
|
|
62
|
+
} : null;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
66
|
+
const sourceHasUnifiedGate = fileText.includes('opacity={(isHoveredEdge || isPinned) ? 1 : (isLight ? 0.95 : 0.85)}');
|
|
67
|
+
const sourceHasActiveData = fileText.includes('data-edge-badge-opacity-active="1"');
|
|
68
|
+
|
|
69
|
+
await browser.close();
|
|
70
|
+
|
|
71
|
+
const results = {
|
|
72
|
+
// Cold rest (no hover, no pin) — pre-R396 path preserved
|
|
73
|
+
rest_opacity_0_85: restProbe?.opacityAttr === '0.85',
|
|
74
|
+
rest_data_0_85: restProbe?.restData === '0.85',
|
|
75
|
+
rest_lifted_false: restProbe?.lifted === 'false',
|
|
76
|
+
// R395 attr preserved + R396 new attr
|
|
77
|
+
hover_data_1: restProbe?.hoverData === '1',
|
|
78
|
+
active_data_1: restProbe?.activeData === '1',
|
|
79
|
+
// R396 source-file wire (mechanical change)
|
|
80
|
+
source_unified_gate: sourceHasUnifiedGate,
|
|
81
|
+
source_active_attr: sourceHasActiveData,
|
|
82
|
+
};
|
|
83
|
+
const ok = Object.values(results).every(Boolean);
|
|
84
|
+
console.log(`${ok ? '✅' : '❌'} edge-badge opacity unifies hover + pin → 1.0:`, JSON.stringify(results),
|
|
85
|
+
'\n restProbe:', restProbe);
|
|
86
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* Round 367 verification: edge midpoint badge rest stroke-width
|
|
2
|
+
* 1 → 1.25. Sibling visual-weight bump (7th canvas anchor in the
|
|
3
|
+
* R287/R295/R359/R360/R361/R365/R367 family).
|
|
4
|
+
*
|
|
5
|
+
* Cold edge badges gain ~25 % stroke presence. Stays clear of the
|
|
6
|
+
* R51 overlap-test sentinel values (1.5 / 3 reserved for node
|
|
7
|
+
* strokes — the test selector is gated to g[data-node] ancestors
|
|
8
|
+
* so this edge-internal circle is invisible to that probe anyway).
|
|
9
|
+
* R188 transition list 'stroke-width 300ms ease-out' still smoothes
|
|
10
|
+
* the hot/pin flip — now 1.25 → 2 instead of 1 → 2.
|
|
11
|
+
*
|
|
12
|
+
* Contract (cyber, fixture with cold flow):
|
|
13
|
+
* - Edge badge circle stroke-width attr === '1.25'.
|
|
14
|
+
* - data-edge-badge-stroke-width-rest === '1.25'.
|
|
15
|
+
* - data-edge-badge-lifted === 'false' (rest, not hovered/pinned).
|
|
16
|
+
* - data-edge-badge-text-pin === 'false' (cold flow, link.count<10).
|
|
17
|
+
* - Inline transition contains 'stroke-width' (R188 preserved).
|
|
18
|
+
* - Pre-R367 invariants: r=9 (cold, R164), fill=pal.legendBox.fill,
|
|
19
|
+
* stroke=pal.flowEdge (cold), opacity 0.82 (cyber).
|
|
20
|
+
*/
|
|
21
|
+
import { chromium } from 'playwright';
|
|
22
|
+
import { readFileSync } from 'node:fs';
|
|
23
|
+
|
|
24
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
25
|
+
const browser = await chromium.launch({ headless: true });
|
|
26
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
27
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
28
|
+
await ctx.addInitScript(() => {
|
|
29
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
30
|
+
});
|
|
31
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
32
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
33
|
+
const r = await route.fetch();
|
|
34
|
+
const b = await r.json();
|
|
35
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
36
|
+
const mk = (alias) => ({
|
|
37
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
38
|
+
network_id: nid, project_dir: null,
|
|
39
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
40
|
+
});
|
|
41
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b') ] } });
|
|
42
|
+
});
|
|
43
|
+
// 1 message → 1 cold flow (count=1, well below R129 isHot=10).
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
|
|
46
|
+
{ id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
|
|
47
|
+
network_id: 'default', created_at: new Date(now - 5000).toISOString() },
|
|
48
|
+
] } }));
|
|
49
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
50
|
+
|
|
51
|
+
const page = await ctx.newPage();
|
|
52
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
53
|
+
await page.waitForSelector('[data-edge-badge-stroke-width-rest]', { timeout: 15000 });
|
|
54
|
+
await page.waitForTimeout(300);
|
|
55
|
+
|
|
56
|
+
const probe = await page.evaluate(() => {
|
|
57
|
+
// Probe the cold/rest edge badge circle (the parent of data-edge-badge-text
|
|
58
|
+
// is the <g> wrapping circle + text; we want the circle sibling).
|
|
59
|
+
const circle = document.querySelector('[data-edge-badge-stroke-width-rest]');
|
|
60
|
+
const cs = circle ? getComputedStyle(circle) : null;
|
|
61
|
+
// Find the text in the same group to check pin state.
|
|
62
|
+
const grp = circle?.parentElement;
|
|
63
|
+
const text = grp?.querySelector('[data-edge-badge-text]');
|
|
64
|
+
return {
|
|
65
|
+
strokeWidthAttr: circle?.getAttribute('stroke-width') ?? null,
|
|
66
|
+
strokeWidthData: circle?.getAttribute('data-edge-badge-stroke-width-rest') ?? null,
|
|
67
|
+
liftedAttr: circle?.getAttribute('data-edge-badge-lifted') ?? null,
|
|
68
|
+
rAttr: circle?.getAttribute('r') ?? null,
|
|
69
|
+
opacityAttr: circle?.getAttribute('opacity') ?? null,
|
|
70
|
+
transition: cs?.transition ?? null,
|
|
71
|
+
pinAttr: text?.getAttribute('data-edge-badge-text-pin') ?? null,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await browser.close();
|
|
76
|
+
|
|
77
|
+
const hasTrans = (s, prop) =>
|
|
78
|
+
new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
|
|
79
|
+
|
|
80
|
+
const results = {
|
|
81
|
+
stroke_width_1_25: probe.strokeWidthAttr === '1.25',
|
|
82
|
+
data_stroke_1_25: probe.strokeWidthData === '1.25',
|
|
83
|
+
lifted_false_rest: probe.liftedAttr === 'false',
|
|
84
|
+
r_9_cold: probe.rAttr === '9', // R164 cold
|
|
85
|
+
opacity_0_82_cyber: probe.opacityAttr === '0.82', // R251 cyber theme
|
|
86
|
+
trans_has_strokew: hasTrans(probe.transition, 'stroke-width'), // R188
|
|
87
|
+
cold_flow_not_hot: probe.pinAttr === 'false', // R220 isHot/isPinned both false
|
|
88
|
+
};
|
|
89
|
+
const ok = Object.values(results).every(Boolean);
|
|
90
|
+
console.log(`${ok ? '✅' : '❌'} edge badge stroke 1 → 1.25:`, JSON.stringify(results),
|
|
91
|
+
'\n probe:', probe);
|
|
92
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* Round 406 verification: edge freshness fade floor 0.35 → 0.40.
|
|
2
|
+
* Stale-state legibility lift family (6th anchor) — stale edges
|
|
3
|
+
* (>5min) now sit at 40% intensity instead of 35%. The decay
|
|
4
|
+
* curve shape (linear 1 - ageMs/300s) unchanged.
|
|
5
|
+
*
|
|
6
|
+
* Contract:
|
|
7
|
+
* - Source-file verification of `Math.max(0.40, 1 - ageMs / (5 * 60 * 1000))`
|
|
8
|
+
* - DOM probe: seed a STALE edge (ageMs > 5min) so fresh hits the
|
|
9
|
+
* floor; assert the visible flow path's opacity reflects the
|
|
10
|
+
* lifted floor (computed as 0.40 × edge_opacity_mul × base).
|
|
11
|
+
* - Compare against pre-R406 expectation (0.35 floor): the lifted
|
|
12
|
+
* floor produces ~14.3 % higher edge opacity on stale edges.
|
|
13
|
+
*/
|
|
14
|
+
import { chromium } from 'playwright';
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
|
|
17
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
18
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
19
|
+
// 10 minutes ago — well past the 5-min decay window; freshness hits floor.
|
|
20
|
+
const stale = new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
21
|
+
|
|
22
|
+
const browser = await chromium.launch({ headless: true });
|
|
23
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
24
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
25
|
+
await ctx.addInitScript(() => {
|
|
26
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } 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) => ({
|
|
33
|
+
alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
34
|
+
network_id: nid, project_dir: null,
|
|
35
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
36
|
+
});
|
|
37
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [ mk('alpha'), mk('beta'), mk('gamma') ] } });
|
|
38
|
+
});
|
|
39
|
+
// Seed a stale flow link 10 minutes ago — past the 5-min decay so floor binds.
|
|
40
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
|
|
41
|
+
json: { messages: [
|
|
42
|
+
{ id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'old', created_at: stale, network_id: 'default' },
|
|
43
|
+
] },
|
|
44
|
+
}));
|
|
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
|
+
await page.waitForSelector('[data-edge-visible]', { timeout: 15000 });
|
|
50
|
+
await page.waitForTimeout(600);
|
|
51
|
+
|
|
52
|
+
const probe = await page.evaluate(() => {
|
|
53
|
+
const edge = document.querySelector('[data-edge-visible]');
|
|
54
|
+
if (!edge) return null;
|
|
55
|
+
return {
|
|
56
|
+
opacityAttr: edge.getAttribute('opacity'),
|
|
57
|
+
edgeKey: edge.getAttribute('data-edge-visible'),
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
62
|
+
const sourceHasFloor040 = /Math\.max\(0\.40, 1 - ageMs/.test(fileText);
|
|
63
|
+
const sourceLacksFloor035 = !/Math\.max\(0\.35, 1 - ageMs/.test(fileText);
|
|
64
|
+
|
|
65
|
+
await browser.close();
|
|
66
|
+
|
|
67
|
+
// Pre-R406 expected: (0.28 cyber base) × 0.35 floor × 1 mul = 0.098
|
|
68
|
+
// Post-R406 expected: (0.28 cyber base) × 0.40 floor × 1 mul = 0.112
|
|
69
|
+
// On the visible flow path the wire is:
|
|
70
|
+
// opacity = Math.min(1, (isLight ? 0.22 : 0.28) * fresh * edgeOpacityMul)
|
|
71
|
+
// For an unhovered edge in a fresh sessions context (no other hover/active):
|
|
72
|
+
// edgeOpacityMul = !hoveredAlias && !activeGroup ? (hoveredActiveLinks ? 1.5 : 1) : 0.35
|
|
73
|
+
// = 1 in this probe → opacity ≈ 0.112 post-R406
|
|
74
|
+
// Browser may serialize this as "0.112" or "0.11" — accept either with
|
|
75
|
+
// a tolerance probe.
|
|
76
|
+
const opacityNum = probe ? parseFloat(probe.opacityAttr || '0') : 0;
|
|
77
|
+
const expectedPost = 0.28 * 0.40 * 1; // 0.112
|
|
78
|
+
const expectedPre = 0.28 * 0.35 * 1; // 0.098
|
|
79
|
+
const distToPost = Math.abs(opacityNum - expectedPost);
|
|
80
|
+
const distToPre = Math.abs(opacityNum - expectedPre);
|
|
81
|
+
|
|
82
|
+
const results = {
|
|
83
|
+
// Edge mounted from seeded stale flow
|
|
84
|
+
edge_mounted: !!probe,
|
|
85
|
+
edge_key_present: !!probe?.edgeKey,
|
|
86
|
+
// Post-R406 opacity is CLOSER to 0.112 than 0.098
|
|
87
|
+
opacity_close_to_post_r406: distToPost < 0.01,
|
|
88
|
+
// Opacity is FURTHER from the pre-R406 0.098 baseline
|
|
89
|
+
opacity_not_pre_r406: distToPre > distToPost,
|
|
90
|
+
// Source-file canonical wire
|
|
91
|
+
source_floor_040: sourceHasFloor040,
|
|
92
|
+
source_no_floor_035: sourceLacksFloor035,
|
|
93
|
+
};
|
|
94
|
+
const ok = Object.values(results).every(Boolean);
|
|
95
|
+
console.log(`${ok ? '✅' : '❌'} edge freshness floor 0.35 → 0.40:`, JSON.stringify(results),
|
|
96
|
+
'\n probe:', probe,
|
|
97
|
+
'\n opacityNum:', opacityNum, '/ expectedPost:', expectedPost, '/ distToPost:', distToPost.toFixed(4),
|
|
98
|
+
'\n expectedPre:', expectedPre, '/ distToPre:', distToPre.toFixed(4));
|
|
99
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* Round 422 verification: edge flow particle radius 4 → 4.5.
|
|
2
|
+
* Visual-weight bump family 15th anchor — particles riding along
|
|
3
|
+
* edge animateMotion paths get +0.5px lift (~27% area increase).
|
|
4
|
+
*
|
|
5
|
+
* Contract:
|
|
6
|
+
* - Seed a flow alpha→beta so an edge mounts → particle mounts
|
|
7
|
+
* - The <circle data-edge-particle> has:
|
|
8
|
+
* * r attr === '4.5'
|
|
9
|
+
* * data-edge-particle-radius === '4.5'
|
|
10
|
+
* - Pre-R422 invariants preserved:
|
|
11
|
+
* * R251 fill present (pal.flowParticle)
|
|
12
|
+
* * R252 transition list (fill 200ms + opacity 200ms)
|
|
13
|
+
*/
|
|
14
|
+
import { chromium } from 'playwright';
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
|
|
17
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
18
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
19
|
+
|
|
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
|
+
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: 'idle', 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('alpha'), mk('beta'), mk('gamma') ] } });
|
|
36
|
+
});
|
|
37
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
|
|
38
|
+
json: { messages: [
|
|
39
|
+
{ id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
|
|
40
|
+
] },
|
|
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-particle]', { timeout: 15000 });
|
|
47
|
+
await page.waitForTimeout(500);
|
|
48
|
+
|
|
49
|
+
const probe = await page.evaluate(() => {
|
|
50
|
+
const particle = document.querySelector('[data-edge-particle]');
|
|
51
|
+
if (!particle) return null;
|
|
52
|
+
return {
|
|
53
|
+
rAttr: particle.getAttribute('r'),
|
|
54
|
+
rData: particle.getAttribute('data-edge-particle-radius'),
|
|
55
|
+
fill: particle.getAttribute('fill'),
|
|
56
|
+
fillPresent: !!particle.getAttribute('fill'),
|
|
57
|
+
styleAttr: particle.getAttribute('style') || '',
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await browser.close();
|
|
62
|
+
|
|
63
|
+
const results = {
|
|
64
|
+
// R422 wire
|
|
65
|
+
r_attr_4_5: probe?.rAttr === '4.5',
|
|
66
|
+
r_data_4_5: probe?.rData === '4.5',
|
|
67
|
+
// R251 fill invariant
|
|
68
|
+
fill_present: probe?.fillPresent === true,
|
|
69
|
+
// R252 transition invariant
|
|
70
|
+
transition_fill: probe?.styleAttr.includes('fill') === true,
|
|
71
|
+
transition_opacity: probe?.styleAttr.includes('opacity') === true,
|
|
72
|
+
};
|
|
73
|
+
const ok = Object.values(results).every(Boolean);
|
|
74
|
+
console.log(`${ok ? '✅' : '❌'} edge flow particle radius 4 → 4.5:`, JSON.stringify(results),
|
|
75
|
+
'\n probe:', probe);
|
|
76
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -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);
|