@sleep2agi/agent-network-dashboard 0.5.1-preview.95 → 0.5.1-preview.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +4 -4
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/admin.html +1 -1
- package/.next/server/app/admin.rsc +1 -1
- package/.next/server/app/admin.segments/_full.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_index.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +2 -2
- package/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/server/app/login.segments/_index.segment.rsc +1 -1
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/server/app/logs.html +1 -1
- package/.next/server/app/logs.rsc +1 -1
- package/.next/server/app/logs.segments/_full.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_index.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
- package/.next/server/app/messages.html +1 -1
- package/.next/server/app/messages.rsc +1 -1
- package/.next/server/app/messages.segments/_full.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_index.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
- package/.next/server/app/node.html +1 -1
- package/.next/server/app/node.rsc +1 -1
- package/.next/server/app/node.segments/_full.segment.rsc +1 -1
- package/.next/server/app/node.segments/_head.segment.rsc +1 -1
- package/.next/server/app/node.segments/_index.segment.rsc +1 -1
- package/.next/server/app/node.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/node.segments/node.segment.rsc +1 -1
- package/.next/server/app/nodes.html +1 -1
- package/.next/server/app/nodes.rsc +1 -1
- package/.next/server/app/nodes.segments/_full.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_index.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +1 -1
- package/.next/server/app/server-logs.rsc +1 -1
- package/.next/server/app/server-logs.segments/_full.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_index.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
- package/.next/server/app/settings/networks.html +1 -1
- package/.next/server/app/settings/networks.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +1 -1
- package/.next/server/app/settings/tokens.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +2 -2
- package/.next/server/app/settings.segments/_full.segment.rsc +2 -2
- package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/server/app/tasks.html +1 -1
- package/.next/server/app/tasks.rsc +1 -1
- package/.next/server/app/tasks.segments/_full.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_index.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +2 -2
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/{0hduheo6~ui~q.js → 085uyvy61dd-4.js} +1 -1
- package/.next/static/chunks/{0d~gzizx7atsl.js → 0a1v54cnxx83..js} +1 -1
- package/.next/static/chunks/0ap~mvvqp~v-_.js +4 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/TopoGraph.tsx +45 -2
- package/package.json +1 -1
- package/scripts/topo-edge-endpoint-hover-sw-test.mjs +127 -0
- package/scripts/topo-hub-spoke-hover-sw-test.mjs +121 -0
- package/.next/static/chunks/0wecjbif70gvd.js +0 -4
- /package/.next/static/{G-QBT67axaIRmw0dasJKn → 44j_WBni902eK_p3ToCrp}/_buildManifest.js +0 -0
- /package/.next/static/{G-QBT67axaIRmw0dasJKn → 44j_WBni902eK_p3ToCrp}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{G-QBT67axaIRmw0dasJKn → 44j_WBni902eK_p3ToCrp}/_ssgManifest.js +0 -0
|
@@ -4123,13 +4123,33 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4123
4123
|
const spokeOpacity = isActiveSpoke
|
|
4124
4124
|
? (isHoveredSpoke ? 0.95 : 0.80)
|
|
4125
4125
|
: (isHoveredSpoke ? 0.70 : 0.50);
|
|
4126
|
+
/* Round 435 / Loop: hub-spoke stroke-width hover lift —
|
|
4127
|
+
sibling to R430 opacity hover at the same surface. When
|
|
4128
|
+
hoveredAlias matches, BOTH opacity AND stroke-width
|
|
4129
|
+
lift on the matched spoke so the eye registers a
|
|
4130
|
+
2-axis "this node's spoke" gesture (paint + geometry).
|
|
4131
|
+
idle 1.00 → 1.25 (Δ +0.25, +25%)
|
|
4132
|
+
active 2.25 → 2.50 (Δ +0.25, +11%)
|
|
4133
|
+
Same +0.25 absolute delta keeps the idle/active visual
|
|
4134
|
+
progression consistent — at rest sw ratio 2.25:1 = 2.25,
|
|
4135
|
+
on hover 2.50:1.25 = 2.0; both still clearly two-tier.
|
|
4136
|
+
R241 transition list already covers stroke-width 250ms
|
|
4137
|
+
so the lift eases for free.
|
|
4138
|
+
R51 sentinel-safe: spoke is canvas <path>, not
|
|
4139
|
+
data-node <circle> (the sentinel selector is gated to
|
|
4140
|
+
g[data-node] descendants). 1.25 and 2.5 are not in the
|
|
4141
|
+
reserved {1.5, 3} set so the overlap-test sentinel
|
|
4142
|
+
attribute selector wouldn't match either way. */
|
|
4143
|
+
const spokeStrokeWidth = isActiveSpoke
|
|
4144
|
+
? (isHoveredSpoke ? 2.5 : 2.25)
|
|
4145
|
+
: (isHoveredSpoke ? 1.25 : 1);
|
|
4126
4146
|
return (
|
|
4127
4147
|
<path
|
|
4128
4148
|
key={`hub-${session.alias}`}
|
|
4129
4149
|
d={path}
|
|
4130
4150
|
fill="none"
|
|
4131
4151
|
stroke={isActiveSpoke ? pal.spokeStroke.active : pal.spokeStroke.idle}
|
|
4132
|
-
strokeWidth={
|
|
4152
|
+
strokeWidth={spokeStrokeWidth}
|
|
4133
4153
|
strokeDasharray={isActiveSpoke ? 'none' : '6 14'}
|
|
4134
4154
|
strokeLinecap="round"
|
|
4135
4155
|
opacity={spokeOpacity}
|
|
@@ -4139,6 +4159,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4139
4159
|
data-topo-hub-spoke-active={isActiveSpoke ? 'true' : 'false'}
|
|
4140
4160
|
data-topo-hub-spoke-hovered={isHoveredSpoke ? 'true' : 'false'}
|
|
4141
4161
|
data-topo-hub-spoke-opacity={spokeOpacity}
|
|
4162
|
+
data-topo-hub-spoke-stroke-width={spokeStrokeWidth}
|
|
4142
4163
|
data-topo-hub-spoke-stroke-width-active="2.25"
|
|
4143
4164
|
data-topo-hub-spoke-linecap="round"
|
|
4144
4165
|
style={{
|
|
@@ -4736,7 +4757,27 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4736
4757
|
// tracks it even at low message counts (width was 3 → 4.5 on
|
|
4737
4758
|
// hover). 1.4× is enough to read as "lifted" without
|
|
4738
4759
|
// breaching the 16-px hitbox bound.
|
|
4739
|
-
|
|
4760
|
+
// Round 436 / Loop: extend the thickening to the
|
|
4761
|
+
// "hovered-endpoint" case — when hoveredAlias matches one
|
|
4762
|
+
// of this edge's endpoints, lift width by 1.15× (capped at
|
|
4763
|
+
// 8 px to stay clear of the 16-px hitbox). Pre-R436 the
|
|
4764
|
+
// edgeOpacityMul=1.7 (line 4703) lifted the matched edge's
|
|
4765
|
+
// OPACITY when an endpoint was hovered but the stroke-WIDTH
|
|
4766
|
+
// stayed at base — so the edge faded brighter without
|
|
4767
|
+
// thickening, leaving the paint+geometry axes mismatched.
|
|
4768
|
+
// Mirror of R430/R435 hub-spoke pattern (opacity + stroke-
|
|
4769
|
+
// width co-lift on hoveredAlias); R436 brings the same
|
|
4770
|
+
// dual-axis "this node's link" gesture to the edge scope.
|
|
4771
|
+
// 1.15× is subtler than isHoveredEdge=1.4× because
|
|
4772
|
+
// endpoint-hover lifts MANY edges at once (every edge
|
|
4773
|
+
// incident on the hovered node) while edge-hover lifts ONE
|
|
4774
|
+
// — the gesture should read as "highlighted" not "loud".
|
|
4775
|
+
// R166 stroke-width 300ms transition already in the
|
|
4776
|
+
// visible-path style list so the lift eases for free.
|
|
4777
|
+
const isEndpointHoveredEdge = !!hoveredAlias && (link.from === hoveredAlias || link.to === hoveredAlias);
|
|
4778
|
+
const renderWidth = isHoveredEdge ? Math.min(width * 1.4, 10)
|
|
4779
|
+
: isEndpointHoveredEdge ? Math.min(width * 1.15, 8)
|
|
4780
|
+
: width;
|
|
4740
4781
|
return (
|
|
4741
4782
|
<g
|
|
4742
4783
|
key={link.key}
|
|
@@ -4851,6 +4892,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4851
4892
|
markerEnd={`url(#${arrowId})`}
|
|
4852
4893
|
data-edge-visible={link.key}
|
|
4853
4894
|
data-edge-visible-linecap="round"
|
|
4895
|
+
data-edge-visible-endpoint-hovered={isEndpointHoveredEdge ? 'true' : 'false'}
|
|
4896
|
+
data-edge-visible-stroke-width={renderWidth}
|
|
4854
4897
|
style={{
|
|
4855
4898
|
pointerEvents: 'none',
|
|
4856
4899
|
transition: 'opacity 300ms ease-out, stroke-width 300ms ease-out, stroke 300ms ease-out',
|
package/package.json
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/* Round 436 verification: edge visible-path stroke-width lift on
|
|
2
|
+
* hoveredAlias endpoint. Mirrors R430/R435 hub-spoke pattern at edge
|
|
3
|
+
* scope — when hoveredAlias matches one of the edge's endpoints, the
|
|
4
|
+
* visible path thickens by 1.15× alongside the existing edgeOpacityMul
|
|
5
|
+
* 1.7× lift (now BOTH paint and geometry lift on endpoint hover).
|
|
6
|
+
*
|
|
7
|
+
* Contract:
|
|
8
|
+
* - rest: no edge reports data-edge-visible-endpoint-hovered='true'
|
|
9
|
+
* - hover one node alias: exactly the edges incident on that alias
|
|
10
|
+
* report endpoint-hovered='true' AND their renderWidth > rest base
|
|
11
|
+
* - siblings unaffected
|
|
12
|
+
* - source-file probe confirms the new conditional + endpoint cap
|
|
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
|
+
// alpha is incident on both edges; gamma↔beta is the non-incident edge
|
|
44
|
+
{ id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
|
|
45
|
+
{ id: 'm2', from_alias: 'alpha', to_alias: 'gamma', content: 'pong', created_at: fresh, network_id: 'default' },
|
|
46
|
+
{ id: 'm3', from_alias: 'gamma', to_alias: 'beta', content: 'pang', created_at: fresh, network_id: 'default' },
|
|
47
|
+
] },
|
|
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-visible]', { timeout: 15000 });
|
|
54
|
+
await page.waitForTimeout(400);
|
|
55
|
+
|
|
56
|
+
const readAll = () => page.evaluate(() => {
|
|
57
|
+
const ps = [...document.querySelectorAll('[data-edge-visible]')];
|
|
58
|
+
return ps.map(p => ({
|
|
59
|
+
key: p.getAttribute('data-edge-visible'),
|
|
60
|
+
endHover: p.getAttribute('data-edge-visible-endpoint-hovered'),
|
|
61
|
+
sw: parseFloat(p.getAttribute('data-edge-visible-stroke-width') || '0'),
|
|
62
|
+
}));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const rest = await readAll();
|
|
66
|
+
|
|
67
|
+
// Hover alpha
|
|
68
|
+
let hover = null;
|
|
69
|
+
const box = await page.evaluate(() => {
|
|
70
|
+
const t = document.querySelector('[data-node-alias-text="alpha"]');
|
|
71
|
+
if (!t) return null;
|
|
72
|
+
const node = t.closest('[data-node]');
|
|
73
|
+
const target = node || t;
|
|
74
|
+
const b = target.getBoundingClientRect();
|
|
75
|
+
return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
|
|
76
|
+
});
|
|
77
|
+
if (box) {
|
|
78
|
+
await page.mouse.move(box.x, box.y);
|
|
79
|
+
await page.waitForTimeout(300);
|
|
80
|
+
hover = await readAll();
|
|
81
|
+
await page.mouse.move(0, 0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
85
|
+
const sourceWired = /isEndpointHoveredEdge\s*\?\s*Math\.min\(width \* 1\.15,\s*8\)\s*\n?\s*:\s*width/.test(fileText);
|
|
86
|
+
const sourceIsEndpoint = /const isEndpointHoveredEdge = !!hoveredAlias && \(link\.from === hoveredAlias \|\| link\.to === hoveredAlias\)/.test(fileText);
|
|
87
|
+
|
|
88
|
+
await browser.close();
|
|
89
|
+
|
|
90
|
+
// rest: no edge should report endpoint-hover=true
|
|
91
|
+
const restNoneEndHover = rest.every(r => r.endHover === 'false');
|
|
92
|
+
|
|
93
|
+
// hover state: edges where alpha is an endpoint should report endpoint=true.
|
|
94
|
+
// fixture edges: alpha↔beta, alpha↔gamma, gamma↔beta (deduped keys).
|
|
95
|
+
const hoverByKey = new Map((hover || []).map(h => [h.key, h]));
|
|
96
|
+
const restByKey = new Map((rest || []).map(r => [r.key, r]));
|
|
97
|
+
|
|
98
|
+
// flow link keys are e.g. "alpha->beta" or "alpha-beta" — we don't know the
|
|
99
|
+
// exact format. Detect alpha-incident edges by key substring.
|
|
100
|
+
const isAlphaEdge = (key) => key && /alpha/i.test(key);
|
|
101
|
+
const alphaEdges = [...hoverByKey.entries()].filter(([k]) => isAlphaEdge(k));
|
|
102
|
+
const nonAlphaEdges = [...hoverByKey.entries()].filter(([k]) => !isAlphaEdge(k));
|
|
103
|
+
|
|
104
|
+
const alphaAllEndHover = alphaEdges.length > 0 && alphaEdges.every(([, h]) => h.endHover === 'true');
|
|
105
|
+
const nonAlphaNoEndHover = nonAlphaEdges.every(([, h]) => h.endHover === 'false');
|
|
106
|
+
|
|
107
|
+
// width lifted for alpha edges (compare to rest sw for same key)
|
|
108
|
+
const alphaSwLifted = alphaEdges.every(([k, h]) => {
|
|
109
|
+
const r = restByKey.get(k);
|
|
110
|
+
return r && h.sw > r.sw;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const results = {
|
|
114
|
+
rest_edge_count_ge_2: rest.length >= 2,
|
|
115
|
+
rest_no_endpoint_hover: restNoneEndHover,
|
|
116
|
+
hover_alpha_edges_present: alphaEdges.length >= 1,
|
|
117
|
+
hover_alpha_all_endHover: alphaAllEndHover,
|
|
118
|
+
hover_alpha_sw_lifted: alphaSwLifted,
|
|
119
|
+
hover_non_alpha_unaffected: nonAlphaNoEndHover,
|
|
120
|
+
source_renderWidth_wired: sourceWired,
|
|
121
|
+
source_isEndpoint_wired: sourceIsEndpoint,
|
|
122
|
+
};
|
|
123
|
+
const ok = Object.values(results).every(Boolean);
|
|
124
|
+
console.log(`${ok ? '✅' : '❌'} edge endpoint-hover sw lift:`, JSON.stringify(results),
|
|
125
|
+
'\n rest:', JSON.stringify(rest),
|
|
126
|
+
'\n hover:', JSON.stringify(hover));
|
|
127
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/* Round 435 verification: hub-spoke stroke-width hover lift —
|
|
2
|
+
* sibling to R430 opacity hover at the same surface. 2-axis hover
|
|
3
|
+
* signature now: paint (opacity R430) + geometry (stroke-width R435).
|
|
4
|
+
*
|
|
5
|
+
* Rest tiers:
|
|
6
|
+
* idle sw 1.00 / α 0.50
|
|
7
|
+
* active sw 2.25 / α 0.80
|
|
8
|
+
* Hover tiers (hoveredAlias === session.alias):
|
|
9
|
+
* idle sw 1.25 / α 0.70
|
|
10
|
+
* active sw 2.50 / α 0.95
|
|
11
|
+
*
|
|
12
|
+
* Contract:
|
|
13
|
+
* - rest: all idle spokes report data-topo-hub-spoke-stroke-width '1'
|
|
14
|
+
* - hover one node: exactly one spoke flips to hover with sw '1.25'
|
|
15
|
+
* AND opacity '0.7' (both R430+R435 axes lift together)
|
|
16
|
+
* - siblings stay rest sw '1' / opacity '0.5'
|
|
17
|
+
* - source-file probe confirms wired stroke-width conditional
|
|
18
|
+
*/
|
|
19
|
+
import { chromium } from 'playwright';
|
|
20
|
+
import { readFileSync } from 'node:fs';
|
|
21
|
+
|
|
22
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
23
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
24
|
+
|
|
25
|
+
const browser = await chromium.launch({ headless: true });
|
|
26
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
27
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
28
|
+
await ctx.addInitScript(() => {
|
|
29
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
30
|
+
});
|
|
31
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
32
|
+
const r = await route.fetch();
|
|
33
|
+
const b = await r.json();
|
|
34
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
35
|
+
const mk = (alias, status) => ({
|
|
36
|
+
alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
37
|
+
network_id: nid, project_dir: null,
|
|
38
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
39
|
+
});
|
|
40
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
41
|
+
mk('alpha', 'working'),
|
|
42
|
+
mk('beta', 'idle'),
|
|
43
|
+
mk('gamma', 'idle'),
|
|
44
|
+
] } });
|
|
45
|
+
});
|
|
46
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
47
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
48
|
+
|
|
49
|
+
const page = await ctx.newPage();
|
|
50
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
51
|
+
await page.waitForSelector('[data-topo-hub-spoke-active]', { timeout: 15000 });
|
|
52
|
+
await page.waitForTimeout(400);
|
|
53
|
+
|
|
54
|
+
const readAll = () => page.evaluate(() => {
|
|
55
|
+
const paths = [...document.querySelectorAll('[data-topo-hub-spoke-active]')];
|
|
56
|
+
return paths.map((p, i) => ({
|
|
57
|
+
idx: i,
|
|
58
|
+
active: p.getAttribute('data-topo-hub-spoke-active'),
|
|
59
|
+
hovered: p.getAttribute('data-topo-hub-spoke-hovered'),
|
|
60
|
+
opacity: p.getAttribute('data-topo-hub-spoke-opacity'),
|
|
61
|
+
sw: p.getAttribute('data-topo-hub-spoke-stroke-width'),
|
|
62
|
+
sw_a: p.getAttribute('stroke-width'),
|
|
63
|
+
}));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const rest = await readAll();
|
|
67
|
+
|
|
68
|
+
// Hover the first node's <g> group
|
|
69
|
+
const firstAlias = await page.evaluate(() => {
|
|
70
|
+
const t = document.querySelector('[data-node-alias-text]');
|
|
71
|
+
return t?.getAttribute('data-node-alias-text');
|
|
72
|
+
});
|
|
73
|
+
let hover = null;
|
|
74
|
+
if (firstAlias) {
|
|
75
|
+
const box = await page.evaluate((alias) => {
|
|
76
|
+
const t = document.querySelector(`[data-node-alias-text="${alias}"]`);
|
|
77
|
+
if (!t) return null;
|
|
78
|
+
const node = t.closest('[data-node]');
|
|
79
|
+
const target = node || t;
|
|
80
|
+
const b = target.getBoundingClientRect();
|
|
81
|
+
return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
|
|
82
|
+
}, firstAlias);
|
|
83
|
+
if (box) {
|
|
84
|
+
await page.mouse.move(box.x, box.y);
|
|
85
|
+
await page.waitForTimeout(300);
|
|
86
|
+
hover = await readAll();
|
|
87
|
+
await page.mouse.move(0, 0);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const fileText = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
|
|
92
|
+
const sourceWired = /const spokeStrokeWidth = isActiveSpoke\s*\n\s*\?\s*\(isHoveredSpoke\s*\?\s*2\.5\s*:\s*2\.25\)\s*\n\s*:\s*\(isHoveredSpoke\s*\?\s*1\.25\s*:\s*1\)/.test(fileText);
|
|
93
|
+
|
|
94
|
+
await browser.close();
|
|
95
|
+
|
|
96
|
+
const restAllIdle = rest.length > 0 && rest.every(r => r.active === 'false');
|
|
97
|
+
const restAllSw1 = rest.every(r => r.sw === '1');
|
|
98
|
+
const restAllOpacity = rest.every(r => r.opacity === '0.5');
|
|
99
|
+
const restNoHover = rest.every(r => r.hovered === 'false');
|
|
100
|
+
const hoveredCount = hover ? hover.filter(s => s.hovered === 'true').length : -1;
|
|
101
|
+
const hoveredEntry = hover?.find(s => s.hovered === 'true');
|
|
102
|
+
const hoverSw_1_25 = hoveredEntry?.sw === '1.25';
|
|
103
|
+
const hoverOpacity_0_7 = hoveredEntry?.opacity === '0.7';
|
|
104
|
+
const othersRestSw = hover ? hover.filter(s => s.hovered === 'false').every(s => s.sw === '1') : false;
|
|
105
|
+
|
|
106
|
+
const results = {
|
|
107
|
+
rest_spoke_count_ge_3: rest.length >= 3,
|
|
108
|
+
rest_all_idle: restAllIdle,
|
|
109
|
+
rest_all_sw_1: restAllSw1,
|
|
110
|
+
rest_all_opacity_0_5: restAllOpacity,
|
|
111
|
+
rest_no_hover: restNoHover,
|
|
112
|
+
hover_exactly_one_match: hoveredCount === 1,
|
|
113
|
+
hover_target_sw_1_25: hoverSw_1_25,
|
|
114
|
+
hover_target_opacity_0_7: hoverOpacity_0_7, /* R430 parity */
|
|
115
|
+
hover_others_stay_sw_1: othersRestSw,
|
|
116
|
+
source_strokeWidth_3tier: sourceWired,
|
|
117
|
+
};
|
|
118
|
+
const ok = Object.values(results).every(Boolean);
|
|
119
|
+
console.log(`${ok ? '✅' : '❌'} hub-spoke hover sw (R435):`, JSON.stringify(results),
|
|
120
|
+
'\n hover target:', JSON.stringify(hoveredEntry));
|
|
121
|
+
process.exit(ok ? 0 : 1);
|