@sleep2agi/agent-network-dashboard 0.4.6-preview.9 → 0.4.6-preview.90
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/0.37dmx27_98x.js +1 -0
- package/.next/static/chunks/01lmbsd37fybu.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/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/0oknosftxc9lm.css +2 -0
- package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
- package/.next/static/chunks/0tjjn5oj-2lk7.js +2 -0
- package/.next/static/chunks/0tvn2l1pc.h65.js +1 -0
- package/.next/static/chunks/0w3m56zhfnsnx.js +7 -0
- package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
- package/.next/static/chunks/0xry0z6-ampwa.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 +55 -0
- package/app/components/ServersDrawer.tsx +206 -0
- package/app/components/TopoGraph.tsx +1606 -153
- 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-hover-test.mjs +103 -0
- package/scripts/topo-active-links-chip-test.mjs +73 -0
- package/scripts/topo-arrival-ping-test.mjs +87 -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-chip-pin-mirror-test.mjs +113 -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-dispatch-pulse-test.mjs +83 -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-help-overlay-r78-test.mjs +59 -0
- package/scripts/topo-help-overlay-r81-test.mjs +58 -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 +32 -5
- 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-hover-test.mjs +123 -0
- package/scripts/topo-pressure-pin-test.mjs +119 -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-status-chip-hover-test.mjs +101 -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-vendor-chip-hover-test.mjs +111 -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/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/0kgak.tfh2-~i.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/10oi7l-tycgl9.js +0 -1
- package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
- package/.next/static/chunks/131.zrqhdbu5x.js +0 -1
- package/.next/static/chunks/165lqf5.z.n10.js +0 -1
- /package/.next/static/{T0hK2WE0kVtyFouPsSpos → QkfrmdIw0FmotuWlcvJ1-}/_buildManifest.js +0 -0
- /package/.next/static/{T0hK2WE0kVtyFouPsSpos → QkfrmdIw0FmotuWlcvJ1-}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{T0hK2WE0kVtyFouPsSpos → QkfrmdIw0FmotuWlcvJ1-}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* Round 60 verification: each pressure-bar segment toggles a sticky
|
|
2
|
+
* status filter. Click "working" → all non-working nodes dim, working
|
|
3
|
+
* ones stay. Click again → release. Pin and the R55 legend hover feed
|
|
4
|
+
* the same node-opacity branch via `activeStatus = hoveredStatus ??
|
|
5
|
+
* pinnedStatus`, so hover transiently overrides without nuking the pin.
|
|
6
|
+
*
|
|
7
|
+
* Sessions:
|
|
8
|
+
* wkr — working
|
|
9
|
+
* idl1 — idle
|
|
10
|
+
* idl2 — idle
|
|
11
|
+
* off — offline
|
|
12
|
+
*/
|
|
13
|
+
import { chromium } from 'playwright';
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
17
|
+
const browser = await chromium.launch({ headless: true });
|
|
18
|
+
|
|
19
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
20
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
21
|
+
await ctx.addInitScript(() => {
|
|
22
|
+
try {
|
|
23
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
24
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
25
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
26
|
+
} catch {}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
30
|
+
const stale = new Date(Date.now() - 30 * 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: 'idl1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
38
|
+
{ alias: 'idl2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
39
|
+
{ alias: 'off', status: 'offline', network_id: nid, project_dir: null, created_at: stale, updated_at: stale, last_seen_at: stale },
|
|
40
|
+
];
|
|
41
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
42
|
+
});
|
|
43
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
44
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
45
|
+
const page = await ctx.newPage();
|
|
46
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
47
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
48
|
+
await page.waitForTimeout(500);
|
|
49
|
+
|
|
50
|
+
const opacities = () => page.evaluate(() => {
|
|
51
|
+
const o = {};
|
|
52
|
+
for (const a of ['wkr', 'idl1', 'idl2', 'off']) {
|
|
53
|
+
const g = document.querySelector(`g[data-node="${a}"]`);
|
|
54
|
+
o[a] = g ? +(g.style.opacity || '1') : null;
|
|
55
|
+
}
|
|
56
|
+
return o;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const before = await opacities();
|
|
60
|
+
|
|
61
|
+
const bright = (v) => v != null && v >= 0.55;
|
|
62
|
+
const dim = (v) => v != null && v < 0.4;
|
|
63
|
+
|
|
64
|
+
// Click the WORKING segment.
|
|
65
|
+
const workingSeg = page.locator('[data-pressure-seg="working"]').first();
|
|
66
|
+
const workingExists = await workingSeg.count() > 0;
|
|
67
|
+
if (!workingExists) { console.log('❌ pressure segment[working] not found'); process.exit(1); }
|
|
68
|
+
await workingSeg.click({ force: true });
|
|
69
|
+
await page.waitForTimeout(250);
|
|
70
|
+
const afterPin = await opacities();
|
|
71
|
+
|
|
72
|
+
// Pin should persist past mouse moving away.
|
|
73
|
+
await page.mouse.move(10, 10);
|
|
74
|
+
await page.waitForTimeout(250);
|
|
75
|
+
const afterMouseMove = await opacities();
|
|
76
|
+
|
|
77
|
+
// Click again to release. R79 added hover-affordances on the working
|
|
78
|
+
// + online chips, so the click locator's mouse path can light a
|
|
79
|
+
// transient hoveredStatus en route to the pressure segment. Mouse-move
|
|
80
|
+
// to a neutral spot after the release click so we observe pin-only
|
|
81
|
+
// state (same gotcha R61's test documented inline).
|
|
82
|
+
await workingSeg.click({ force: true });
|
|
83
|
+
await page.mouse.move(10, 10);
|
|
84
|
+
await page.waitForTimeout(250);
|
|
85
|
+
const afterRelease = await opacities();
|
|
86
|
+
|
|
87
|
+
// Repeat for offline segment.
|
|
88
|
+
const offlineSeg = page.locator('[data-pressure-seg="offline"]').first();
|
|
89
|
+
await offlineSeg.click({ force: true });
|
|
90
|
+
await page.mouse.move(10, 10);
|
|
91
|
+
await page.waitForTimeout(250);
|
|
92
|
+
const afterOfflinePin = await opacities();
|
|
93
|
+
await offlineSeg.click({ force: true });
|
|
94
|
+
await page.mouse.move(10, 10);
|
|
95
|
+
await page.waitForTimeout(250);
|
|
96
|
+
const afterOfflineRelease = await opacities();
|
|
97
|
+
|
|
98
|
+
await browser.close();
|
|
99
|
+
|
|
100
|
+
const results = {
|
|
101
|
+
before_baseline: bright(before.wkr) && bright(before.idl1),
|
|
102
|
+
working_pinKeepsWorking: bright(afterPin.wkr),
|
|
103
|
+
working_pinDimsIdle: dim(afterPin.idl1) && dim(afterPin.idl2),
|
|
104
|
+
working_pinDimsOffline: dim(afterPin.off),
|
|
105
|
+
working_pinSurvivesMouse: bright(afterMouseMove.wkr) && dim(afterMouseMove.idl1),
|
|
106
|
+
working_releaseRestoresAll: bright(afterRelease.wkr) && bright(afterRelease.idl1),
|
|
107
|
+
offline_pinKeepsOffline: bright(afterOfflinePin.off), // base offline 0.6 reads as bright (>=0.55)
|
|
108
|
+
offline_pinDimsOnline: dim(afterOfflinePin.wkr) && dim(afterOfflinePin.idl1),
|
|
109
|
+
offline_releaseRestoresAll: bright(afterOfflineRelease.wkr),
|
|
110
|
+
};
|
|
111
|
+
const ok = Object.values(results).every(Boolean);
|
|
112
|
+
console.log(`${ok ? '✅' : '❌'} pressure pin:`, JSON.stringify(results),
|
|
113
|
+
`\n before=`, before,
|
|
114
|
+
`\n afterPin=`, afterPin,
|
|
115
|
+
`\n afterMouseMove=`, afterMouseMove,
|
|
116
|
+
`\n afterRelease=`, afterRelease,
|
|
117
|
+
`\n afterOfflinePin=`, afterOfflinePin,
|
|
118
|
+
`\n afterOfflineRelease=`, afterOfflineRelease);
|
|
119
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* Round 24 verification: working-node pulse duration tiers by sse:N.
|
|
2
|
+
* sse 0-1 → 1.2s (calm)
|
|
3
|
+
* sse 2-3 → 0.9s (active)
|
|
4
|
+
* sse ≥ 4 → 0.7s (busy)
|
|
5
|
+
* Inject sse counts via mocked /api/hub/health → sse_sessions map. */
|
|
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
|
+
// Three working sessions with different sse counts. Use a known network_id
|
|
23
|
+
// so the `${nid}:${alias}` sse-map key works (the page's grouped lookup).
|
|
24
|
+
const NID = 'net_test';
|
|
25
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
26
|
+
await route.fulfill({ json: {
|
|
27
|
+
ok: true,
|
|
28
|
+
sessions: ['calm', 'active', 'busy'].map(alias => ({
|
|
29
|
+
alias, status: 'working', network_id: NID, project_dir: null,
|
|
30
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
31
|
+
})),
|
|
32
|
+
} });
|
|
33
|
+
});
|
|
34
|
+
await ctx.route('**/api/hub/health*', async (route) => {
|
|
35
|
+
await route.fulfill({ json: {
|
|
36
|
+
ok: true,
|
|
37
|
+
version: '0.8.0-test',
|
|
38
|
+
sse_sessions: {
|
|
39
|
+
[`${NID}:calm`]: 1,
|
|
40
|
+
[`${NID}:active`]: 2,
|
|
41
|
+
[`${NID}:busy`]: 4,
|
|
42
|
+
},
|
|
43
|
+
} });
|
|
44
|
+
});
|
|
45
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
46
|
+
const page = await ctx.newPage();
|
|
47
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
48
|
+
await page.waitForFunction(() => {
|
|
49
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
50
|
+
return !!svg && svg.querySelectorAll('g[data-node]').length === 3;
|
|
51
|
+
}, { timeout: 30000 }).catch(() => {});
|
|
52
|
+
await page.waitForTimeout(600);
|
|
53
|
+
|
|
54
|
+
const durOf = alias => page.$eval(
|
|
55
|
+
`g[data-node="${alias}"] circle[data-pulse-dur]`,
|
|
56
|
+
el => el.getAttribute('data-pulse-dur'),
|
|
57
|
+
).catch(() => null);
|
|
58
|
+
|
|
59
|
+
const calm = await durOf('calm');
|
|
60
|
+
const active = await durOf('active');
|
|
61
|
+
const busy = await durOf('busy');
|
|
62
|
+
|
|
63
|
+
await browser.close();
|
|
64
|
+
const results = {
|
|
65
|
+
calmIs1_2s: calm === '1.2s',
|
|
66
|
+
activeIs0_9s: active === '0.9s',
|
|
67
|
+
busyIs0_7s: busy === '0.7s',
|
|
68
|
+
};
|
|
69
|
+
const ok = Object.values(results).every(Boolean);
|
|
70
|
+
console.log(`${ok ? '✅' : '❌'} pulse rate:`, JSON.stringify(results), `calm=${calm} active=${active} busy=${busy}`);
|
|
71
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/* Round 56 verification: hovering a recent-signal panel row sets
|
|
2
|
+
* hoveredEdgeKey to that link, which fires the R50 edge-focus +
|
|
3
|
+
* R49 endpoint-highlight ladders against the canvas.
|
|
4
|
+
*
|
|
5
|
+
* Sessions: alpha, beta, gamma, delta, epsilon, zeta (all idle).
|
|
6
|
+
* Messages create three deduped flow links in this order:
|
|
7
|
+
* alpha → beta
|
|
8
|
+
* gamma → delta
|
|
9
|
+
* epsilon → zeta
|
|
10
|
+
* Hover the FIRST recent-signal row (alpha→beta):
|
|
11
|
+
* - alpha→beta visible edge: opacity boosts ≥1.5× baseline (R50)
|
|
12
|
+
* - other edges (γ→δ, ε→ζ): opacity dims < 0.6× baseline
|
|
13
|
+
* - alpha + beta nodes: stay bright (≥0.55) — endpoint highlight (R49)
|
|
14
|
+
* - gamma/delta/epsilon/zeta nodes: dim (<0.4)
|
|
15
|
+
* Release → all restore.
|
|
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
|
+
|
|
23
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
24
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
25
|
+
await ctx.addInitScript(() => {
|
|
26
|
+
try {
|
|
27
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
28
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
29
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
30
|
+
} catch {}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
34
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
35
|
+
const r = await route.fetch();
|
|
36
|
+
const b = await r.json();
|
|
37
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
38
|
+
const sessions = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta'].map(a => ({
|
|
39
|
+
alias: a, status: 'idle', 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 } });
|
|
43
|
+
});
|
|
44
|
+
const now = new Date().toISOString();
|
|
45
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
|
|
46
|
+
{ from_alias: 'alpha', to_alias: 'beta', content: 'm', created_at: now },
|
|
47
|
+
{ from_alias: 'gamma', to_alias: 'delta', content: 'm', created_at: now },
|
|
48
|
+
{ from_alias: 'epsilon', to_alias: 'zeta', content: 'm', created_at: now },
|
|
49
|
+
] } }));
|
|
50
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
51
|
+
const page = await ctx.newPage();
|
|
52
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
53
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 6, { timeout: 30000 });
|
|
54
|
+
await page.waitForTimeout(600);
|
|
55
|
+
|
|
56
|
+
const readState = () => page.evaluate(() => {
|
|
57
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
58
|
+
const edges = {};
|
|
59
|
+
for (const g of svg.querySelectorAll(':scope > g > g')) {
|
|
60
|
+
const t = g.querySelector('path[data-edge-hitbox] title');
|
|
61
|
+
if (!t) continue;
|
|
62
|
+
const route = (t.textContent || '').split('\n')[0];
|
|
63
|
+
const base = [...g.querySelectorAll(':scope > path')].find(
|
|
64
|
+
p => !p.hasAttribute('data-edge-hitbox') && p.hasAttribute('marker-end')
|
|
65
|
+
);
|
|
66
|
+
if (base) edges[route] = +base.getAttribute('opacity');
|
|
67
|
+
}
|
|
68
|
+
const nodes = {};
|
|
69
|
+
for (const a of ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta']) {
|
|
70
|
+
const n = document.querySelector(`g[data-node="${a}"]`);
|
|
71
|
+
nodes[a] = n ? +(n.style.opacity || '1') : null;
|
|
72
|
+
}
|
|
73
|
+
return { edges, nodes };
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const before = await readState();
|
|
77
|
+
|
|
78
|
+
// Hover the first recent-signal row — its key is "alpha->beta".
|
|
79
|
+
const rowRect = await page.evaluate(() => {
|
|
80
|
+
const g = document.querySelector('g[data-recent-row="alpha->beta"]');
|
|
81
|
+
if (!g) return null;
|
|
82
|
+
const r = g.getBoundingClientRect();
|
|
83
|
+
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
|
84
|
+
});
|
|
85
|
+
if (!rowRect) { console.log('❌ recent-signal row alpha->beta not found'); process.exit(1); }
|
|
86
|
+
await page.mouse.move(10, 10);
|
|
87
|
+
await page.mouse.move(rowRect.x, rowRect.y);
|
|
88
|
+
await page.waitForTimeout(350);
|
|
89
|
+
const during = await readState();
|
|
90
|
+
|
|
91
|
+
await page.mouse.move(10, 10);
|
|
92
|
+
await page.waitForTimeout(350);
|
|
93
|
+
const after = await readState();
|
|
94
|
+
|
|
95
|
+
await browser.close();
|
|
96
|
+
|
|
97
|
+
const baseEdgeOp = before.edges['alpha → beta'];
|
|
98
|
+
const eAB = during.edges['alpha → beta'];
|
|
99
|
+
const eGD = during.edges['gamma → delta'];
|
|
100
|
+
const eEZ = during.edges['epsilon → zeta'];
|
|
101
|
+
|
|
102
|
+
const bright = (v) => v != null && v >= 0.55;
|
|
103
|
+
const dim = (v) => v != null && v < 0.4;
|
|
104
|
+
const results = {
|
|
105
|
+
baselineEqual: Math.abs(before.edges['alpha → beta'] - before.edges['gamma → delta']) < 0.001
|
|
106
|
+
&& Math.abs(before.edges['alpha → beta'] - before.edges['epsilon → zeta']) < 0.001,
|
|
107
|
+
hoverBrightensTargetEdge: eAB > baseEdgeOp * 1.5,
|
|
108
|
+
hoverDimsOtherEdges: eGD < baseEdgeOp * 0.6 && eEZ < baseEdgeOp * 0.6,
|
|
109
|
+
endpointsStayBright: bright(during.nodes.alpha) && bright(during.nodes.beta),
|
|
110
|
+
nonEndpointsDim: dim(during.nodes.gamma) && dim(during.nodes.delta)
|
|
111
|
+
&& dim(during.nodes.epsilon) && dim(during.nodes.zeta),
|
|
112
|
+
releaseRestoresEdges: Math.abs(after.edges['alpha → beta'] - baseEdgeOp) < 0.05
|
|
113
|
+
&& Math.abs(after.edges['gamma → delta'] - baseEdgeOp) < 0.05,
|
|
114
|
+
releaseRestoresNodes: bright(after.nodes.alpha) && bright(after.nodes.gamma) && bright(after.nodes.epsilon),
|
|
115
|
+
};
|
|
116
|
+
const ok = Object.values(results).every(Boolean);
|
|
117
|
+
console.log(`${ok ? '✅' : '❌'} recent-row hover:`, JSON.stringify(results),
|
|
118
|
+
`\n baseline α→β=${baseEdgeOp}`,
|
|
119
|
+
`\n during α→β=${eAB} γ→δ=${eGD} ε→ζ=${eEZ}`,
|
|
120
|
+
`\n during nodes=`, during.nodes,
|
|
121
|
+
`\n after edges=`, after.edges);
|
|
122
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* Round 45 verification: recent-signal panel shows "no flow yet" placeholder
|
|
2
|
+
* when flowLinks is empty; renders normal link rows when populated. */
|
|
3
|
+
import { chromium } from 'playwright';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
7
|
+
const browser = await chromium.launch({ headless: true });
|
|
8
|
+
|
|
9
|
+
async function probe({ msgs }) {
|
|
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 = ['a', 'b'].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
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
|
|
31
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
32
|
+
const page = await ctx.newPage();
|
|
33
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
34
|
+
await page.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 30000 });
|
|
35
|
+
await page.waitForTimeout(500);
|
|
36
|
+
const data = await page.evaluate(() => {
|
|
37
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
38
|
+
const empty = svg.querySelector('text[data-recent-signal-empty]');
|
|
39
|
+
// count flow-link description rows in the recent-signal panel (translate(16,16))
|
|
40
|
+
const panel = [...svg.querySelectorAll(':scope > g[transform^="translate(16, 16)"]')][0];
|
|
41
|
+
const linkRows = panel ? [...panel.querySelectorAll('text')].filter(t => /->.*\/.*\//.test(t.textContent || '')).length : 0;
|
|
42
|
+
return { emptyShown: !!empty, emptyText: empty?.textContent || null, linkRows };
|
|
43
|
+
});
|
|
44
|
+
await ctx.close();
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const noFlow = await probe({ msgs: [] });
|
|
49
|
+
const withFlow = await probe({ msgs: [
|
|
50
|
+
{ from_alias: 'a', to_alias: 'b', content: 'hello', created_at: new Date().toISOString() },
|
|
51
|
+
] });
|
|
52
|
+
await browser.close();
|
|
53
|
+
|
|
54
|
+
const results = {
|
|
55
|
+
emptyShowsPlaceholder: noFlow.emptyShown === true && /no flow yet/i.test(noFlow.emptyText || ''),
|
|
56
|
+
emptyHidesLinkRows: noFlow.linkRows === 0,
|
|
57
|
+
populatedHidesPlaceholder: withFlow.emptyShown === false,
|
|
58
|
+
populatedShowsLinkRows: withFlow.linkRows >= 1,
|
|
59
|
+
};
|
|
60
|
+
const ok = Object.values(results).every(Boolean);
|
|
61
|
+
console.log(`${ok ? '✅' : '❌'} recent-signal empty state:`, JSON.stringify(results),
|
|
62
|
+
`\n noFlow=`, noFlow, `\n withFlow=`, withFlow);
|
|
63
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* Round 36 verification: prefers-reduced-motion disables all SMIL
|
|
2
|
+
* animations in the topology. Round 29 zeroed CSS via media query;
|
|
3
|
+
* SVG <animate> / <animateMotion> elements need JS to opt out and
|
|
4
|
+
* this round wires that via the useReducedMotion hook. */
|
|
5
|
+
import { chromium } from 'playwright';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
|
|
8
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
9
|
+
const browser = await chromium.launch({ headless: true });
|
|
10
|
+
|
|
11
|
+
async function probe({ reduce, layout }) {
|
|
12
|
+
const ctx = await browser.newContext({
|
|
13
|
+
viewport: { width: 1280, height: 1600 },
|
|
14
|
+
reducedMotion: reduce ? 'reduce' : 'no-preference',
|
|
15
|
+
});
|
|
16
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
17
|
+
await ctx.addInitScript((lay) => {
|
|
18
|
+
try {
|
|
19
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
20
|
+
localStorage.setItem('anet-topo-layout', lay);
|
|
21
|
+
localStorage.setItem('anet-topo-view', JSON.stringify({ zoom: 1, x: 0, y: 0 }));
|
|
22
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
23
|
+
} catch {}
|
|
24
|
+
}, layout);
|
|
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
|
+
// Include a 'working' status so the working pulse path renders.
|
|
31
|
+
const sessions = [
|
|
32
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null,
|
|
33
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
34
|
+
{ alias: 'idl', status: 'idle', network_id: nid, project_dir: null,
|
|
35
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
36
|
+
];
|
|
37
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
38
|
+
});
|
|
39
|
+
// Inject a tiny message so a flow link + particle render.
|
|
40
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
|
|
41
|
+
{ from_alias: 'wkr', to_alias: 'idl', content: 'hi', created_at: fresh },
|
|
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 === 2, { timeout: 30000 });
|
|
47
|
+
await page.waitForTimeout(600);
|
|
48
|
+
// Click a node so the click ripple is invoked (it should NOT render under reduced motion).
|
|
49
|
+
await page.locator('g[data-node="wkr"]').click().catch(() => {});
|
|
50
|
+
await page.waitForTimeout(120);
|
|
51
|
+
const counts = await page.evaluate(() => {
|
|
52
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
53
|
+
return {
|
|
54
|
+
animate: svg.querySelectorAll('animate').length,
|
|
55
|
+
animateMotion: svg.querySelectorAll('animateMotion').length,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
await ctx.close();
|
|
59
|
+
return counts;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Baseline (no reduce) — many SMIL elements expected.
|
|
63
|
+
const normalRing = await probe({ reduce: false, layout: 'ring' });
|
|
64
|
+
// Reduced motion — every SMIL element should be gone.
|
|
65
|
+
const reducedRing = await probe({ reduce: true, layout: 'ring' });
|
|
66
|
+
const reducedGrid = await probe({ reduce: true, layout: 'grid' });
|
|
67
|
+
|
|
68
|
+
await browser.close();
|
|
69
|
+
const results = {
|
|
70
|
+
baselineHasAnimations: normalRing.animate + normalRing.animateMotion > 0,
|
|
71
|
+
reducedRingDropsAll: reducedRing.animate === 0 && reducedRing.animateMotion === 0,
|
|
72
|
+
reducedGridDropsAll: reducedGrid.animate === 0 && reducedGrid.animateMotion === 0,
|
|
73
|
+
};
|
|
74
|
+
const ok = Object.values(results).every(Boolean);
|
|
75
|
+
console.log(`${ok ? '✅' : '❌'} reduced motion:`, JSON.stringify(results),
|
|
76
|
+
`\n normalRing=`, normalRing, `\n reducedRing=`, reducedRing, `\n reducedGrid=`, reducedGrid);
|
|
77
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Round 79 verification: header working + online count chips become
|
|
2
|
+
* hover affordances (sister to R77 active-links chip).
|
|
3
|
+
* - Hover "N working" → setHoveredStatus('working') → idle + offline
|
|
4
|
+
* nodes dim, working stays bright. Same outcome as R55 legend row
|
|
5
|
+
* hover, just from the chip surface.
|
|
6
|
+
* - Hover "N online" with working present → same as above.
|
|
7
|
+
* - Mouseleave restores baseline.
|
|
8
|
+
*
|
|
9
|
+
* Fleet: 1 working (wkr), 2 idle (idl1, idl2), 1 offline (off).
|
|
10
|
+
*/
|
|
11
|
+
import { chromium } from 'playwright';
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
|
|
14
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
15
|
+
const browser = await chromium.launch({ headless: true });
|
|
16
|
+
|
|
17
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
18
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
19
|
+
await ctx.addInitScript(() => {
|
|
20
|
+
try {
|
|
21
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
22
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
23
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
24
|
+
sessionStorage.removeItem('anet-topo-pinned-status');
|
|
25
|
+
sessionStorage.removeItem('anet-topo-pinned-group');
|
|
26
|
+
} catch {}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
30
|
+
const stale = new Date(Date.now() - 30 * 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: 'idl1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
38
|
+
{ alias: 'idl2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
39
|
+
{ alias: 'off', status: 'offline', network_id: nid, project_dir: null, created_at: stale, updated_at: stale, last_seen_at: stale },
|
|
40
|
+
];
|
|
41
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
42
|
+
});
|
|
43
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
44
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
45
|
+
const page = await ctx.newPage();
|
|
46
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
47
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
48
|
+
await page.waitForTimeout(500);
|
|
49
|
+
|
|
50
|
+
const opacities = () => page.evaluate(() => {
|
|
51
|
+
const o = {};
|
|
52
|
+
for (const a of ['wkr', 'idl1', 'idl2', 'off']) {
|
|
53
|
+
const g = document.querySelector(`g[data-node="${a}"]`);
|
|
54
|
+
o[a] = g ? +(g.style.opacity || '1') : null;
|
|
55
|
+
}
|
|
56
|
+
return o;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const before = await opacities();
|
|
60
|
+
|
|
61
|
+
// Hover working chip
|
|
62
|
+
await page.locator('[data-working-chip]').hover();
|
|
63
|
+
await page.waitForTimeout(250);
|
|
64
|
+
const onWorking = await opacities();
|
|
65
|
+
|
|
66
|
+
// Move away
|
|
67
|
+
await page.mouse.move(10, 10);
|
|
68
|
+
await page.waitForTimeout(250);
|
|
69
|
+
const afterWorking = await opacities();
|
|
70
|
+
|
|
71
|
+
// Hover online chip (working present → routes to 'working')
|
|
72
|
+
await page.locator('[data-online-chip]').hover();
|
|
73
|
+
await page.waitForTimeout(250);
|
|
74
|
+
const onOnline = await opacities();
|
|
75
|
+
|
|
76
|
+
await page.mouse.move(10, 10);
|
|
77
|
+
await page.waitForTimeout(250);
|
|
78
|
+
const afterOnline = 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
|
+
working_keepsWkrBright: bright(onWorking.wkr),
|
|
87
|
+
working_dimsIdle: dim(onWorking.idl1) && dim(onWorking.idl2),
|
|
88
|
+
working_dimsOffline: dim(onWorking.off),
|
|
89
|
+
workingLeave_restoresAll: bright(afterWorking.wkr) && bright(afterWorking.idl1),
|
|
90
|
+
online_keepsWkrBright: bright(onOnline.wkr), // routes to working filter
|
|
91
|
+
online_dimsIdle: dim(onOnline.idl1) && dim(onOnline.idl2),
|
|
92
|
+
onlineLeave_restoresAll: bright(afterOnline.wkr) && bright(afterOnline.idl1),
|
|
93
|
+
};
|
|
94
|
+
const ok = Object.values(results).every(Boolean);
|
|
95
|
+
console.log(`${ok ? '✅' : '❌'} status-chip hover:`, JSON.stringify(results),
|
|
96
|
+
`\n before=`, before,
|
|
97
|
+
`\n onWorking=`, onWorking,
|
|
98
|
+
`\n afterWorking=`, afterWorking,
|
|
99
|
+
`\n onOnline=`, onOnline,
|
|
100
|
+
`\n afterOnline=`, afterOnline);
|
|
101
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* Round 54 verification: tier-radius guide rings render at the active
|
|
2
|
+
* tier radii only, picked from the online node count.
|
|
3
|
+
* - N ≤ 8 → 1 ring at r=220
|
|
4
|
+
* - 9-14 → 2 rings at r=175 and r=260
|
|
5
|
+
* - N > 14 → 3 rings at r=145, 215, 285
|
|
6
|
+
* - Grid layout has NO tier rings (they're ring-only).
|
|
7
|
+
* Identified via the `data-tier-ring` attribute the implementation
|
|
8
|
+
* stamps on each guide circle. */
|
|
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(layout, count) {
|
|
16
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 1500 } });
|
|
17
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
18
|
+
await ctx.addInitScript((layout) => {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
21
|
+
localStorage.setItem('anet-topo-layout', layout);
|
|
22
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
23
|
+
} catch {}
|
|
24
|
+
}, layout);
|
|
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 = Array.from({ length: count }).map((_, i) => ({
|
|
31
|
+
alias: `n${i.toString().padStart(2, '0')}`,
|
|
32
|
+
status: 'idle', 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
|
+
});
|
|
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(c => document.querySelectorAll('g[data-node]').length === c, count, { timeout: 30000 });
|
|
42
|
+
await page.waitForTimeout(400);
|
|
43
|
+
const radii = await page.evaluate(() => {
|
|
44
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
45
|
+
return [...svg.querySelectorAll('circle[data-tier-ring]')]
|
|
46
|
+
.map(c => +c.getAttribute('data-tier-ring'))
|
|
47
|
+
.sort((a, b) => a - b);
|
|
48
|
+
});
|
|
49
|
+
await ctx.close();
|
|
50
|
+
return radii;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const single = await probe('ring', 6);
|
|
54
|
+
const dual = await probe('ring', 12);
|
|
55
|
+
const triple = await probe('ring', 22);
|
|
56
|
+
const grid = await probe('grid', 6);
|
|
57
|
+
|
|
58
|
+
await browser.close();
|
|
59
|
+
const eq = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
|
|
60
|
+
const results = {
|
|
61
|
+
singleHas220: eq(single, [220]),
|
|
62
|
+
dualHas175And260: eq(dual, [175, 260]),
|
|
63
|
+
tripleHas145_215_285: eq(triple, [145, 215, 285]),
|
|
64
|
+
gridHasNoTierRings: eq(grid, []),
|
|
65
|
+
};
|
|
66
|
+
const ok = Object.values(results).every(Boolean);
|
|
67
|
+
console.log(`${ok ? '✅' : '❌'} tier rings:`, JSON.stringify(results),
|
|
68
|
+
`\n single=${JSON.stringify(single)} dual=${JSON.stringify(dual)} triple=${JSON.stringify(triple)} grid=${JSON.stringify(grid)}`);
|
|
69
|
+
process.exit(ok ? 0 : 1);
|