@sleep2agi/agent-network-dashboard 0.4.6-preview.8 → 0.4.6-preview.81
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/app-path-routes-manifest.json +1 -0
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +69 -55
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +6 -0
- 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 +12 -12
- package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
- package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
- package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/api/hub/servers/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/hub/servers/route/build-manifest.json +9 -0
- package/.next/server/app/api/hub/servers/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/hub/servers/route.js +7 -0
- package/.next/server/app/api/hub/servers/route.js.map +5 -0
- package/.next/server/app/api/hub/servers/route.js.nft.json +1 -0
- package/.next/server/app/api/hub/servers/route_client-reference-manifest.js +3 -0
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +14 -14
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +7 -7
- 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 +14 -14
- package/.next/server/app/login.segments/_full.segment.rsc +14 -14
- package/.next/server/app/login.segments/_head.segment.rsc +4 -4
- package/.next/server/app/login.segments/_index.segment.rsc +7 -7
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/login.segments/login.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
- package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
- package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/node.segments/_full.segment.rsc +14 -14
- package/.next/server/app/node.segments/_head.segment.rsc +4 -4
- package/.next/server/app/node.segments/_index.segment.rsc +7 -7
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/node.segments/node.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
- package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +14 -14
- package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/.next/server/app-paths-manifest.json +1 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +2 -1
- 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_057q.ne._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.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/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.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/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/0-mpa_947ipeq.js +1 -0
- package/.next/static/chunks/01lmbsd37fybu.js +1 -0
- package/.next/static/chunks/02l19-r-o5h9w.js +1 -0
- package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
- package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
- package/.next/static/chunks/04w4aircqqhb2.js +1 -0
- package/.next/static/chunks/05d9zwwtt-dh9.js +2 -0
- package/.next/static/chunks/05uk96gc~9mni.js +1 -0
- package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
- package/.next/static/chunks/0glmrdj9112tc.js +7 -0
- package/.next/static/chunks/0nd-y01y92.c8.js +7 -0
- package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
- package/.next/static/chunks/0tvn2l1pc.h65.js +1 -0
- package/.next/static/chunks/0w2-uz~4phw2f.css +2 -0
- package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
- package/.next/static/chunks/174re5ar~kxne.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +9 -0
- package/app/api/hub/servers/route.ts +39 -0
- package/app/components/AppShell.tsx +2 -0
- package/app/components/ChatPopover.tsx +38 -2
- package/app/components/CommandPalette.tsx +51 -0
- package/app/components/HelpOverlay.tsx +44 -0
- package/app/components/ServersDrawer.tsx +206 -0
- package/app/components/TopoGraph.tsx +1478 -166
- package/app/components/utils.ts +8 -7
- package/app/lib/time.ts +36 -0
- package/package.json +1 -1
- package/scripts/help-overlay-topo-test.mjs +44 -0
- package/scripts/servers-drawer-live-test.mjs +96 -0
- package/scripts/topo-active-filter-pill-test.mjs +101 -0
- package/scripts/topo-active-links-chip-test.mjs +73 -0
- package/scripts/topo-arrow-tier-test.mjs +61 -0
- package/scripts/topo-auto-fit-test.mjs +73 -0
- package/scripts/topo-chat-focus-ring-test.mjs +77 -0
- package/scripts/topo-click-ripple-test.mjs +66 -0
- package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
- package/scripts/topo-cmdk-pin-test.mjs +123 -0
- package/scripts/topo-cwd-tooltip-test.mjs +64 -0
- package/scripts/topo-dblclick-reset-test.mjs +74 -0
- package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
- package/scripts/topo-edge-focus-test.mjs +113 -0
- package/scripts/topo-edge-hover-test.mjs +93 -0
- package/scripts/topo-edge-tooltip-test.mjs +70 -0
- package/scripts/topo-esc-clears-pin-test.mjs +98 -0
- package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
- package/scripts/topo-filter-pill-count-test.mjs +110 -0
- package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
- package/scripts/topo-fit-key-test.mjs +106 -0
- package/scripts/topo-freshness-chip-test.mjs +74 -0
- package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
- package/scripts/topo-group-count-test.mjs +68 -0
- package/scripts/topo-group-hover-test.mjs +82 -0
- package/scripts/topo-group-pin-test.mjs +119 -0
- package/scripts/topo-group-status-pips-test.mjs +103 -0
- package/scripts/topo-groupbox-test.mjs +4 -2
- package/scripts/topo-help-overlay-r59-test.mjs +67 -0
- package/scripts/topo-help-overlay-r65-test.mjs +64 -0
- package/scripts/topo-help-overlay-r70-test.mjs +63 -0
- package/scripts/topo-hub-click-fit-test.mjs +137 -0
- package/scripts/topo-hub-title-test.mjs +93 -0
- package/scripts/topo-ingroup-edge-test.mjs +103 -0
- package/scripts/topo-label-lift-test.mjs +119 -0
- package/scripts/topo-label-scale-test.mjs +78 -0
- package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
- package/scripts/topo-layout-key-test.mjs +91 -0
- package/scripts/topo-legend-hover-test.mjs +113 -0
- package/scripts/topo-legend-pin-test.mjs +128 -0
- package/scripts/topo-minimap-test.mjs +68 -0
- package/scripts/topo-mobile-chips-test.mjs +103 -0
- package/scripts/topo-node-hover-lift-test.mjs +85 -0
- package/scripts/topo-offline-dim-test.mjs +70 -0
- package/scripts/topo-overlap-test.mjs +164 -0
- package/scripts/topo-p0round-test.mjs +2 -2
- package/scripts/topo-pan-cursor-test.mjs +62 -0
- package/scripts/topo-panel-elevation-test.mjs +76 -0
- package/scripts/topo-pin-persistence-test.mjs +105 -0
- package/scripts/topo-popover-meta-test.mjs +78 -0
- package/scripts/topo-pressure-bar-test.mjs +69 -0
- package/scripts/topo-pressure-pin-test.mjs +112 -0
- package/scripts/topo-pulse-rate-test.mjs +71 -0
- package/scripts/topo-recent-row-hover-test.mjs +122 -0
- package/scripts/topo-recent-signal-empty-test.mjs +63 -0
- package/scripts/topo-reduced-motion-test.mjs +77 -0
- package/scripts/topo-tier-ring-test.mjs +69 -0
- package/scripts/topo-tier-stagger-test.mjs +89 -0
- package/scripts/topo-tz-ghost-test.mjs +69 -0
- package/scripts/topo-wheel-zoom-test.mjs +79 -0
- package/scripts/topo-zoom-keys-test.mjs +89 -0
- package/.next/static/chunks/0-7iowzu4.798.js +0 -1
- package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
- package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
- package/.next/static/chunks/0_rmxom9~1lmk.css +0 -2
- package/.next/static/chunks/0cbezu61m-e~z.js +0 -1
- package/.next/static/chunks/0cj5qr_a8dzky.js +0 -7
- package/.next/static/chunks/0elalb9n_.~di.js +0 -1
- package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
- package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
- package/.next/static/chunks/0mn0ruf0g4op5.js +0 -1
- package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
- package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
- package/.next/static/chunks/0y9subfnntv1s.js +0 -1
- package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
- package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
- /package/.next/static/{7xem03-473G3qw-fC160p → T0cmGL0ztnd3KBUXEqBqR}/_buildManifest.js +0 -0
- /package/.next/static/{7xem03-473G3qw-fC160p → T0cmGL0ztnd3KBUXEqBqR}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{7xem03-473G3qw-fC160p → T0cmGL0ztnd3KBUXEqBqR}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* Round 40 verification: hovering a node brightens edges touching it
|
|
2
|
+
* and dims the rest. Three edges in the fleet — hover one node and
|
|
3
|
+
* compare base-path opacity of touching vs non-touching edges. */
|
|
4
|
+
import { chromium } from 'playwright';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
|
|
7
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
8
|
+
const browser = await chromium.launch({ headless: true });
|
|
9
|
+
|
|
10
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
|
11
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
12
|
+
await ctx.addInitScript(() => {
|
|
13
|
+
try {
|
|
14
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
15
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
16
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
17
|
+
} catch {}
|
|
18
|
+
});
|
|
19
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
20
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
21
|
+
const r = await route.fetch();
|
|
22
|
+
const b = await r.json();
|
|
23
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
24
|
+
const sessions = ['alpha', 'beta', 'gamma', 'delta'].map(a => ({
|
|
25
|
+
alias: a, status: 'idle', network_id: nid, project_dir: null,
|
|
26
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
27
|
+
}));
|
|
28
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
29
|
+
});
|
|
30
|
+
// Edges:
|
|
31
|
+
// alpha → beta (touches alpha + beta)
|
|
32
|
+
// alpha → gamma (touches alpha + gamma)
|
|
33
|
+
// delta → gamma (touches delta + gamma — does NOT touch alpha)
|
|
34
|
+
const now = new Date().toISOString();
|
|
35
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
|
|
36
|
+
{ from_alias: 'alpha', to_alias: 'beta', content: 'm', created_at: now },
|
|
37
|
+
{ from_alias: 'alpha', to_alias: 'gamma', content: 'm', created_at: now },
|
|
38
|
+
{ from_alias: 'delta', to_alias: 'gamma', content: 'm', created_at: now },
|
|
39
|
+
] } }));
|
|
40
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
41
|
+
const page = await ctx.newPage();
|
|
42
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
43
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
44
|
+
await page.waitForTimeout(600);
|
|
45
|
+
|
|
46
|
+
const edgeOpacities = () => page.evaluate(() => {
|
|
47
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
48
|
+
// R48 reordered the children of each flow-link <g>:
|
|
49
|
+
// path[data-edge-hitbox] <-- transparent hover surface, carries <title>
|
|
50
|
+
// path[marker-end] <-- the visible base edge with opacity
|
|
51
|
+
// path[id^=flow-path-] <-- the dashed runway
|
|
52
|
+
// Read the route from the hitbox's <title>, but the opacity from the
|
|
53
|
+
// visible base path. Picking by marker-end keeps the selector resilient
|
|
54
|
+
// to future siblings.
|
|
55
|
+
return [...svg.querySelectorAll(':scope > g > g')]
|
|
56
|
+
.map(g => {
|
|
57
|
+
const hitbox = g.querySelector('path[data-edge-hitbox]');
|
|
58
|
+
const title = hitbox?.querySelector('title');
|
|
59
|
+
const base = [...g.querySelectorAll(':scope > path')].find(
|
|
60
|
+
p => !p.hasAttribute('data-edge-hitbox') && p.hasAttribute('marker-end')
|
|
61
|
+
);
|
|
62
|
+
if (!title || !base) return null;
|
|
63
|
+
return { route: title.textContent.split('\n')[0], opacity: +base.getAttribute('opacity') };
|
|
64
|
+
})
|
|
65
|
+
.filter(Boolean);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const before = await edgeOpacities();
|
|
69
|
+
// Hover alpha → its two outgoing edges should brighten, delta→gamma should dim.
|
|
70
|
+
await page.locator('g[data-node="alpha"]').hover();
|
|
71
|
+
await page.waitForTimeout(350);
|
|
72
|
+
const after = await edgeOpacities();
|
|
73
|
+
|
|
74
|
+
await browser.close();
|
|
75
|
+
const get = (rows, r) => rows.find(e => e.route === r)?.opacity ?? null;
|
|
76
|
+
const b_ab = get(before, 'alpha → beta');
|
|
77
|
+
const b_ag = get(before, 'alpha → gamma');
|
|
78
|
+
const b_dg = get(before, 'delta → gamma');
|
|
79
|
+
const a_ab = get(after, 'alpha → beta');
|
|
80
|
+
const a_ag = get(after, 'alpha → gamma');
|
|
81
|
+
const a_dg = get(after, 'delta → gamma');
|
|
82
|
+
|
|
83
|
+
const results = {
|
|
84
|
+
baselineMatches: b_ab !== null && b_ag !== null && b_dg !== null && Math.abs(b_ab - b_dg) < 0.001,
|
|
85
|
+
hoverBrightensAlphaBeta: a_ab !== null && a_ab > b_ab + 0.05,
|
|
86
|
+
hoverBrightensAlphaGamma: a_ag !== null && a_ag > b_ag + 0.05,
|
|
87
|
+
hoverDimsDeltaGamma: a_dg !== null && a_dg < b_dg - 0.05,
|
|
88
|
+
};
|
|
89
|
+
const ok = Object.values(results).every(Boolean);
|
|
90
|
+
console.log(`${ok ? '✅' : '❌'} edge hover:`, JSON.stringify(results),
|
|
91
|
+
`\n before α→β=${b_ab} α→γ=${b_ag} δ→γ=${b_dg}`,
|
|
92
|
+
`\n after α→β=${a_ab} α→γ=${a_ag} δ→γ=${a_dg}`);
|
|
93
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* Round 39 verification: each flow-link path carries a `<title>` with
|
|
2
|
+
* "from → to\nN messages · last <relative>". Tests:
|
|
3
|
+
* - tooltip text shape (route + count + relative time)
|
|
4
|
+
* - singular vs. plural ("1 message" vs "5 messages")
|
|
5
|
+
* - missing last_at falls through cleanly */
|
|
6
|
+
import { chromium } from 'playwright';
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
|
|
9
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
10
|
+
const browser = await chromium.launch({ headless: true });
|
|
11
|
+
|
|
12
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
|
13
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
14
|
+
await ctx.addInitScript(() => {
|
|
15
|
+
try {
|
|
16
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
17
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
18
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
19
|
+
} catch {}
|
|
20
|
+
});
|
|
21
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
22
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
23
|
+
const r = await route.fetch();
|
|
24
|
+
const b = await r.json();
|
|
25
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
26
|
+
const sessions = ['alpha', 'beta', 'gamma'].map(a => ({
|
|
27
|
+
alias: a, status: 'idle', network_id: nid, project_dir: null,
|
|
28
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
29
|
+
}));
|
|
30
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
31
|
+
});
|
|
32
|
+
// Inject messages: 1 between alpha→beta (singular), 5 between alpha→gamma (plural).
|
|
33
|
+
const old = new Date(Date.now() - 8 * 60 * 1000).toISOString();
|
|
34
|
+
const recent = new Date(Date.now() - 30 * 1000).toISOString();
|
|
35
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
|
|
36
|
+
{ from_alias: 'alpha', to_alias: 'beta', content: 'hello', created_at: old },
|
|
37
|
+
{ from_alias: 'alpha', to_alias: 'gamma', content: 'm1', created_at: recent },
|
|
38
|
+
{ from_alias: 'alpha', to_alias: 'gamma', content: 'm2', created_at: recent },
|
|
39
|
+
{ from_alias: 'alpha', to_alias: 'gamma', content: 'm3', created_at: recent },
|
|
40
|
+
{ from_alias: 'alpha', to_alias: 'gamma', content: 'm4', created_at: recent },
|
|
41
|
+
{ from_alias: 'alpha', to_alias: 'gamma', content: 'm5', created_at: recent },
|
|
42
|
+
] } }));
|
|
43
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
44
|
+
const page = await ctx.newPage();
|
|
45
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
46
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
|
|
47
|
+
await page.waitForTimeout(600);
|
|
48
|
+
|
|
49
|
+
// Collect every flow-link tooltip (each base path with a child <title>).
|
|
50
|
+
const tooltips = await page.evaluate(() => {
|
|
51
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
52
|
+
// Two paths per flow link — the base one carries the <title>.
|
|
53
|
+
const titled = [...svg.querySelectorAll('path > title')];
|
|
54
|
+
return titled.map(t => t.textContent);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await browser.close();
|
|
58
|
+
const has = (route, count, plural) => tooltips.some(t =>
|
|
59
|
+
t.includes(route) &&
|
|
60
|
+
(plural ? t.includes(`${count} messages`) : t.includes(`${count} message`)) &&
|
|
61
|
+
/last \w+ ago/.test(t)
|
|
62
|
+
);
|
|
63
|
+
const results = {
|
|
64
|
+
singularAlphaToBeta: has('alpha → beta', 1, false),
|
|
65
|
+
pluralAlphaToGamma: has('alpha → gamma', 5, true),
|
|
66
|
+
tooltipCount: tooltips.length === 2,
|
|
67
|
+
};
|
|
68
|
+
const ok = Object.values(results).every(Boolean);
|
|
69
|
+
console.log(`${ok ? '✅' : '❌'} edge tooltip:`, JSON.stringify(results), '\n tooltips=', tooltips);
|
|
70
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* Round 62 verification: pressing Esc clears pinnedStatus when no chat
|
|
2
|
+
* is open. Composes cleanly with the R136 ChatPopover Esc handler
|
|
3
|
+
* (which only mounts while chatAlias is set).
|
|
4
|
+
*
|
|
5
|
+
* - Pin via legend row (R61) → assert filter active.
|
|
6
|
+
* - Press Esc → assert pin released, all nodes restore.
|
|
7
|
+
* - aria-pressed flips back to false.
|
|
8
|
+
*/
|
|
9
|
+
import { chromium } from 'playwright';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
|
|
12
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
13
|
+
const browser = await chromium.launch({ headless: true });
|
|
14
|
+
|
|
15
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
16
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
17
|
+
await ctx.addInitScript(() => {
|
|
18
|
+
try {
|
|
19
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
20
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
21
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
22
|
+
} catch {}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
26
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
27
|
+
const r = await route.fetch();
|
|
28
|
+
const b = await r.json();
|
|
29
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
30
|
+
const sessions = [
|
|
31
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
32
|
+
{ alias: 'idl1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
33
|
+
{ alias: 'idl2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
34
|
+
];
|
|
35
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
36
|
+
});
|
|
37
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
38
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
39
|
+
const page = await ctx.newPage();
|
|
40
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
41
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
|
|
42
|
+
await page.waitForTimeout(500);
|
|
43
|
+
|
|
44
|
+
const opacities = () => page.evaluate(() => {
|
|
45
|
+
const o = {};
|
|
46
|
+
for (const a of ['wkr', 'idl1', 'idl2']) {
|
|
47
|
+
const g = document.querySelector(`g[data-node="${a}"]`);
|
|
48
|
+
o[a] = g ? +(g.style.opacity || '1') : null;
|
|
49
|
+
}
|
|
50
|
+
return o;
|
|
51
|
+
});
|
|
52
|
+
const ariaPressed = (status) => page.evaluate(s => {
|
|
53
|
+
const g = document.querySelector(`g[data-legend-status="${s}"]`);
|
|
54
|
+
return g?.getAttribute('aria-pressed');
|
|
55
|
+
}, status);
|
|
56
|
+
|
|
57
|
+
const before = await opacities();
|
|
58
|
+
|
|
59
|
+
// Pin working via legend row click.
|
|
60
|
+
await page.locator('g[data-legend-status="working"]').first().click({ force: true });
|
|
61
|
+
// Move mouse away so we observe pin-only (not hover-driven) filter.
|
|
62
|
+
await page.mouse.move(10, 10);
|
|
63
|
+
await page.waitForTimeout(250);
|
|
64
|
+
const afterPin = await opacities();
|
|
65
|
+
const ariaAfterPin = await ariaPressed('working');
|
|
66
|
+
|
|
67
|
+
// Press Esc. Body should have focus so the document-level keydown
|
|
68
|
+
// handler fires.
|
|
69
|
+
await page.evaluate(() => document.activeElement && typeof document.activeElement.blur === 'function' && document.activeElement.blur());
|
|
70
|
+
await page.keyboard.press('Escape');
|
|
71
|
+
await page.waitForTimeout(250);
|
|
72
|
+
const afterEsc = await opacities();
|
|
73
|
+
const ariaAfterEsc = await ariaPressed('working');
|
|
74
|
+
|
|
75
|
+
// Press Esc again — should be a no-op (no pin, no chat).
|
|
76
|
+
await page.keyboard.press('Escape');
|
|
77
|
+
await page.waitForTimeout(150);
|
|
78
|
+
const afterEsc2 = await opacities();
|
|
79
|
+
|
|
80
|
+
await browser.close();
|
|
81
|
+
|
|
82
|
+
const bright = (v) => v != null && v >= 0.55;
|
|
83
|
+
const dim = (v) => v != null && v < 0.4;
|
|
84
|
+
const results = {
|
|
85
|
+
before_baseline: bright(before.wkr) && bright(before.idl1) && bright(before.idl2),
|
|
86
|
+
pin_keepsWorking: bright(afterPin.wkr),
|
|
87
|
+
pin_dimsIdle: dim(afterPin.idl1) && dim(afterPin.idl2),
|
|
88
|
+
pin_ariaTrue: ariaAfterPin === 'true',
|
|
89
|
+
esc_releasesPin: bright(afterEsc.wkr) && bright(afterEsc.idl1) && bright(afterEsc.idl2),
|
|
90
|
+
esc_ariaBackToFalse: ariaAfterEsc === 'false',
|
|
91
|
+
escAgain_isNoOp: bright(afterEsc2.wkr) && bright(afterEsc2.idl1),
|
|
92
|
+
};
|
|
93
|
+
const ok = Object.values(results).every(Boolean);
|
|
94
|
+
console.log(`${ok ? '✅' : '❌'} esc clears pin:`, JSON.stringify(results),
|
|
95
|
+
`\n before=`, before,
|
|
96
|
+
`\n afterPin=`, afterPin, ` aria=${ariaAfterPin}`,
|
|
97
|
+
`\n afterEsc=`, afterEsc, ` aria=${ariaAfterEsc}`);
|
|
98
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* Round 73 verification: clicking the pill body (not just the ×) clears
|
|
2
|
+
* the pin. Matches the Notion / Linear tag UX where the whole chip is
|
|
3
|
+
* the clear target. The × button keeps its dedicated <button> +
|
|
4
|
+
* aria-label so screen readers still get a proper "Clear filter" action.
|
|
5
|
+
*
|
|
6
|
+
* - Pre-seed pin via R66 sessionStorage so pill renders on first paint.
|
|
7
|
+
* - Click the pill body (text portion, NOT the × button).
|
|
8
|
+
* - Assert: pin clears + pill removed from DOM + node opacities
|
|
9
|
+
* restored.
|
|
10
|
+
* - Re-pin; click the × button → pin also clears.
|
|
11
|
+
* - title attr on the pill body reads "Click to clear filter".
|
|
12
|
+
* - cursor:pointer set on the pill body for affordance.
|
|
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 browser = await chromium.launch({ headless: true });
|
|
19
|
+
|
|
20
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
21
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
22
|
+
await ctx.addInitScript(() => {
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
25
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
26
|
+
sessionStorage.setItem('anet-topo-pinned-status', 'working');
|
|
27
|
+
} catch {}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
31
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
32
|
+
const r = await route.fetch();
|
|
33
|
+
const b = await r.json();
|
|
34
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
35
|
+
const sessions = [
|
|
36
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
37
|
+
{ alias: 'idl', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
38
|
+
];
|
|
39
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
40
|
+
});
|
|
41
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
42
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
43
|
+
const page = await ctx.newPage();
|
|
44
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
45
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
|
|
46
|
+
await page.waitForTimeout(500);
|
|
47
|
+
|
|
48
|
+
const readState = () => page.evaluate(() => {
|
|
49
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
50
|
+
return {
|
|
51
|
+
pillPresent: !!pill,
|
|
52
|
+
pillTitle: pill?.getAttribute('title') || null,
|
|
53
|
+
pillCursor: pill ? getComputedStyle(pill).cursor : null,
|
|
54
|
+
idlOpacity: +(document.querySelector('g[data-node="idl"]')?.style.opacity || '1'),
|
|
55
|
+
storageStatus: sessionStorage.getItem('anet-topo-pinned-status'),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const before = await readState();
|
|
60
|
+
|
|
61
|
+
// Click on the pill body — pick a point in the text region, NOT the ×.
|
|
62
|
+
// The pill is a flex row; the first inner <span> holds the text. Click
|
|
63
|
+
// at its centre.
|
|
64
|
+
await page.evaluate(() => {
|
|
65
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
66
|
+
if (!pill) throw new Error('pill not found');
|
|
67
|
+
const text = pill.querySelector('span');
|
|
68
|
+
if (!text) throw new Error('pill text not found');
|
|
69
|
+
const r = text.getBoundingClientRect();
|
|
70
|
+
window.__bodyClickXY = { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
|
71
|
+
});
|
|
72
|
+
const xy = await page.evaluate(() => window.__bodyClickXY);
|
|
73
|
+
await page.mouse.click(xy.x, xy.y);
|
|
74
|
+
await page.waitForTimeout(250);
|
|
75
|
+
const afterBodyClick = await readState();
|
|
76
|
+
|
|
77
|
+
// Re-pin via sessionStorage + event.
|
|
78
|
+
await page.evaluate(() => {
|
|
79
|
+
sessionStorage.setItem('anet-topo-pinned-status', 'working');
|
|
80
|
+
window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'status', value: 'working' } }));
|
|
81
|
+
});
|
|
82
|
+
await page.waitForTimeout(250);
|
|
83
|
+
const afterRepin = await readState();
|
|
84
|
+
|
|
85
|
+
// Click the × button.
|
|
86
|
+
await page.locator('[data-active-filter="status"] button').first().click();
|
|
87
|
+
await page.waitForTimeout(250);
|
|
88
|
+
const afterXClick = await readState();
|
|
89
|
+
|
|
90
|
+
await browser.close();
|
|
91
|
+
|
|
92
|
+
const bright = (v) => v != null && v >= 0.55;
|
|
93
|
+
const dim = (v) => v != null && v < 0.4;
|
|
94
|
+
const results = {
|
|
95
|
+
before_pillPresent: before.pillPresent === true,
|
|
96
|
+
before_titleHint: before.pillTitle === 'Click to clear filter',
|
|
97
|
+
before_cursorPointer: before.pillCursor === 'pointer',
|
|
98
|
+
before_idleDim: dim(before.idlOpacity),
|
|
99
|
+
bodyClick_pillGone: afterBodyClick.pillPresent === false,
|
|
100
|
+
bodyClick_storageGone: afterBodyClick.storageStatus === null,
|
|
101
|
+
bodyClick_idleRestored: bright(afterBodyClick.idlOpacity),
|
|
102
|
+
repin_pillBack: afterRepin.pillPresent === true,
|
|
103
|
+
xClick_pillGone: afterXClick.pillPresent === false,
|
|
104
|
+
xClick_storageGone: afterXClick.storageStatus === null,
|
|
105
|
+
};
|
|
106
|
+
const ok = Object.values(results).every(Boolean);
|
|
107
|
+
console.log(`${ok ? '✅' : '❌'} filter pill body click:`, JSON.stringify(results),
|
|
108
|
+
`\n before=`, before,
|
|
109
|
+
`\n afterBodyClick=`, afterBodyClick,
|
|
110
|
+
`\n afterRepin=`, afterRepin,
|
|
111
|
+
`\n afterXClick=`, afterXClick);
|
|
112
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* Round 71 verification: each active-filter pill shows a "· N" match
|
|
2
|
+
* count tail and surfaces `data-filter-match-count` for tooling.
|
|
3
|
+
*
|
|
4
|
+
* Sessions: 1 working + 2 idle + 1 offline + 3-alpha cluster.
|
|
5
|
+
* Pin status=working → pill "filter: working · 1", count=1
|
|
6
|
+
* Pin status=idle → pill "filter: idle · 5", count=5 (idl + alpha1/2/3 all idle online)
|
|
7
|
+
* Pin status=offline → pill "filter: offline · 1", count=1
|
|
8
|
+
* Pin group=alpha → pill "filter: alpha · 3", count=3 (3 alphas)
|
|
9
|
+
*/
|
|
10
|
+
import { chromium } from 'playwright';
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
|
|
13
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
14
|
+
const browser = await chromium.launch({ headless: true });
|
|
15
|
+
|
|
16
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
17
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
18
|
+
await ctx.addInitScript(() => {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
21
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
22
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
23
|
+
sessionStorage.removeItem('anet-topo-pinned-status');
|
|
24
|
+
sessionStorage.removeItem('anet-topo-pinned-group');
|
|
25
|
+
} catch {}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
29
|
+
const stale = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
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 sessions = [
|
|
35
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
36
|
+
{ alias: 'idl', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
37
|
+
{ alias: 'idl2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
38
|
+
{ alias: 'alpha1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
39
|
+
{ alias: 'alpha2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
40
|
+
{ alias: 'alpha3', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
41
|
+
{ alias: 'off', status: 'offline', network_id: nid, project_dir: null, created_at: stale, updated_at: stale, last_seen_at: stale },
|
|
42
|
+
];
|
|
43
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
44
|
+
});
|
|
45
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
46
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
47
|
+
const page = await ctx.newPage();
|
|
48
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
49
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 7, { timeout: 30000 });
|
|
50
|
+
await page.waitForTimeout(700);
|
|
51
|
+
|
|
52
|
+
async function pin({ status, group }) {
|
|
53
|
+
await page.evaluate(({ status, group }) => {
|
|
54
|
+
if (status === null) { sessionStorage.removeItem('anet-topo-pinned-status'); }
|
|
55
|
+
else { sessionStorage.setItem('anet-topo-pinned-status', status); }
|
|
56
|
+
if (group === null) { sessionStorage.removeItem('anet-topo-pinned-group'); }
|
|
57
|
+
else if (group) { sessionStorage.setItem('anet-topo-pinned-group', group); }
|
|
58
|
+
window.dispatchEvent(new CustomEvent('anet:topo-pin',
|
|
59
|
+
{ detail: status !== undefined ? { kind: 'status', value: status }
|
|
60
|
+
: group !== undefined ? { kind: 'group', value: group }
|
|
61
|
+
: { kind: 'clear' } }));
|
|
62
|
+
}, { status, group });
|
|
63
|
+
await page.waitForTimeout(250);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const readPill = (kind) => page.evaluate(k => {
|
|
67
|
+
const el = document.querySelector(`[data-active-filter="${k}"]`);
|
|
68
|
+
if (!el) return null;
|
|
69
|
+
return {
|
|
70
|
+
text: (el.innerText || el.textContent || '').trim(),
|
|
71
|
+
count: el.getAttribute('data-filter-match-count'),
|
|
72
|
+
};
|
|
73
|
+
}, kind);
|
|
74
|
+
|
|
75
|
+
// Pin working → 1 match.
|
|
76
|
+
await pin({ status: 'working' });
|
|
77
|
+
const w = await readPill('status');
|
|
78
|
+
|
|
79
|
+
// Pin idle → 5 (idl + idl2 + alpha1 + alpha2 + alpha3 — all idle online).
|
|
80
|
+
await pin({ status: 'idle' });
|
|
81
|
+
const i = await readPill('status');
|
|
82
|
+
|
|
83
|
+
// Pin offline → 1 match.
|
|
84
|
+
await pin({ status: 'offline' });
|
|
85
|
+
const o = await readPill('status');
|
|
86
|
+
|
|
87
|
+
// Pin group alpha → 3 match.
|
|
88
|
+
await pin({ status: null });
|
|
89
|
+
await pin({ group: 'alpha' });
|
|
90
|
+
const g = await readPill('group');
|
|
91
|
+
|
|
92
|
+
await browser.close();
|
|
93
|
+
|
|
94
|
+
const results = {
|
|
95
|
+
working_attrCount1: w && w.count === '1',
|
|
96
|
+
working_textHas1: w && /·\s*1\b/.test(w.text),
|
|
97
|
+
idle_attrCount5: i && i.count === '5',
|
|
98
|
+
idle_textHas5: i && /·\s*5\b/.test(i.text),
|
|
99
|
+
offline_attrCount1: o && o.count === '1',
|
|
100
|
+
offline_textHas1: o && /·\s*1\b/.test(o.text),
|
|
101
|
+
group_attrCount3: g && g.count === '3',
|
|
102
|
+
group_textHas3: g && /·\s*3\b/.test(g.text),
|
|
103
|
+
};
|
|
104
|
+
const ok = Object.values(results).every(Boolean);
|
|
105
|
+
console.log(`${ok ? '✅' : '❌'} filter pill count:`, JSON.stringify(results),
|
|
106
|
+
`\n working=`, w,
|
|
107
|
+
`\n idle=`, i,
|
|
108
|
+
`\n offline=`, o,
|
|
109
|
+
`\n group=`, g);
|
|
110
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* Round 67 verification: filter pills shed the "filter:" prefix on
|
|
2
|
+
* narrow viewports and gain an anet-fade-in entrance.
|
|
3
|
+
* - At <sm (375 px) the prefix span has `display:none` via the
|
|
4
|
+
* `hidden sm:inline` class — visible pill text = "working×".
|
|
5
|
+
* - At sm+ (900 px) the prefix is visible — "filter: working ×".
|
|
6
|
+
* - The pill `<span>` carries `anet-fade-in` class so it eases in
|
|
7
|
+
* on mount (entrance polish).
|
|
8
|
+
*/
|
|
9
|
+
import { chromium } from 'playwright';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
|
|
12
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
13
|
+
const browser = await chromium.launch({ headless: true });
|
|
14
|
+
|
|
15
|
+
async function probe(width) {
|
|
16
|
+
const ctx = await browser.newContext({ viewport: { width, height: 1500 } });
|
|
17
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
18
|
+
await ctx.addInitScript(() => {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
21
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
22
|
+
// Pre-seed the working pin via R66 persistence so the pill exists
|
|
23
|
+
// on first paint — keeps the test independent of click wiring.
|
|
24
|
+
sessionStorage.setItem('anet-topo-pinned-status', 'working');
|
|
25
|
+
} catch {}
|
|
26
|
+
});
|
|
27
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
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 sessions = [
|
|
33
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
34
|
+
{ alias: 'idl', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
35
|
+
];
|
|
36
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
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
|
+
const page = await ctx.newPage();
|
|
41
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
42
|
+
// Mobile viewport auto-hides the topology section; click "Show Topology"
|
|
43
|
+
// if the button surfaces. Otherwise the pill still renders in the chip
|
|
44
|
+
// row (chip row is independent of canvas visibility).
|
|
45
|
+
await page.waitForTimeout(1200);
|
|
46
|
+
const showBtn = page.locator('button', { hasText: /Show Topology/i });
|
|
47
|
+
if (await showBtn.count() > 0) await showBtn.first().click();
|
|
48
|
+
await page.waitForSelector('[data-active-filter="status"]', { timeout: 15000 });
|
|
49
|
+
|
|
50
|
+
const info = await page.evaluate(() => {
|
|
51
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
52
|
+
if (!pill) return null;
|
|
53
|
+
const prefix = pill.querySelector('[data-filter-prefix]');
|
|
54
|
+
// innerText respects display:none (vs textContent which includes
|
|
55
|
+
// all DOM text); that's the visible-to-the-user version.
|
|
56
|
+
return {
|
|
57
|
+
pillText: (pill.innerText || '').trim(),
|
|
58
|
+
pillFull: (pill.textContent || '').trim(),
|
|
59
|
+
pillClasses: pill.getAttribute('class') || '',
|
|
60
|
+
hasFadeIn: /\banet-fade-in\b/.test(pill.getAttribute('class') || ''),
|
|
61
|
+
prefixVisible: prefix
|
|
62
|
+
? (() => {
|
|
63
|
+
const r = prefix.getBoundingClientRect();
|
|
64
|
+
return r.width > 0 && r.height > 0;
|
|
65
|
+
})()
|
|
66
|
+
: false,
|
|
67
|
+
prefixDisplay: prefix ? getComputedStyle(prefix).display : null,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
await ctx.close();
|
|
71
|
+
return info;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const mobile = await probe(375);
|
|
75
|
+
const desktop = await probe(900);
|
|
76
|
+
await browser.close();
|
|
77
|
+
|
|
78
|
+
const results = {
|
|
79
|
+
mobile_prefixHidden: mobile && mobile.prefixVisible === false && mobile.prefixDisplay === 'none',
|
|
80
|
+
mobile_textNoPrefix: mobile && /^working/.test(mobile.pillText.trim()),
|
|
81
|
+
mobile_hasFadeIn: mobile && mobile.hasFadeIn === true,
|
|
82
|
+
desktop_prefixVisible: desktop && desktop.prefixVisible === true,
|
|
83
|
+
desktop_textWithPrefix: desktop && /^filter:\s*working/.test(desktop.pillText.trim()),
|
|
84
|
+
desktop_hasFadeIn: desktop && desktop.hasFadeIn === true,
|
|
85
|
+
};
|
|
86
|
+
const ok = Object.values(results).every(Boolean);
|
|
87
|
+
console.log(`${ok ? '✅' : '❌'} filter pill mobile:`, JSON.stringify(results),
|
|
88
|
+
`\n mobile=`, mobile,
|
|
89
|
+
`\n desktop=`, desktop);
|
|
90
|
+
process.exit(ok ? 0 : 1);
|