@sleep2agi/agent-network-dashboard 0.4.6-preview.12 → 0.4.6-preview.121
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 +4 -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.hnfdve7~422.js +7 -0
- package/.next/static/chunks/001t1axy5kvz8.css +2 -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/0a-6zp8m0ux5..js +1 -0
- package/.next/static/chunks/0ekib0be3t9i5.js +1 -0
- package/.next/static/chunks/0glmrdj9112tc.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/0x8x.29hkju5a.js +1 -0
- package/.next/static/chunks/106~a5kttxze-.js +4 -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 +94 -0
- package/app/components/HelpOverlay.tsx +76 -0
- package/app/components/ServersDrawer.tsx +206 -0
- package/app/components/TopoGraph.tsx +2181 -163
- package/app/components/utils.ts +8 -7
- package/app/globals.css +19 -1
- 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-active-links-tooltip-test.mjs +79 -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-cmdk-vendor-clear-test.mjs +133 -0
- package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -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-count-badge-test.mjs +84 -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-endpoint-ring-test.mjs +93 -0
- package/scripts/topo-esc-clears-pin-test.mjs +98 -0
- package/scripts/topo-filter-pill-aliases-test.mjs +121 -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-label-hover-test.mjs +119 -0
- package/scripts/topo-group-label-tint-test.mjs +99 -0
- package/scripts/topo-group-label-title-test.mjs +83 -0
- package/scripts/topo-group-pin-test.mjs +119 -0
- package/scripts/topo-group-status-pips-test.mjs +103 -0
- package/scripts/topo-groupbox-live-test.mjs +100 -0
- package/scripts/topo-groupbox-test.mjs +4 -2
- package/scripts/topo-help-overlay-r109-test.mjs +67 -0
- 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-help-overlay-r87-test.mjs +64 -0
- package/scripts/topo-help-overlay-r91-test.mjs +65 -0
- package/scripts/topo-hub-busyness-test.mjs +100 -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-count-test.mjs +88 -0
- package/scripts/topo-legend-hover-test.mjs +113 -0
- package/scripts/topo-legend-panel-header-test.mjs +83 -0
- package/scripts/topo-legend-pin-test.mjs +128 -0
- package/scripts/topo-legend-row-tint-test.mjs +95 -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-node-title-test.mjs +101 -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-particle-stagger-test.mjs +82 -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-pressure-seg-tooltip-test.mjs +98 -0
- package/scripts/topo-pulse-rate-test.mjs +71 -0
- package/scripts/topo-recent-panel-count-test.mjs +72 -0
- package/scripts/topo-recent-row-hover-test.mjs +122 -0
- package/scripts/topo-recent-row-tint-test.mjs +91 -0
- package/scripts/topo-recent-row-ts-test.mjs +92 -0
- package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -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-status-chip-tooltip-test.mjs +95 -0
- package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
- package/scripts/topo-tier-ring-test.mjs +69 -0
- package/scripts/topo-tier-ring-tint-test.mjs +110 -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-vendor-letter-tooltip-test.mjs +92 -0
- package/scripts/topo-vendor-pill-test.mjs +125 -0
- package/scripts/topo-vendor-pin-test.mjs +125 -0
- package/scripts/topo-wheel-zoom-test.mjs +79 -0
- package/scripts/topo-working-halo-breath-test.mjs +86 -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/0cj5qr_a8dzky.js +0 -7
- package/.next/static/chunks/0dttftqqehpdj.js +0 -1
- package/.next/static/chunks/0elalb9n_.~di.js +0 -1
- package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
- package/.next/static/chunks/0ja71r81z59di.js +0 -1
- package/.next/static/chunks/0jl6_w50zh4l5.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/0z6mgaoe8c89-.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/11y8xsp9mn.m8.css +0 -2
- /package/.next/static/{irozqkI_mD2Ls10yQ_4yG → fh5pUqgTFNt162QarYePi}/_buildManifest.js +0 -0
- /package/.next/static/{irozqkI_mD2Ls10yQ_4yG → fh5pUqgTFNt162QarYePi}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{irozqkI_mD2Ls10yQ_4yG → fh5pUqgTFNt162QarYePi}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* Round 15 verification: at nodeScale=S the label card + fonts step down
|
|
2
|
+
* one tier (rect 100×42 → 88×36, alias 12 → 11, sub 9 → 8); M and L keep
|
|
3
|
+
* the existing sizes. */
|
|
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
|
+
async function measure(scaleVal) {
|
|
11
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
|
12
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
13
|
+
await ctx.addInitScript((s) => {
|
|
14
|
+
try {
|
|
15
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
16
|
+
localStorage.removeItem('anet-brand');
|
|
17
|
+
localStorage.removeItem('anet-topo-view');
|
|
18
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
19
|
+
localStorage.setItem('anet-topo-nodescale', s);
|
|
20
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
21
|
+
} catch {}
|
|
22
|
+
}, scaleVal);
|
|
23
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
24
|
+
const r = await route.fetch();
|
|
25
|
+
const b = await r.json();
|
|
26
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
27
|
+
// 4 nodes only → sparse, so the full-card label path renders.
|
|
28
|
+
const sessions = ['lab1', 'lab2', 'lab3', 'lab4'].map(a => ({
|
|
29
|
+
alias: a, status: 'idle', network_id: nid, project_dir: null,
|
|
30
|
+
created_at: '2026-05-15T00:00:00Z', updated_at: '2026-05-15T00:00:00Z',
|
|
31
|
+
last_seen_at: new Date().toISOString(),
|
|
32
|
+
}));
|
|
33
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
34
|
+
});
|
|
35
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
36
|
+
const page = await ctx.newPage();
|
|
37
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
38
|
+
await page.waitForFunction(() => {
|
|
39
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
40
|
+
return !!svg && svg.querySelectorAll('g[data-node]').length === 4;
|
|
41
|
+
}, { timeout: 30000 }).catch(() => {});
|
|
42
|
+
await page.waitForTimeout(400);
|
|
43
|
+
const m = await page.$eval('g[data-node="lab1"]', g => {
|
|
44
|
+
// label card = the <g transform="translate(...)" > inside the node <g>
|
|
45
|
+
// that contains the rect+two texts; pick the rect we just changed.
|
|
46
|
+
const labelG = [...g.querySelectorAll('g')].find(x => x.querySelector('rect') && x.querySelectorAll('text').length === 2);
|
|
47
|
+
if (!labelG) return null;
|
|
48
|
+
const rect = labelG.querySelector('rect');
|
|
49
|
+
const texts = labelG.querySelectorAll('text');
|
|
50
|
+
return {
|
|
51
|
+
w: +rect.getAttribute('width'),
|
|
52
|
+
h: +rect.getAttribute('height'),
|
|
53
|
+
aliasFs: +texts[0].getAttribute('font-size'),
|
|
54
|
+
subFs: +texts[1].getAttribute('font-size'),
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
await ctx.close();
|
|
58
|
+
return m;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const S = await measure('0.7');
|
|
62
|
+
const M = await measure('0.84');
|
|
63
|
+
const L = await measure('1');
|
|
64
|
+
await browser.close();
|
|
65
|
+
|
|
66
|
+
const results = {
|
|
67
|
+
smallCardW: S?.w === 88,
|
|
68
|
+
smallCardH: S?.h === 36,
|
|
69
|
+
smallAliasFs: S?.aliasFs === 11,
|
|
70
|
+
smallSubFs: S?.subFs === 8,
|
|
71
|
+
mediumKeepsW: M?.w === 100,
|
|
72
|
+
mediumKeepsAliasFs: M?.aliasFs === 12,
|
|
73
|
+
largeKeepsW: L?.w === 100,
|
|
74
|
+
largeKeepsAliasFs: L?.aliasFs === 12,
|
|
75
|
+
};
|
|
76
|
+
const ok = Object.values(results).every(Boolean);
|
|
77
|
+
console.log(`${ok ? '✅' : '❌'} label scale:`, JSON.stringify(results), 'S=', S, 'M=', M, 'L=', L);
|
|
78
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* Round 34 verification: offline node tooltip carries "last seen: <relative>";
|
|
2
|
+
* online nodes skip the line. SQL-style timestamp parsing is exercised in
|
|
3
|
+
* production (CommHub format) but the existing isGhost filter has a
|
|
4
|
+
* separate TZ-sensitivity issue that ghosts SQL-without-Z entries on
|
|
5
|
+
* non-UTC browsers — out of Round 34 scope. The helper supports both
|
|
6
|
+
* formats; the SQL path is covered by manual inspection. */
|
|
7
|
+
import { chromium } from 'playwright';
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
|
|
10
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
11
|
+
const browser = await chromium.launch({ headless: true });
|
|
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
|
+
// One online, two offline at different ages (both ISO).
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const isoMin = (m) => new Date(now - m * 60 * 1000).toISOString();
|
|
24
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
25
|
+
const r = await route.fetch();
|
|
26
|
+
const b = await r.json();
|
|
27
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
28
|
+
const fresh = isoMin(0.1);
|
|
29
|
+
const sessions = [
|
|
30
|
+
{ alias: 'live', status: 'idle', network_id: nid, project_dir: null,
|
|
31
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
32
|
+
{ alias: 'off6m', status: 'offline', network_id: nid, project_dir: null,
|
|
33
|
+
created_at: fresh, updated_at: fresh, last_seen_at: isoMin(6) },
|
|
34
|
+
{ alias: 'off45m', status: 'offline', network_id: nid, project_dir: null,
|
|
35
|
+
created_at: fresh, updated_at: fresh, last_seen_at: isoMin(45) },
|
|
36
|
+
];
|
|
37
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
38
|
+
});
|
|
39
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
40
|
+
const page = await ctx.newPage();
|
|
41
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
42
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
|
|
43
|
+
await page.waitForTimeout(400);
|
|
44
|
+
|
|
45
|
+
const titleOf = alias => page.$eval(`g[data-node="${alias}"] title`, el => el.textContent);
|
|
46
|
+
const live = await titleOf('live');
|
|
47
|
+
const off6m = await titleOf('off6m');
|
|
48
|
+
const off45m = await titleOf('off45m');
|
|
49
|
+
|
|
50
|
+
await browser.close();
|
|
51
|
+
const results = {
|
|
52
|
+
liveSkipsLastSeen: !/last seen:/.test(live),
|
|
53
|
+
off6mShows6mAgo: /last seen: [56]m ago/.test(off6m), // 6m exactly, ±1 minute slack
|
|
54
|
+
off45mShows45mAgo: /last seen: 4[45]m ago/.test(off45m), // 45m exactly, ±1 minute slack
|
|
55
|
+
};
|
|
56
|
+
const ok = Object.values(results).every(Boolean);
|
|
57
|
+
console.log(`${ok ? '✅' : '❌'} last-seen tooltip:`, JSON.stringify(results),
|
|
58
|
+
`\n live=${JSON.stringify(live)}\n off6m=${JSON.stringify(off6m)}\n off45m=${JSON.stringify(off45m)}`);
|
|
59
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* Round 32 verification: `l` (and `L`) keyboard shortcut toggles
|
|
2
|
+
* ring ↔ grid; titles + Help overlay surface the hint; input-focus
|
|
3
|
+
* suppression still blocks it. */
|
|
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
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
|
|
10
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
11
|
+
await ctx.addInitScript(() => {
|
|
12
|
+
try {
|
|
13
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
14
|
+
localStorage.setItem('anet-topo-layout', 'grid'); // start on grid
|
|
15
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
16
|
+
} catch {}
|
|
17
|
+
});
|
|
18
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
19
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
20
|
+
const r = await route.fetch();
|
|
21
|
+
const b = await r.json();
|
|
22
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
23
|
+
const sessions = ['a', 'b', 'c'].map(a => ({
|
|
24
|
+
alias: a, status: 'idle', network_id: nid, project_dir: null,
|
|
25
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
26
|
+
}));
|
|
27
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
28
|
+
});
|
|
29
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
30
|
+
const page = await ctx.newPage();
|
|
31
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
32
|
+
await page.waitForSelector('button[aria-label="Topology layout"], button[title*="Ring layout"]', { timeout: 30000 });
|
|
33
|
+
await page.waitForTimeout(400);
|
|
34
|
+
|
|
35
|
+
const layoutPressed = () => page.evaluate(() => {
|
|
36
|
+
const ring = document.querySelector('button[title^="Ring layout"]');
|
|
37
|
+
const grid = document.querySelector('button[title^="Grid layout"]');
|
|
38
|
+
return { ring: ring?.getAttribute('aria-pressed'), grid: grid?.getAttribute('aria-pressed') };
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const initial = await layoutPressed();
|
|
42
|
+
|
|
43
|
+
// Press `l` → should toggle to ring
|
|
44
|
+
await page.keyboard.press('l');
|
|
45
|
+
await page.waitForTimeout(150);
|
|
46
|
+
const afterL = await layoutPressed();
|
|
47
|
+
|
|
48
|
+
// Press `L` (shift+l) → toggle back to grid
|
|
49
|
+
await page.keyboard.press('Shift+L');
|
|
50
|
+
await page.waitForTimeout(150);
|
|
51
|
+
const afterShiftL = await layoutPressed();
|
|
52
|
+
|
|
53
|
+
// Titles carry the hint
|
|
54
|
+
const titles = await page.evaluate(() => ({
|
|
55
|
+
ring: document.querySelector('button[title^="Ring layout"]')?.getAttribute('title') || '',
|
|
56
|
+
grid: document.querySelector('button[title^="Grid layout"]')?.getAttribute('title') || '',
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
// Help overlay lists the shortcut
|
|
60
|
+
await page.keyboard.press('?');
|
|
61
|
+
await page.waitForTimeout(200);
|
|
62
|
+
const helpHasLayout = await page.evaluate(() => {
|
|
63
|
+
const heading = [...document.querySelectorAll('[role="dialog"] div')].find(el => el.textContent === 'Topology canvas');
|
|
64
|
+
if (!heading) return false;
|
|
65
|
+
return /Toggle layout/i.test(heading.parentElement?.textContent || '');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Input-focus suppression — Cmd+K input, press l, no toggle
|
|
69
|
+
await page.keyboard.press('Escape');
|
|
70
|
+
await page.keyboard.press('Meta+k');
|
|
71
|
+
await page.waitForTimeout(200);
|
|
72
|
+
const palette = page.locator('input[placeholder*="Search"], input[type="search"], [role="dialog"] input').first();
|
|
73
|
+
await palette.focus().catch(() => {});
|
|
74
|
+
const beforeFocusToggle = await layoutPressed();
|
|
75
|
+
await page.keyboard.press('l');
|
|
76
|
+
await page.waitForTimeout(150);
|
|
77
|
+
const afterFocusToggle = await layoutPressed();
|
|
78
|
+
await page.keyboard.press('Escape');
|
|
79
|
+
|
|
80
|
+
await browser.close();
|
|
81
|
+
const results = {
|
|
82
|
+
startsGrid: initial.grid === 'true' && initial.ring === 'false',
|
|
83
|
+
lTogglesToRing: afterL.ring === 'true' && afterL.grid === 'false',
|
|
84
|
+
shiftLTogglesBack: afterShiftL.grid === 'true' && afterShiftL.ring === 'false',
|
|
85
|
+
titlesPresent: /l to toggle/i.test(titles.ring) && /l to toggle/i.test(titles.grid),
|
|
86
|
+
helpListsLayout: helpHasLayout,
|
|
87
|
+
inputFocusSuppressed: beforeFocusToggle.grid === afterFocusToggle.grid && beforeFocusToggle.ring === afterFocusToggle.ring,
|
|
88
|
+
};
|
|
89
|
+
const ok = Object.values(results).every(Boolean);
|
|
90
|
+
console.log(`${ok ? '✅' : '❌'} layout key:`, JSON.stringify(results), `titles=`, titles);
|
|
91
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* Round 95 verification: legend rows show live counts. Fleet = 3
|
|
2
|
+
* working + 2 idle + 1 offline. Each row's data-legend-count
|
|
3
|
+
* attribute matches the expected number. Pinning a row brightens
|
|
4
|
+
* its count alongside the label.
|
|
5
|
+
*
|
|
6
|
+
* The count is right-aligned at x=215 (after the flow-arrow swatch
|
|
7
|
+
* which spans x=140-196 on the offline row), pointerEvents:none so
|
|
8
|
+
* the row's R55 hover affordance still hits.
|
|
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
|
+
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
|
+
sessionStorage.removeItem('anet-topo-pinned-status');
|
|
23
|
+
} catch {}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
27
|
+
const stale10 = new Date(Date.now() - 10 * 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 mk = (alias, status, ts) => ({
|
|
33
|
+
alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
34
|
+
network_id: nid, project_dir: null,
|
|
35
|
+
created_at: ts, updated_at: ts, last_seen_at: ts,
|
|
36
|
+
});
|
|
37
|
+
const sessions = [
|
|
38
|
+
mk('wkr1', 'working', fresh),
|
|
39
|
+
mk('wkr2', 'working', fresh),
|
|
40
|
+
mk('wkr3', 'working', fresh),
|
|
41
|
+
mk('idl1', 'idle', fresh),
|
|
42
|
+
mk('idl2', 'idle', fresh),
|
|
43
|
+
mk('off1', 'offline', stale10),
|
|
44
|
+
];
|
|
45
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
46
|
+
});
|
|
47
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
48
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
49
|
+
const page = await ctx.newPage();
|
|
50
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
51
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 6, { timeout: 30000 });
|
|
52
|
+
await page.waitForTimeout(400);
|
|
53
|
+
|
|
54
|
+
const counts = () => page.evaluate(() => ({
|
|
55
|
+
working: document.querySelector('[data-legend-count="working"]')?.textContent,
|
|
56
|
+
idle: document.querySelector('[data-legend-count="idle"]')?.textContent,
|
|
57
|
+
offline: document.querySelector('[data-legend-count="offline"]')?.textContent,
|
|
58
|
+
}));
|
|
59
|
+
const opacities = () => page.evaluate(() => ({
|
|
60
|
+
working: +(document.querySelector('[data-legend-count="working"]')?.getAttribute('opacity') || '0'),
|
|
61
|
+
idle: +(document.querySelector('[data-legend-count="idle"]')?.getAttribute('opacity') || '0'),
|
|
62
|
+
offline: +(document.querySelector('[data-legend-count="offline"]')?.getAttribute('opacity') || '0'),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
const c = await counts();
|
|
66
|
+
const opsRest = await opacities();
|
|
67
|
+
|
|
68
|
+
// Pin the working row — its count should brighten.
|
|
69
|
+
await page.locator('[data-legend-status="working"]').click();
|
|
70
|
+
await page.waitForTimeout(250);
|
|
71
|
+
const opsPinned = await opacities();
|
|
72
|
+
|
|
73
|
+
await browser.close();
|
|
74
|
+
|
|
75
|
+
const results = {
|
|
76
|
+
workingCount_3: c.working === '3',
|
|
77
|
+
idleCount_2: c.idle === '2',
|
|
78
|
+
offlineCount_1: c.offline === '1',
|
|
79
|
+
resting_dim: opsRest.working < 0.7 && opsRest.idle < 0.7 && opsRest.offline < 0.7,
|
|
80
|
+
pinnedWorking_bright: opsPinned.working >= 0.9,
|
|
81
|
+
pinnedWorking_othersUnchanged: opsPinned.idle < 0.7 && opsPinned.offline < 0.7,
|
|
82
|
+
};
|
|
83
|
+
const ok = Object.values(results).every(Boolean);
|
|
84
|
+
console.log(`${ok ? '✅' : '❌'} legend counts:`, JSON.stringify(results),
|
|
85
|
+
`\n counts=`, c,
|
|
86
|
+
`\n opsRest=`, opsRest,
|
|
87
|
+
`\n opsPinned=`, opsPinned);
|
|
88
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* Round 55 verification: hovering a legend status row dims nodes whose
|
|
2
|
+
* status doesn't match.
|
|
3
|
+
*
|
|
4
|
+
* Sessions (mix of statuses):
|
|
5
|
+
* wkr — working (status=working)
|
|
6
|
+
* idl1 — idle online (status=idle)
|
|
7
|
+
* idl2 — idle online (status=idle)
|
|
8
|
+
* off — offline (status=offline, last_seen old)
|
|
9
|
+
*
|
|
10
|
+
* Hover the "working node" row → wkr stays bright (~1.0), the rest dim
|
|
11
|
+
* (~0.28). Hover "online idle" → idl1/idl2 stay, wkr+off dim. Hover
|
|
12
|
+
* "offline / no SSE" → off stays, the three online dim. Release →
|
|
13
|
+
* all back to base.
|
|
14
|
+
*/
|
|
15
|
+
import { chromium } from 'playwright';
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
|
|
18
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
19
|
+
const browser = await chromium.launch({ headless: true });
|
|
20
|
+
|
|
21
|
+
// Dashboard chrome pushes the SVG ~830 px down at width 1400, so the
|
|
22
|
+
// legend's offline row sits at screen y≈900 — below a 900-px viewport.
|
|
23
|
+
// 1500 px tall gives a comfortable margin for all three rows.
|
|
24
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
25
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
26
|
+
await ctx.addInitScript(() => {
|
|
27
|
+
try {
|
|
28
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
29
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
30
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
31
|
+
} catch {}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
35
|
+
const stale = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
36
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
37
|
+
const r = await route.fetch();
|
|
38
|
+
const b = await r.json();
|
|
39
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
40
|
+
const sessions = [
|
|
41
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
42
|
+
{ alias: 'idl1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
43
|
+
{ alias: 'idl2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
44
|
+
{ alias: 'off', status: 'offline', network_id: nid, project_dir: null, created_at: stale, updated_at: stale, last_seen_at: stale },
|
|
45
|
+
];
|
|
46
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
47
|
+
});
|
|
48
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
49
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
50
|
+
const page = await ctx.newPage();
|
|
51
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
52
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
53
|
+
await page.waitForTimeout(500);
|
|
54
|
+
|
|
55
|
+
const opacities = () => page.evaluate(() => {
|
|
56
|
+
const o = {};
|
|
57
|
+
for (const a of ['wkr', 'idl1', 'idl2', 'off']) {
|
|
58
|
+
const g = document.querySelector(`g[data-node="${a}"]`);
|
|
59
|
+
o[a] = g ? +(g.style.opacity || '1') : null;
|
|
60
|
+
}
|
|
61
|
+
return o;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const before = await opacities();
|
|
65
|
+
|
|
66
|
+
async function hoverRow(key) {
|
|
67
|
+
const target = await page.evaluate((k) => {
|
|
68
|
+
const g = document.querySelector(`g[data-legend-status="${k}"]`);
|
|
69
|
+
if (!g) return null;
|
|
70
|
+
const r = g.getBoundingClientRect();
|
|
71
|
+
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
|
72
|
+
}, key);
|
|
73
|
+
if (!target) throw new Error(`legend row ${key} not found`);
|
|
74
|
+
// jiggle from a neutral spot, then to the target
|
|
75
|
+
await page.mouse.move(10, 10);
|
|
76
|
+
await page.mouse.move(target.x, target.y);
|
|
77
|
+
await page.waitForTimeout(300);
|
|
78
|
+
return await opacities();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const onWorking = await hoverRow('working');
|
|
82
|
+
const onIdle = await hoverRow('idle');
|
|
83
|
+
const onOffline = await hoverRow('offline');
|
|
84
|
+
await page.mouse.move(10, 10);
|
|
85
|
+
await page.waitForTimeout(300);
|
|
86
|
+
const released = await opacities();
|
|
87
|
+
|
|
88
|
+
await browser.close();
|
|
89
|
+
|
|
90
|
+
const dim = (v) => v != null && v < 0.4;
|
|
91
|
+
const bright = (v) => v != null && v >= 0.55;
|
|
92
|
+
const results = {
|
|
93
|
+
before_baseline: bright(before.wkr) && bright(before.idl1) && bright(before.idl2),
|
|
94
|
+
working_keepsWorking: bright(onWorking.wkr),
|
|
95
|
+
working_dimsIdle: dim(onWorking.idl1) && dim(onWorking.idl2),
|
|
96
|
+
working_dimsOffline: dim(onWorking.off),
|
|
97
|
+
idle_keepsIdle: bright(onIdle.idl1) && bright(onIdle.idl2),
|
|
98
|
+
idle_dimsWorking: dim(onIdle.wkr),
|
|
99
|
+
idle_dimsOffline: dim(onIdle.off),
|
|
100
|
+
// Offline base opacity is 0.6 (R17), so "kept" means >= 0.55 which the
|
|
101
|
+
// bright() helper already covers — anything in [0.55, 1] counts.
|
|
102
|
+
offline_keepsOffline: bright(onOffline.off),
|
|
103
|
+
offline_dimsOnline: dim(onOffline.wkr) && dim(onOffline.idl1) && dim(onOffline.idl2),
|
|
104
|
+
released_restoresAll: bright(released.wkr) && bright(released.idl1) && bright(released.idl2),
|
|
105
|
+
};
|
|
106
|
+
const ok = Object.values(results).every(Boolean);
|
|
107
|
+
console.log(`${ok ? '✅' : '❌'} legend status hover:`, JSON.stringify(results),
|
|
108
|
+
`\n before=`, before,
|
|
109
|
+
`\n onWorking=`, onWorking,
|
|
110
|
+
`\n onIdle=`, onIdle,
|
|
111
|
+
`\n onOffline=`, onOffline,
|
|
112
|
+
`\n released=`, released);
|
|
113
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* Round 106 verification: legend panel gains a header matching the
|
|
2
|
+
* recent-signal panel's "recent signal · N flows" format. Same
|
|
3
|
+
* font/position vocabulary so the two side panels feel paired. The
|
|
4
|
+
* fleet count is right-aligned at x=215 in the accent colour.
|
|
5
|
+
*
|
|
6
|
+
* Sanity-checks:
|
|
7
|
+
* - "legend" title text exists in the legend panel
|
|
8
|
+
* - "N node(s)" count matches sessions.length
|
|
9
|
+
* - Singular vs plural ("1 node" vs "5 nodes") correct
|
|
10
|
+
* - Existing rows still render (hover affordance preserved)
|
|
11
|
+
*/
|
|
12
|
+
import { chromium } from 'playwright';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
|
|
15
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
16
|
+
const browser = await chromium.launch({ headless: true });
|
|
17
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
18
|
+
|
|
19
|
+
const probe = async (count) => {
|
|
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 { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
24
|
+
});
|
|
25
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
26
|
+
const r = await route.fetch();
|
|
27
|
+
const b = await r.json();
|
|
28
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
29
|
+
const sessions = [];
|
|
30
|
+
for (let i = 0; i < count; i++) {
|
|
31
|
+
sessions.push({
|
|
32
|
+
alias: `n${i}`, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
33
|
+
network_id: nid, project_dir: null,
|
|
34
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
38
|
+
});
|
|
39
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
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((n) => document.querySelectorAll('g[data-node]').length === n, count, { timeout: 30000 });
|
|
44
|
+
await page.waitForSelector('[data-legend-panel-count]', { timeout: 10000 });
|
|
45
|
+
await page.waitForTimeout(400);
|
|
46
|
+
|
|
47
|
+
const data = await page.evaluate(() => {
|
|
48
|
+
const headerCount = document.querySelector('[data-legend-panel-count]')?.textContent;
|
|
49
|
+
const headerTitle = (() => {
|
|
50
|
+
// Find a <text> with "legend" content in the legend panel <g>
|
|
51
|
+
const panel = document.querySelector('[data-topo-panel-elevation="legend"]')?.parentElement;
|
|
52
|
+
if (!panel) return null;
|
|
53
|
+
for (const t of panel.querySelectorAll('text')) {
|
|
54
|
+
if (t.textContent === 'legend') return t.textContent;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
})();
|
|
58
|
+
const legendRows = document.querySelectorAll('[data-legend-status]').length;
|
|
59
|
+
return { headerCount, headerTitle, legendRows };
|
|
60
|
+
});
|
|
61
|
+
await ctx.close();
|
|
62
|
+
return data;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const single = await probe(1);
|
|
66
|
+
const many = await probe(5);
|
|
67
|
+
await browser.close();
|
|
68
|
+
|
|
69
|
+
const results = {
|
|
70
|
+
single_hasTitle: single.headerTitle === 'legend',
|
|
71
|
+
single_count1Node: single.headerCount === '1 node',
|
|
72
|
+
many_hasTitle: many.headerTitle === 'legend',
|
|
73
|
+
many_count5Nodes: many.headerCount === '5 nodes',
|
|
74
|
+
many_rowsStillRender: many.legendRows >= 1, // depends on which states are present
|
|
75
|
+
// Sanity: singular vs plural
|
|
76
|
+
singular_correct: single.headerCount?.endsWith(' node'),
|
|
77
|
+
plural_correct: many.headerCount?.endsWith(' nodes'),
|
|
78
|
+
};
|
|
79
|
+
const ok = Object.values(results).every(Boolean);
|
|
80
|
+
console.log(`${ok ? '✅' : '❌'} legend panel header:`, JSON.stringify(results),
|
|
81
|
+
`\n single (1 node)=`, single,
|
|
82
|
+
`\n many (5 nodes)=`, many);
|
|
83
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/* Round 61 verification: legend rows also pin status filter on click.
|
|
2
|
+
* - Hovering still works (R55, transient release).
|
|
3
|
+
* - Clicking a row toggles pinnedStatus same as R60 pressure segments.
|
|
4
|
+
* - Pinned row's swatch gets an outer ring at r=8 (concentric, row colour).
|
|
5
|
+
* - Click same row again → release.
|
|
6
|
+
* - Sticky: pin survives mouse-move-away (vs R55 hover which clears).
|
|
7
|
+
* - The R60 pressure-segment pin and this share `pinnedStatus`, so
|
|
8
|
+
* clicking the legend then clicking the pressure-segment for a
|
|
9
|
+
* different status replaces (not stacks) the filter — verified by
|
|
10
|
+
* chain: legend.working → pressure.idle → only idle stays bright.
|
|
11
|
+
*/
|
|
12
|
+
import { chromium } from 'playwright';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
|
|
15
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
16
|
+
const browser = await chromium.launch({ headless: true });
|
|
17
|
+
|
|
18
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
19
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
20
|
+
await ctx.addInitScript(() => {
|
|
21
|
+
try {
|
|
22
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
23
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
24
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
25
|
+
} catch {}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
29
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
30
|
+
const r = await route.fetch();
|
|
31
|
+
const b = await r.json();
|
|
32
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
33
|
+
const sessions = [
|
|
34
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
35
|
+
{ alias: 'idl1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
36
|
+
{ alias: 'idl2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
37
|
+
];
|
|
38
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
39
|
+
});
|
|
40
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
41
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
42
|
+
const page = await ctx.newPage();
|
|
43
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
44
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
|
|
45
|
+
await page.waitForTimeout(500);
|
|
46
|
+
|
|
47
|
+
const opacities = () => page.evaluate(() => {
|
|
48
|
+
const o = {};
|
|
49
|
+
for (const a of ['wkr', 'idl1', 'idl2']) {
|
|
50
|
+
const g = document.querySelector(`g[data-node="${a}"]`);
|
|
51
|
+
o[a] = g ? +(g.style.opacity || '1') : null;
|
|
52
|
+
}
|
|
53
|
+
return o;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const before = await opacities();
|
|
57
|
+
|
|
58
|
+
// Click the WORKING legend row.
|
|
59
|
+
const workingLegend = page.locator('g[data-legend-status="working"]').first();
|
|
60
|
+
const workingExists = await workingLegend.count() > 0;
|
|
61
|
+
if (!workingExists) { console.log('❌ legend row[working] not found'); process.exit(1); }
|
|
62
|
+
await workingLegend.click({ force: true });
|
|
63
|
+
await page.waitForTimeout(250);
|
|
64
|
+
const afterPin = await opacities();
|
|
65
|
+
|
|
66
|
+
// Pin survives mouse move.
|
|
67
|
+
await page.mouse.move(10, 10);
|
|
68
|
+
await page.waitForTimeout(250);
|
|
69
|
+
const afterMouseMove = await opacities();
|
|
70
|
+
|
|
71
|
+
// Pinned row should carry an aria-pressed=true.
|
|
72
|
+
const pinnedAria = await page.evaluate(() => {
|
|
73
|
+
const g = document.querySelector('g[data-legend-status="working"]');
|
|
74
|
+
return g?.getAttribute('aria-pressed');
|
|
75
|
+
});
|
|
76
|
+
// And a concentric r=8 ring exists inside it.
|
|
77
|
+
const pinnedRingExists = await page.evaluate(() => {
|
|
78
|
+
const g = document.querySelector('g[data-legend-status="working"]');
|
|
79
|
+
if (!g) return false;
|
|
80
|
+
return [...g.querySelectorAll('circle')].some(c => c.getAttribute('r') === '8');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Switch pin via pressure segment (idle) — should REPLACE, not stack.
|
|
84
|
+
const idleSeg = page.locator('[data-pressure-seg="idle"]').first();
|
|
85
|
+
await idleSeg.click({ force: true });
|
|
86
|
+
await page.waitForTimeout(250);
|
|
87
|
+
const afterSwitch = await opacities();
|
|
88
|
+
|
|
89
|
+
// And the legend pin should release (concentric ring gone from working).
|
|
90
|
+
const workingRingGone = await page.evaluate(() => {
|
|
91
|
+
const g = document.querySelector('g[data-legend-status="working"]');
|
|
92
|
+
if (!g) return true;
|
|
93
|
+
return ![...g.querySelectorAll('circle')].some(c => c.getAttribute('r') === '8');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Click idle legend again to release pin. The click ALSO triggers a
|
|
97
|
+
// mouseenter that sets hoveredStatus='idle', which would keep the
|
|
98
|
+
// filter active (activeStatus = hoveredStatus ?? pinnedStatus). Move
|
|
99
|
+
// the mouse away to clear the hover so we observe the pin-only state.
|
|
100
|
+
await page.locator('g[data-legend-status="idle"]').first().click({ force: true });
|
|
101
|
+
await page.mouse.move(10, 10);
|
|
102
|
+
await page.waitForTimeout(250);
|
|
103
|
+
const afterRelease = await opacities();
|
|
104
|
+
|
|
105
|
+
await browser.close();
|
|
106
|
+
|
|
107
|
+
const bright = (v) => v != null && v >= 0.55;
|
|
108
|
+
const dim = (v) => v != null && v < 0.4;
|
|
109
|
+
const results = {
|
|
110
|
+
before_baseline: bright(before.wkr) && bright(before.idl1) && bright(before.idl2),
|
|
111
|
+
legendPin_keepsWorking: bright(afterPin.wkr),
|
|
112
|
+
legendPin_dimsIdle: dim(afterPin.idl1) && dim(afterPin.idl2),
|
|
113
|
+
legendPin_survivesMouse: bright(afterMouseMove.wkr) && dim(afterMouseMove.idl1),
|
|
114
|
+
legendPin_ariaPressed: pinnedAria === 'true',
|
|
115
|
+
legendPin_ringRendered: pinnedRingExists,
|
|
116
|
+
pressureSwitch_replaces: dim(afterSwitch.wkr) && bright(afterSwitch.idl1) && bright(afterSwitch.idl2),
|
|
117
|
+
pressureSwitch_clearsLegendRing: workingRingGone,
|
|
118
|
+
release_restoresAll: bright(afterRelease.wkr) && bright(afterRelease.idl1),
|
|
119
|
+
};
|
|
120
|
+
const ok = Object.values(results).every(Boolean);
|
|
121
|
+
console.log(`${ok ? '✅' : '❌'} legend pin:`, JSON.stringify(results),
|
|
122
|
+
`\n before=`, before,
|
|
123
|
+
`\n afterPin=`, afterPin,
|
|
124
|
+
`\n afterMouseMove=`, afterMouseMove,
|
|
125
|
+
`\n afterSwitch=`, afterSwitch,
|
|
126
|
+
`\n afterRelease=`, afterRelease,
|
|
127
|
+
`\n pinnedAria=${pinnedAria} ringRendered=${pinnedRingExists} workingRingGone=${workingRingGone}`);
|
|
128
|
+
process.exit(ok ? 0 : 1);
|