@sleep2agi/agent-network-dashboard 0.5.1-preview.1 → 0.5.1-preview.100
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/0.pk4~tmpnb0_.js +1 -0
- package/.next/static/chunks/004_pmytjt5g..js +4 -0
- package/.next/static/chunks/017hq2-5l~_98.css +2 -0
- package/.next/static/chunks/0poutjg8.1a9n.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/TopoGraph.tsx +2126 -145
- package/package.json +1 -1
- package/scripts/topo-active-links-chip-hover-lift-test.mjs +93 -0
- package/scripts/topo-active-links-freshness-color-test.mjs +86 -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-letter-spacing-test.mjs +86 -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-endpoint-hover-sw-test.mjs +127 -0
- package/scripts/topo-edge-freshness-floor-test.mjs +99 -0
- package/scripts/topo-edge-particle-hover-r-test.mjs +113 -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-weight-test.mjs +75 -0
- package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
- package/scripts/topo-flow-rail-hover-sw-test.mjs +113 -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-group-label-letter-spacing-test.mjs +113 -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-hover-sw-test.mjs +121 -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-legend-row-letter-spacing-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-hover-test.mjs +109 -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-more-flows-hover-letterspacing-test.mjs +98 -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-rect-opacity-hover-test.mjs +109 -0
- package/scripts/topo-panel-stroke-hover-test.mjs +110 -0
- package/scripts/topo-panel-title-hover-letterspacing-test.mjs +97 -0
- package/scripts/topo-pin-intersection-unit-test.mjs +100 -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-letter-spacing-test.mjs +109 -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-status-ring-hover-sw-test.mjs +105 -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/scripts/topo-zoom-level-hover-letterspacing-test.mjs +91 -0
- package/.next/static/chunks/0aauz~36q5n2a.css +0 -2
- package/.next/static/chunks/0wratil0li9xw.js +0 -4
- package/.next/static/chunks/10paf4uz-_0g1.js +0 -1
- package/.next/static/chunks/12wcm9omjb_x6.js +0 -1
- /package/.next/static/{4t8OH-lNFieRsHEC2eZfD → gcwnb1fGF5WXL0umdpAPw}/_buildManifest.js +0 -0
- /package/.next/static/{4t8OH-lNFieRsHEC2eZfD → gcwnb1fGF5WXL0umdpAPw}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{4t8OH-lNFieRsHEC2eZfD → gcwnb1fGF5WXL0umdpAPw}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* Round 439 verification: edge flow particle radius hover lift —
|
|
2
|
+
* r 4.5 → 5.5 on (isHoveredEdge || isEndpointHoveredEdge). Continues
|
|
3
|
+
* the edge paint-layer parity arc (R436 visible path / R437 flow rail
|
|
4
|
+
* / R439 particle).
|
|
5
|
+
*
|
|
6
|
+
* Contract:
|
|
7
|
+
* - rest: every particle reports data-edge-particle-radius '4.5' +
|
|
8
|
+
* lifted='false'
|
|
9
|
+
* - hover one node alias: particles on edges incident to that node
|
|
10
|
+
* report radius '5.5' + lifted='true'
|
|
11
|
+
* - siblings unaffected
|
|
12
|
+
* - source-file probe confirms conditional + transition extension
|
|
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, status) => ({
|
|
31
|
+
alias, status, 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: [
|
|
36
|
+
mk('alpha', 'working'),
|
|
37
|
+
mk('beta', 'idle'),
|
|
38
|
+
mk('gamma', 'idle'),
|
|
39
|
+
] } });
|
|
40
|
+
});
|
|
41
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
|
|
42
|
+
json: { messages: [
|
|
43
|
+
{ id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
|
|
44
|
+
{ id: 'm2', from_alias: 'alpha', to_alias: 'gamma', content: 'pong', created_at: fresh, network_id: 'default' },
|
|
45
|
+
{ id: 'm3', from_alias: 'gamma', to_alias: 'beta', content: 'pang', created_at: fresh, network_id: 'default' },
|
|
46
|
+
] },
|
|
47
|
+
}));
|
|
48
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
49
|
+
|
|
50
|
+
const page = await ctx.newPage();
|
|
51
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
52
|
+
await page.waitForSelector('[data-edge-particle]', { timeout: 15000 });
|
|
53
|
+
await page.waitForTimeout(400);
|
|
54
|
+
|
|
55
|
+
const readAll = () => page.evaluate(() => {
|
|
56
|
+
const ps = [...document.querySelectorAll('[data-edge-particle]')];
|
|
57
|
+
return ps.map(p => ({
|
|
58
|
+
key: p.getAttribute('data-edge-particle'),
|
|
59
|
+
r_attr: p.getAttribute('r'),
|
|
60
|
+
r_data: parseFloat(p.getAttribute('data-edge-particle-radius') || '0'),
|
|
61
|
+
lifted: p.getAttribute('data-edge-particle-lifted'),
|
|
62
|
+
}));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const rest = await readAll();
|
|
66
|
+
|
|
67
|
+
let hover = null;
|
|
68
|
+
const box = await page.evaluate(() => {
|
|
69
|
+
const t = document.querySelector('[data-node-alias-text="alpha"]');
|
|
70
|
+
if (!t) return null;
|
|
71
|
+
const node = t.closest('[data-node]');
|
|
72
|
+
const target = node || t;
|
|
73
|
+
const b = target.getBoundingClientRect();
|
|
74
|
+
return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
|
|
75
|
+
});
|
|
76
|
+
if (box) {
|
|
77
|
+
await page.mouse.move(box.x, box.y);
|
|
78
|
+
await page.waitForTimeout(300);
|
|
79
|
+
hover = await readAll();
|
|
80
|
+
await page.mouse.move(0, 0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
84
|
+
const sourceWired = /r=\{\(isHoveredEdge \|\| isEndpointHoveredEdge\) \? 5\.5 : 4\.5\}/.test(fileText);
|
|
85
|
+
const sourceTransition = /transition: 'fill 200ms ease-out, opacity 200ms ease-out, r 200ms ease-out'/.test(fileText);
|
|
86
|
+
|
|
87
|
+
await browser.close();
|
|
88
|
+
|
|
89
|
+
const isAlphaKey = (k) => /alpha/i.test(k);
|
|
90
|
+
const hoverByKey = new Map((hover || []).map(h => [h.key, h]));
|
|
91
|
+
const alphaEntries = [...hoverByKey.entries()].filter(([k]) => isAlphaKey(k));
|
|
92
|
+
const nonAlphaEntries = [...hoverByKey.entries()].filter(([k]) => !isAlphaKey(k));
|
|
93
|
+
|
|
94
|
+
const restAllR_4_5 = rest.every(r => r.r_data === 4.5);
|
|
95
|
+
const restNoneLifted = rest.every(r => r.lifted === 'false');
|
|
96
|
+
const alphaAllR_5_5 = alphaEntries.length > 0 && alphaEntries.every(([, h]) => h.r_data === 5.5 && h.lifted === 'true');
|
|
97
|
+
const nonAlphaRestR = nonAlphaEntries.every(([, h]) => h.r_data === 4.5 && h.lifted === 'false');
|
|
98
|
+
|
|
99
|
+
const results = {
|
|
100
|
+
rest_particle_count_ge_2: rest.length >= 2,
|
|
101
|
+
rest_all_r_4_5: restAllR_4_5,
|
|
102
|
+
rest_none_lifted: restNoneLifted,
|
|
103
|
+
hover_alpha_count_ge_2: alphaEntries.length >= 2,
|
|
104
|
+
hover_alpha_all_lifted_5_5: alphaAllR_5_5,
|
|
105
|
+
hover_non_alpha_unaffected: nonAlphaRestR,
|
|
106
|
+
source_r_wired: sourceWired,
|
|
107
|
+
source_transition_wired: sourceTransition,
|
|
108
|
+
};
|
|
109
|
+
const ok = Object.values(results).every(Boolean);
|
|
110
|
+
console.log(`${ok ? '✅' : '❌'} edge-particle r hover lift:`, JSON.stringify(results),
|
|
111
|
+
'\n rest:', JSON.stringify(rest),
|
|
112
|
+
'\n hover:', JSON.stringify(hover));
|
|
113
|
+
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);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* Round 412 verification: filter pin pill VALUE picks up fw=600
|
|
2
|
+
* (font-semibold). Extends chip-internal-hierarchy arc (11th
|
|
3
|
+
* anchor) — pill now reads with proper data-tier emphasis around
|
|
4
|
+
* its R355 opacity-70 prefix/suffix.
|
|
5
|
+
*
|
|
6
|
+
* Contract:
|
|
7
|
+
* - Source-file verification of 4 occurrences of
|
|
8
|
+
* `<span className="font-semibold" data-filter-value>`
|
|
9
|
+
* - Runtime: pin a status filter, probe the data-filter-value
|
|
10
|
+
* span on the mounted pill — className includes font-semibold
|
|
11
|
+
* + textContent matches the pinned value.
|
|
12
|
+
* - Pre-R412 invariants on the pill:
|
|
13
|
+
* * data-filter-prefix span still opacity-70
|
|
14
|
+
* * data-filter-pill-count still opacity-70 tabular-nums
|
|
15
|
+
* * R397 hover-translate-y-px className still present
|
|
16
|
+
* * R332/R355/R356 className tokens preserved
|
|
17
|
+
*/
|
|
18
|
+
import { chromium } from 'playwright';
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
|
|
21
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
22
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
23
|
+
|
|
24
|
+
const browser = await chromium.launch({ headless: true });
|
|
25
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
26
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
27
|
+
await ctx.addInitScript(() => {
|
|
28
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
29
|
+
});
|
|
30
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
31
|
+
const r = await route.fetch();
|
|
32
|
+
const b = await r.json();
|
|
33
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
34
|
+
const mk = (alias, status) => ({
|
|
35
|
+
alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
36
|
+
network_id: nid, project_dir: null,
|
|
37
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
38
|
+
});
|
|
39
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
40
|
+
mk('alpha', 'working'),
|
|
41
|
+
mk('beta', 'idle'),
|
|
42
|
+
] } });
|
|
43
|
+
});
|
|
44
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
45
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
46
|
+
|
|
47
|
+
const page = await ctx.newPage();
|
|
48
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
49
|
+
await page.waitForSelector('g[data-node]', { timeout: 15000 });
|
|
50
|
+
|
|
51
|
+
// Click legend status row to pin filter → mounts the filter pill
|
|
52
|
+
await page.click('[data-legend-status="working"]');
|
|
53
|
+
await page.waitForSelector('[data-filter-value]', { timeout: 5000 });
|
|
54
|
+
|
|
55
|
+
const probe = await page.evaluate(() => {
|
|
56
|
+
const valueSpan = document.querySelector('[data-filter-value]');
|
|
57
|
+
if (!valueSpan) return null;
|
|
58
|
+
const pill = valueSpan.closest('[data-topo-filter-pill-hover-lift="true"]');
|
|
59
|
+
return {
|
|
60
|
+
valueText: valueSpan.textContent,
|
|
61
|
+
valueClass: valueSpan.className,
|
|
62
|
+
valueFwComputed: getComputedStyle(valueSpan).fontWeight,
|
|
63
|
+
pillClass: pill?.className ?? null,
|
|
64
|
+
pillActiveFilter: pill?.getAttribute('data-active-filter') ?? null,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
69
|
+
const valueOccurrences = (fileText.match(/<span className="font-semibold" data-filter-value>/g) || []).length;
|
|
70
|
+
|
|
71
|
+
await browser.close();
|
|
72
|
+
|
|
73
|
+
const results = {
|
|
74
|
+
// Runtime: value span has font-semibold
|
|
75
|
+
value_has_font_semibold: probe?.valueClass.includes('font-semibold') === true,
|
|
76
|
+
value_text_working: probe?.valueText === 'working',
|
|
77
|
+
value_fw_600: probe?.valueFwComputed === '600',
|
|
78
|
+
// Pill mounted with R397 hover-lift
|
|
79
|
+
pill_has_hover_lift: probe?.pillClass?.includes('hover:-translate-y-px') === true,
|
|
80
|
+
pill_active_filter: probe?.pillActiveFilter === 'status',
|
|
81
|
+
// Source-file: 4 occurrences (status + group + vendor + edge filters)
|
|
82
|
+
source_4_value_spans: valueOccurrences === 4,
|
|
83
|
+
};
|
|
84
|
+
const ok = Object.values(results).every(Boolean);
|
|
85
|
+
console.log(`${ok ? '✅' : '❌'} filter pin pill VALUE fw 500 → 600:`, JSON.stringify(results),
|
|
86
|
+
'\n probe:', probe,
|
|
87
|
+
'\n source occurrences:', valueOccurrences);
|
|
88
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* Round 343 verification: filter pin pills (status / group / vendor /
|
|
2
|
+
* edge — 4 surfaces sharing className) gain `font-medium`. Aligns
|
|
3
|
+
* chip-row data-weight tier with working/online chips (which already
|
|
4
|
+
* carry font-medium per R313).
|
|
5
|
+
*
|
|
6
|
+
* Contract:
|
|
7
|
+
* - Active filter pill ([data-active-filter] of any kind) has
|
|
8
|
+
* className containing 'font-medium'.
|
|
9
|
+
* - Computed font-weight reads 500.
|
|
10
|
+
* - Prefix opacity-0.7 (R335) + R342 freshness wrapper gray-400
|
|
11
|
+
* regressions intact.
|
|
12
|
+
* - R317/R318/R294 chrome + pulse regressions intact.
|
|
13
|
+
*
|
|
14
|
+
* Fixture: pin status (working) so the filter pin pill mounts.
|
|
15
|
+
*/
|
|
16
|
+
import { chromium } from 'playwright';
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
|
|
19
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
20
|
+
const browser = await chromium.launch({ headless: true });
|
|
21
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
22
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
23
|
+
await ctx.addInitScript(() => {
|
|
24
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
25
|
+
});
|
|
26
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
27
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
28
|
+
const r = await route.fetch();
|
|
29
|
+
const b = await r.json();
|
|
30
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
31
|
+
const mk = (alias) => ({
|
|
32
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
33
|
+
network_id: nid, project_dir: null,
|
|
34
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
35
|
+
});
|
|
36
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [ mk('alpha'), mk('beta'), mk('gamma') ] } });
|
|
37
|
+
});
|
|
38
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
39
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
40
|
+
|
|
41
|
+
const page = await ctx.newPage();
|
|
42
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
43
|
+
await page.waitForSelector('[data-working-chip]', { timeout: 15000 });
|
|
44
|
+
await page.waitForTimeout(200);
|
|
45
|
+
await page.click('[data-working-chip]', { delay: 50 }).catch(() => {});
|
|
46
|
+
await page.waitForTimeout(300);
|
|
47
|
+
|
|
48
|
+
const probe = await page.evaluate(() => {
|
|
49
|
+
const filterPill = document.querySelector('[data-active-filter]');
|
|
50
|
+
const prefix = document.querySelector('[data-filter-prefix]');
|
|
51
|
+
return {
|
|
52
|
+
pillClass: filterPill?.className ?? '',
|
|
53
|
+
pillFw: filterPill ? getComputedStyle(filterPill).fontWeight : null,
|
|
54
|
+
prefixClass: prefix?.className ?? '',
|
|
55
|
+
layoutInactiveCls: document.querySelector('[data-topo-chrome-layout="grid"]')?.className ?? '',
|
|
56
|
+
layoutActiveCls: document.querySelector('[data-topo-chrome-layout="ring"]')?.className ?? '',
|
|
57
|
+
pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
await browser.close();
|
|
61
|
+
|
|
62
|
+
const results = {
|
|
63
|
+
pill_has_font_medium: /font-medium/.test(probe.pillClass),
|
|
64
|
+
pill_computed_fw_500: String(probe.pillFw) === '500',
|
|
65
|
+
// R335 regression — prefix still has opacity-70.
|
|
66
|
+
r335_prefix_opacity70: /opacity-70/.test(probe.prefixClass),
|
|
67
|
+
// R317 / R318 chrome regression.
|
|
68
|
+
r317_inactive_gray_400: probe.layoutInactiveCls.includes('text-gray-400'),
|
|
69
|
+
r318_active_font_medium: probe.layoutActiveCls.includes('font-medium'),
|
|
70
|
+
r294_pulse_absent: probe.pulseCount === 0,
|
|
71
|
+
};
|
|
72
|
+
const ok = Object.values(results).every(Boolean);
|
|
73
|
+
console.log(`${ok ? '✅' : '❌'} filter pill font-medium:`, JSON.stringify(results),
|
|
74
|
+
'\n pill fw:', probe.pillFw);
|
|
75
|
+
process.exit(ok ? 0 : 1);
|