@sleep2agi/agent-network-dashboard 0.4.6-preview.16 → 0.4.6-preview.161
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 +41 -41
- 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/01lmbsd37fybu.js +1 -0
- package/.next/static/chunks/032~4k7il7hnk.css +2 -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/06fy8p2g0jbz1.js +4 -0
- package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
- package/.next/static/chunks/0ggj5ummw2a84.js +7 -0
- package/.next/static/chunks/0glmrdj9112tc.js +7 -0
- package/.next/static/chunks/0oehx47rte9ek.js +1 -0
- package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
- package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
- package/.next/static/chunks/10z.kc0g0jj8p.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/ChatPopover.tsx +38 -2
- package/app/components/CommandPalette.tsx +107 -0
- package/app/components/HelpOverlay.tsx +126 -0
- package/app/components/ServersDrawer.tsx +64 -27
- package/app/components/TopoGraph.tsx +3102 -237
- package/app/components/utils.ts +8 -7
- package/app/globals.css +46 -5
- 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-nav-test.mjs +117 -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-chat-target-breath-test.mjs +84 -0
- package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
- package/scripts/topo-chrome-a11y-test.mjs +107 -0
- package/scripts/topo-click-ripple-test.mjs +66 -0
- package/scripts/topo-cmdk-edge-clear-test.mjs +109 -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-badge-click-pin-test.mjs +94 -0
- package/scripts/topo-edge-badge-hot-test.mjs +115 -0
- package/scripts/topo-edge-badge-hover-test.mjs +85 -0
- package/scripts/topo-edge-count-badge-test.mjs +86 -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-pill-hot-test.mjs +129 -0
- package/scripts/topo-edge-pill-test.mjs +105 -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-a11y-test.mjs +113 -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-hover-lift-test.mjs +127 -0
- package/scripts/topo-groupbox-live-test.mjs +100 -0
- package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
- package/scripts/topo-groupbox-test.mjs +4 -2
- package/scripts/topo-help-overlay-r109-test.mjs +67 -0
- package/scripts/topo-help-overlay-r118-test.mjs +69 -0
- package/scripts/topo-help-overlay-r123-test.mjs +69 -0
- package/scripts/topo-help-overlay-r134-test.mjs +74 -0
- package/scripts/topo-help-overlay-r137-test.mjs +81 -0
- package/scripts/topo-help-overlay-r141-test.mjs +86 -0
- package/scripts/topo-help-overlay-r153-test.mjs +85 -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-hover-ring-test.mjs +82 -0
- package/scripts/topo-hub-title-test.mjs +93 -0
- package/scripts/topo-hub-working-count-test.mjs +99 -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-hover-lift-test.mjs +109 -0
- package/scripts/topo-legend-row-tint-test.mjs +95 -0
- package/scripts/topo-legend-row-title-test.mjs +102 -0
- package/scripts/topo-minimap-test.mjs +68 -0
- package/scripts/topo-mobile-chips-test.mjs +103 -0
- package/scripts/topo-node-a11y-test.mjs +107 -0
- package/scripts/topo-node-hover-lift-test.mjs +85 -0
- package/scripts/topo-node-title-peers-test.mjs +107 -0
- package/scripts/topo-node-title-test.mjs +101 -0
- package/scripts/topo-offline-dim-test.mjs +70 -0
- package/scripts/topo-online-chip-nav-test.mjs +143 -0
- package/scripts/topo-orbit-busyness-test.mjs +96 -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-panel-hover-elevation-test.mjs +118 -0
- package/scripts/topo-particle-stagger-test.mjs +82 -0
- package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
- package/scripts/topo-pin-intersection-test.mjs +121 -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-panel-hot-header-test.mjs +132 -0
- package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
- package/scripts/topo-recent-panel-more-test.mjs +106 -0
- package/scripts/topo-recent-row-hot-test.mjs +110 -0
- package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
- package/scripts/topo-recent-row-hover-test.mjs +122 -0
- package/scripts/topo-recent-row-pin-test.mjs +126 -0
- package/scripts/topo-recent-row-tint-test.mjs +91 -0
- package/scripts/topo-recent-row-title-test.mjs +112 -0
- package/scripts/topo-recent-row-ts-test.mjs +95 -0
- package/scripts/topo-recent-row-typography-test.mjs +119 -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-spoke-busyness-test.mjs +110 -0
- package/scripts/topo-status-chip-hover-test.mjs +101 -0
- package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
- package/scripts/topo-sweep-busyness-test.mjs +94 -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-chip-pin-test.mjs +148 -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/05gis4d.n_tv~.js +0 -1
- package/.next/static/chunks/09~8xbt2mo_.u.js +0 -1
- package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
- package/.next/static/chunks/0a0wcpeb8i2my.js +0 -1
- package/.next/static/chunks/0elalb9n_.~di.js +0 -1
- package/.next/static/chunks/0fqy_0wj_1xa7.js +0 -7
- package/.next/static/chunks/0j_q__o8p6g83.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/10oi7l-tycgl9.js +0 -1
- package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
- package/.next/static/chunks/13vw-ubg-9pjp.css +0 -2
- /package/.next/static/{71LBMB3-nZe_9iMTuD7GZ → IJ5nEFbzhj38AtfV6zg14}/_buildManifest.js +0 -0
- /package/.next/static/{71LBMB3-nZe_9iMTuD7GZ → IJ5nEFbzhj38AtfV6zg14}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{71LBMB3-nZe_9iMTuD7GZ → IJ5nEFbzhj38AtfV6zg14}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* Round 67 verification: filter pills shed the "filter:" prefix on
|
|
2
|
+
* narrow viewports and gain an anet-fade-in entrance.
|
|
3
|
+
* - At <sm (375 px) the prefix span has `display:none` via the
|
|
4
|
+
* `hidden sm:inline` class — visible pill text = "working×".
|
|
5
|
+
* - At sm+ (900 px) the prefix is visible — "filter: working ×".
|
|
6
|
+
* - The pill `<span>` carries `anet-fade-in` class so it eases in
|
|
7
|
+
* on mount (entrance polish).
|
|
8
|
+
*/
|
|
9
|
+
import { chromium } from 'playwright';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
|
|
12
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
13
|
+
const browser = await chromium.launch({ headless: true });
|
|
14
|
+
|
|
15
|
+
async function probe(width) {
|
|
16
|
+
const ctx = await browser.newContext({ viewport: { width, height: 1500 } });
|
|
17
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
18
|
+
await ctx.addInitScript(() => {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
21
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
22
|
+
// Pre-seed the working pin via R66 persistence so the pill exists
|
|
23
|
+
// on first paint — keeps the test independent of click wiring.
|
|
24
|
+
sessionStorage.setItem('anet-topo-pinned-status', 'working');
|
|
25
|
+
} catch {}
|
|
26
|
+
});
|
|
27
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
28
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
29
|
+
const r = await route.fetch();
|
|
30
|
+
const b = await r.json();
|
|
31
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
32
|
+
const sessions = [
|
|
33
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
34
|
+
{ alias: 'idl', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
35
|
+
];
|
|
36
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
37
|
+
});
|
|
38
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
39
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
40
|
+
const page = await ctx.newPage();
|
|
41
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
42
|
+
// Mobile viewport auto-hides the topology section; click "Show Topology"
|
|
43
|
+
// if the button surfaces. Otherwise the pill still renders in the chip
|
|
44
|
+
// row (chip row is independent of canvas visibility).
|
|
45
|
+
await page.waitForTimeout(1200);
|
|
46
|
+
const showBtn = page.locator('button', { hasText: /Show Topology/i });
|
|
47
|
+
if (await showBtn.count() > 0) await showBtn.first().click();
|
|
48
|
+
await page.waitForSelector('[data-active-filter="status"]', { timeout: 15000 });
|
|
49
|
+
|
|
50
|
+
const info = await page.evaluate(() => {
|
|
51
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
52
|
+
if (!pill) return null;
|
|
53
|
+
const prefix = pill.querySelector('[data-filter-prefix]');
|
|
54
|
+
// innerText respects display:none (vs textContent which includes
|
|
55
|
+
// all DOM text); that's the visible-to-the-user version.
|
|
56
|
+
return {
|
|
57
|
+
pillText: (pill.innerText || '').trim(),
|
|
58
|
+
pillFull: (pill.textContent || '').trim(),
|
|
59
|
+
pillClasses: pill.getAttribute('class') || '',
|
|
60
|
+
hasFadeIn: /\banet-fade-in\b/.test(pill.getAttribute('class') || ''),
|
|
61
|
+
prefixVisible: prefix
|
|
62
|
+
? (() => {
|
|
63
|
+
const r = prefix.getBoundingClientRect();
|
|
64
|
+
return r.width > 0 && r.height > 0;
|
|
65
|
+
})()
|
|
66
|
+
: false,
|
|
67
|
+
prefixDisplay: prefix ? getComputedStyle(prefix).display : null,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
await ctx.close();
|
|
71
|
+
return info;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const mobile = await probe(375);
|
|
75
|
+
const desktop = await probe(900);
|
|
76
|
+
await browser.close();
|
|
77
|
+
|
|
78
|
+
const results = {
|
|
79
|
+
mobile_prefixHidden: mobile && mobile.prefixVisible === false && mobile.prefixDisplay === 'none',
|
|
80
|
+
mobile_textNoPrefix: mobile && /^working/.test(mobile.pillText.trim()),
|
|
81
|
+
mobile_hasFadeIn: mobile && mobile.hasFadeIn === true,
|
|
82
|
+
desktop_prefixVisible: desktop && desktop.prefixVisible === true,
|
|
83
|
+
desktop_textWithPrefix: desktop && /^filter:\s*working/.test(desktop.pillText.trim()),
|
|
84
|
+
desktop_hasFadeIn: desktop && desktop.hasFadeIn === true,
|
|
85
|
+
};
|
|
86
|
+
const ok = Object.values(results).every(Boolean);
|
|
87
|
+
console.log(`${ok ? '✅' : '❌'} filter pill mobile:`, JSON.stringify(results),
|
|
88
|
+
`\n mobile=`, mobile,
|
|
89
|
+
`\n desktop=`, desktop);
|
|
90
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/* Round 29 verification: `f` keyboard shortcut triggers fit-to-content.
|
|
2
|
+
* - on a fleet that overflows: f sets zoom < 100%
|
|
3
|
+
* - on a fleet that fits at 100%: f is a recenter (zoom stays/returns 100%)
|
|
4
|
+
* - input-focus suppression: typing `f` in Cmd+K palette must not fit
|
|
5
|
+
* - help overlay lists `f` */
|
|
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
|
+
async function load({ fleetSize, persisted }) {
|
|
13
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
|
|
14
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
15
|
+
await ctx.addInitScript(([pers]) => {
|
|
16
|
+
try {
|
|
17
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
18
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
19
|
+
if (pers) localStorage.setItem('anet-topo-view', JSON.stringify(pers));
|
|
20
|
+
else localStorage.removeItem('anet-topo-view');
|
|
21
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
22
|
+
} catch {}
|
|
23
|
+
}, [persisted]);
|
|
24
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
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 groups = ['A站', 'B站', 'C站', 'D站', 'E站', 'F站', 'G站'];
|
|
30
|
+
const aliases = Array.from({ length: fleetSize }, (_, i) =>
|
|
31
|
+
`${groups[Math.floor(i / 5) % groups.length]}n${i + 1}`);
|
|
32
|
+
const sessions = aliases.map(a => ({
|
|
33
|
+
alias: a, status: 'idle', network_id: nid, project_dir: null,
|
|
34
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
35
|
+
}));
|
|
36
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
37
|
+
});
|
|
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((n) => document.querySelectorAll('g[data-node]').length === n, fleetSize, { timeout: 30000 }).catch(() => {});
|
|
42
|
+
await page.waitForTimeout(500);
|
|
43
|
+
return { ctx, page };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const readZoom = (page) => page.evaluate(() => {
|
|
47
|
+
const span = document.querySelector('button[aria-label="Zoom in"]').parentElement.querySelector('span[title*="zoom level"]');
|
|
48
|
+
return +span.textContent.replace('%', '');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// 1. Big fleet, persisted view at 100% → autoFit doesn't run (persisted respected);
|
|
52
|
+
// user presses `f` and gets fitted now.
|
|
53
|
+
const big = await load({ fleetSize: 35, persisted: { zoom: 1, x: 0, y: 0 } });
|
|
54
|
+
const bigBefore = await readZoom(big.page);
|
|
55
|
+
await big.page.keyboard.press('f');
|
|
56
|
+
await big.page.waitForTimeout(150);
|
|
57
|
+
const bigAfter = await readZoom(big.page);
|
|
58
|
+
await big.ctx.close();
|
|
59
|
+
|
|
60
|
+
// 2. Small fleet (no overflow), user manually zoomed in via `+` then presses `f` to recenter.
|
|
61
|
+
const small = await load({ fleetSize: 6, persisted: { zoom: 1, x: 0, y: 0 } });
|
|
62
|
+
await small.page.keyboard.press('=');
|
|
63
|
+
await small.page.keyboard.press('=');
|
|
64
|
+
await small.page.waitForTimeout(120);
|
|
65
|
+
const smallZoomedIn = await readZoom(small.page);
|
|
66
|
+
await small.page.keyboard.press('f');
|
|
67
|
+
await small.page.waitForTimeout(150);
|
|
68
|
+
const smallAfterFit = await readZoom(small.page);
|
|
69
|
+
await small.ctx.close();
|
|
70
|
+
|
|
71
|
+
// 3. Input-focus suppression: open Cmd+K, type `f`, expect no fit.
|
|
72
|
+
const focus = await load({ fleetSize: 35, persisted: { zoom: 1, x: 0, y: 0 } });
|
|
73
|
+
const focusBefore = await readZoom(focus.page);
|
|
74
|
+
await focus.page.keyboard.press('Meta+k');
|
|
75
|
+
await focus.page.waitForTimeout(200);
|
|
76
|
+
const paletteInput = focus.page.locator('input[placeholder*="Search"], input[type="search"], [role="dialog"] input').first();
|
|
77
|
+
await paletteInput.focus().catch(() => {});
|
|
78
|
+
await focus.page.keyboard.press('f');
|
|
79
|
+
await focus.page.waitForTimeout(150);
|
|
80
|
+
const focusAfter = await readZoom(focus.page);
|
|
81
|
+
await focus.page.keyboard.press('Escape');
|
|
82
|
+
await focus.ctx.close();
|
|
83
|
+
|
|
84
|
+
// 4. Help overlay lists `f`.
|
|
85
|
+
const help = await load({ fleetSize: 4 });
|
|
86
|
+
await help.page.keyboard.press('?');
|
|
87
|
+
await help.page.waitForTimeout(200);
|
|
88
|
+
const helpHasFit = await help.page.evaluate(() => {
|
|
89
|
+
const heading = [...document.querySelectorAll('[role="dialog"] div')].find(el => el.textContent === 'Topology canvas');
|
|
90
|
+
if (!heading) return false;
|
|
91
|
+
const txt = heading.parentElement?.textContent || '';
|
|
92
|
+
return /Fit topology/i.test(txt);
|
|
93
|
+
});
|
|
94
|
+
await help.ctx.close();
|
|
95
|
+
|
|
96
|
+
await browser.close();
|
|
97
|
+
const results = {
|
|
98
|
+
bigFleetFKeyFits: bigBefore === 100 && bigAfter < 100 && bigAfter >= 50,
|
|
99
|
+
smallFleetFKeyRecenters: smallZoomedIn > 100 && smallAfterFit === 100,
|
|
100
|
+
inputFocusSuppressed: focusAfter === focusBefore,
|
|
101
|
+
helpOverlayListsFit: helpHasFit,
|
|
102
|
+
};
|
|
103
|
+
const ok = Object.values(results).every(Boolean);
|
|
104
|
+
console.log(`${ok ? '✅' : '❌'} fit key:`, JSON.stringify(results),
|
|
105
|
+
`bigBefore=${bigBefore} bigAfter=${bigAfter} smallZoomedIn=${smallZoomedIn} smallAfterFit=${smallAfterFit} focusBefore=${focusBefore} focusAfter=${focusAfter}`);
|
|
106
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* Round 46 verification: live · Ns freshness chip.
|
|
2
|
+
* - renders in the topology header with initial "live · 0s" or "live · 1s"
|
|
3
|
+
* - ticks up over time (>= 2s after a 2.5s wait)
|
|
4
|
+
* - resets near 0 when SWR delivers a new sessions payload (we trigger
|
|
5
|
+
* this by mutating the mocked /api/hub/status response — SWR's 5s
|
|
6
|
+
* refresh re-fetches and the chip should re-zero)
|
|
7
|
+
* - turns amber when stale (>10s since last sync; verified by blocking
|
|
8
|
+
* the second poll and waiting >12s) */
|
|
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
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 900 } });
|
|
15
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
16
|
+
await ctx.addInitScript(() => {
|
|
17
|
+
try {
|
|
18
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
19
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
20
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
21
|
+
} catch {}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
let pollCount = 0;
|
|
25
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
26
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
27
|
+
pollCount++;
|
|
28
|
+
const r = await route.fetch();
|
|
29
|
+
const b = await r.json();
|
|
30
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
31
|
+
const sessions = [
|
|
32
|
+
{ alias: `a${pollCount}`, status: 'idle', network_id: nid, project_dir: null,
|
|
33
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
34
|
+
{ alias: 'b', 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
|
+
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.waitForSelector('[data-freshness-chip]', { timeout: 30000 });
|
|
43
|
+
|
|
44
|
+
const readChip = () => page.evaluate(() => {
|
|
45
|
+
const el = document.querySelector('[data-freshness-chip]');
|
|
46
|
+
if (!el) return null;
|
|
47
|
+
return {
|
|
48
|
+
text: (el.textContent || '').trim(),
|
|
49
|
+
isAmber: /amber|warning/i.test(el.className) || el.className.includes('amber'),
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const initial = await readChip();
|
|
54
|
+
await page.waitForTimeout(2500);
|
|
55
|
+
const afterTick = await readChip();
|
|
56
|
+
|
|
57
|
+
// SWR refreshes every 5s — wait past one cycle and confirm the chip re-zeros.
|
|
58
|
+
// Initial chip might be 1s, after 2.5s should be 3s. After a ~5s sync,
|
|
59
|
+
// should drop to 0-1s.
|
|
60
|
+
await page.waitForTimeout(3500);
|
|
61
|
+
const afterSync = await readChip();
|
|
62
|
+
|
|
63
|
+
await browser.close();
|
|
64
|
+
const seconds = (s) => +(s.text.match(/(\d+)s/)?.[1] ?? -1);
|
|
65
|
+
const results = {
|
|
66
|
+
initialNearZero: initial !== null && seconds(initial) <= 2,
|
|
67
|
+
tickedUpAfter25s: afterTick !== null && seconds(afterTick) >= 2 && seconds(afterTick) <= 4,
|
|
68
|
+
reZeroedAfterSync: afterSync !== null && seconds(afterSync) <= 2,
|
|
69
|
+
pollsHappened: pollCount >= 2,
|
|
70
|
+
};
|
|
71
|
+
const ok = Object.values(results).every(Boolean);
|
|
72
|
+
console.log(`${ok ? '✅' : '❌'} freshness chip:`, JSON.stringify(results),
|
|
73
|
+
`\n initial=${JSON.stringify(initial)}\n afterTick=${JSON.stringify(afterTick)}\n afterSync=${JSON.stringify(afterSync)} polls=${pollCount}`);
|
|
74
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/* Round 68 verification: group-box rect has a 3-tier visual ladder:
|
|
2
|
+
* idle → strokeWidth 1.5, dashed, ringStroke colour
|
|
3
|
+
* hovered → strokeWidth 2, solid, legendAccent colour
|
|
4
|
+
* pinned → strokeWidth 3, solid, legendAccent colour (stronger)
|
|
5
|
+
*
|
|
6
|
+
* Plus `data-group-box-pinned` reflects the pinned state so external
|
|
7
|
+
* tooling can spot a pinned box without inspecting computed CSS.
|
|
8
|
+
*/
|
|
9
|
+
import { chromium } from 'playwright';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
|
|
12
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
13
|
+
const browser = await chromium.launch({ headless: true });
|
|
14
|
+
|
|
15
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, 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', 'grid');
|
|
21
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
22
|
+
} catch {}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
26
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
27
|
+
const r = await route.fetch();
|
|
28
|
+
const b = await r.json();
|
|
29
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
30
|
+
const sessions = [
|
|
31
|
+
{ alias: 'alpha1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
32
|
+
{ alias: 'alpha2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
33
|
+
{ alias: 'alpha3', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
34
|
+
{ alias: 'beta1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
35
|
+
{ alias: 'beta2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
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(() => document.querySelectorAll('g[data-node]').length === 5, { timeout: 30000 });
|
|
44
|
+
await page.waitForTimeout(600);
|
|
45
|
+
|
|
46
|
+
const readAlphaBox = () => page.evaluate(() => {
|
|
47
|
+
// Find the alpha group <g data-group="alpha"> and read its <rect>.
|
|
48
|
+
const g = document.querySelector('g[data-group="alpha"]');
|
|
49
|
+
if (!g) return null;
|
|
50
|
+
const rect = g.querySelector('rect');
|
|
51
|
+
if (!rect) return null;
|
|
52
|
+
return {
|
|
53
|
+
strokeWidth: rect.getAttribute('stroke-width'),
|
|
54
|
+
stroke: rect.getAttribute('stroke'),
|
|
55
|
+
dashArray: rect.getAttribute('stroke-dasharray'),
|
|
56
|
+
fillOpacity: rect.getAttribute('fill-opacity'),
|
|
57
|
+
pinnedAttr: rect.getAttribute('data-group-box-pinned'),
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const idle = await readAlphaBox();
|
|
62
|
+
|
|
63
|
+
// Hover a member of alpha (alpha1) → group becomes hovered.
|
|
64
|
+
await page.locator('g[data-node="alpha1"]').hover();
|
|
65
|
+
await page.waitForTimeout(300);
|
|
66
|
+
const hovered = await readAlphaBox();
|
|
67
|
+
await page.mouse.move(10, 10);
|
|
68
|
+
await page.waitForTimeout(300);
|
|
69
|
+
|
|
70
|
+
// Pin alpha via group label click.
|
|
71
|
+
await page.locator('g[data-group-label-hit="alpha"]').first().click({ force: true });
|
|
72
|
+
await page.mouse.move(10, 10);
|
|
73
|
+
await page.waitForTimeout(300);
|
|
74
|
+
const pinned = await readAlphaBox();
|
|
75
|
+
|
|
76
|
+
await browser.close();
|
|
77
|
+
|
|
78
|
+
const results = {
|
|
79
|
+
idle_stroke15: idle && idle.strokeWidth === '1.5',
|
|
80
|
+
idle_dashed: idle && (idle.dashArray || '').startsWith('6'),
|
|
81
|
+
idle_pinnedFalse: idle && idle.pinnedAttr === 'false',
|
|
82
|
+
hovered_stroke2: hovered && hovered.strokeWidth === '2',
|
|
83
|
+
hovered_solid: hovered && (hovered.dashArray === 'none' || hovered.dashArray === null),
|
|
84
|
+
hovered_pinnedFalse: hovered && hovered.pinnedAttr === 'false',
|
|
85
|
+
pinned_stroke3: pinned && pinned.strokeWidth === '3',
|
|
86
|
+
pinned_solid: pinned && (pinned.dashArray === 'none' || pinned.dashArray === null),
|
|
87
|
+
pinned_pinnedTrue: pinned && pinned.pinnedAttr === 'true',
|
|
88
|
+
pinned_strongerFill: pinned && hovered && parseFloat(pinned.fillOpacity) > parseFloat(hovered.fillOpacity),
|
|
89
|
+
};
|
|
90
|
+
const ok = Object.values(results).every(Boolean);
|
|
91
|
+
console.log(`${ok ? '✅' : '❌'} group-box pinned state:`, JSON.stringify(results),
|
|
92
|
+
`\n idle=`, idle,
|
|
93
|
+
`\n hovered=`, hovered,
|
|
94
|
+
`\n pinned=`, pinned);
|
|
95
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* Round 19 verification: each group-box label carries a member-count
|
|
2
|
+
* tspan ("· N"), reflecting the actual member count of the group. */
|
|
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
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
|
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.removeItem('anet-brand');
|
|
15
|
+
localStorage.removeItem('anet-topo-view');
|
|
16
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
17
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
18
|
+
} catch {}
|
|
19
|
+
});
|
|
20
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
21
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
22
|
+
const r = await route.fetch();
|
|
23
|
+
const b = await r.json();
|
|
24
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
25
|
+
// A站 with 4 members, B站 with 2 members → 2 group boxes (counts 4 and 2).
|
|
26
|
+
const aliases = ['A站红', 'A站蓝', 'A站绿', 'A站黄', 'B站乙', 'B站丙'];
|
|
27
|
+
const sessions = aliases.map(a => ({
|
|
28
|
+
alias: a, status: 'idle', network_id: nid, project_dir: null,
|
|
29
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
30
|
+
}));
|
|
31
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
32
|
+
});
|
|
33
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
34
|
+
const page = await ctx.newPage();
|
|
35
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
36
|
+
await page.waitForFunction(() => {
|
|
37
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
38
|
+
return !!svg && svg.querySelectorAll('g[data-group]').length === 2;
|
|
39
|
+
}, { timeout: 30000 }).catch(() => {});
|
|
40
|
+
await page.waitForTimeout(400);
|
|
41
|
+
|
|
42
|
+
const labels = await page.$$eval('svg[viewBox="0 0 1000 680"] g[data-group]', els => els.map(g => {
|
|
43
|
+
const text = g.querySelector('text');
|
|
44
|
+
const tspan = text.querySelector('tspan');
|
|
45
|
+
return {
|
|
46
|
+
key: g.getAttribute('data-group'),
|
|
47
|
+
full: text.textContent.replace(/\s+/g, ' ').trim(),
|
|
48
|
+
chipFontSize: tspan ? +tspan.getAttribute('font-size') : null,
|
|
49
|
+
chipFontWeight: tspan ? tspan.getAttribute('font-weight') : null,
|
|
50
|
+
};
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
await browser.close();
|
|
54
|
+
const a = labels.find(l => l.key === 'A站');
|
|
55
|
+
const b = labels.find(l => l.key === 'B站');
|
|
56
|
+
// R58 appended status-mix pips inside the same <text>, and SVG textContent
|
|
57
|
+
// concatenates tspans without preserving the visual `dx` gaps — so what
|
|
58
|
+
// renders as "A站 · 4 4w" surfaces as the string "A站· 44w". The R19
|
|
59
|
+
// count chip is still the second tspan; verify by matching "Group·" then
|
|
60
|
+
// the count, allowing any trailing pip text.
|
|
61
|
+
const results = {
|
|
62
|
+
bothGroupsHaveChip: /^A站·\s*4/.test(a?.full || '') && /^B站·\s*2/.test(b?.full || ''),
|
|
63
|
+
chipSmallerFont: a?.chipFontSize === 11,
|
|
64
|
+
chipLighterWeight: a?.chipFontWeight === '400',
|
|
65
|
+
};
|
|
66
|
+
const ok = Object.values(results).every(Boolean);
|
|
67
|
+
console.log(`${ok ? '✅' : '❌'} group count chip:`, JSON.stringify(results), 'labels=', labels);
|
|
68
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/* Round 18 verification: hovering a node in a multi-member group upgrades
|
|
2
|
+
* the group-box to accent treatment (solid stroke, thicker, legendAccent
|
|
3
|
+
* colour) and brightens the label; other groups dim to 0.28; resting
|
|
4
|
+
* appearance unchanged. */
|
|
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
|
+
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(() => {
|
|
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
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
20
|
+
} catch {}
|
|
21
|
+
});
|
|
22
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
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
|
+
// Two prefix groups (A站 / B站), each ≥2 members so groupBoxes renders.
|
|
28
|
+
const aliases = ['A站红', 'A站蓝', 'A站绿', 'B站乙', 'B站丙', 'B站丁'];
|
|
29
|
+
const sessions = aliases.map(a => ({
|
|
30
|
+
alias: a, status: 'idle', network_id: nid, project_dir: null,
|
|
31
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
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-group]').length === 2;
|
|
41
|
+
}, { timeout: 30000 }).catch(() => {});
|
|
42
|
+
await page.waitForTimeout(400);
|
|
43
|
+
|
|
44
|
+
const readGroup = key => page.evaluate(k => {
|
|
45
|
+
const g = document.querySelector(`g[data-group="${k}"]`);
|
|
46
|
+
if (!g) return null;
|
|
47
|
+
const rect = g.querySelector('rect');
|
|
48
|
+
const text = g.querySelector('text');
|
|
49
|
+
return {
|
|
50
|
+
opacity: parseFloat(getComputedStyle(g).opacity || '1'),
|
|
51
|
+
stroke: rect.getAttribute('stroke'),
|
|
52
|
+
strokeWidth: +rect.getAttribute('stroke-width'),
|
|
53
|
+
strokeDasharray: rect.getAttribute('stroke-dasharray'),
|
|
54
|
+
fillOpacity: rect.getAttribute('fill-opacity'),
|
|
55
|
+
textFill: text.getAttribute('fill'),
|
|
56
|
+
};
|
|
57
|
+
}, key);
|
|
58
|
+
|
|
59
|
+
// Resting state (no hover)
|
|
60
|
+
const restA = await readGroup('A站');
|
|
61
|
+
const restB = await readGroup('B站');
|
|
62
|
+
|
|
63
|
+
// Hover a node in group A站
|
|
64
|
+
await page.locator('g[data-node="A站红"]').hover();
|
|
65
|
+
await page.waitForTimeout(350); // allow the 200ms transition to settle
|
|
66
|
+
|
|
67
|
+
const hoverA = await readGroup('A站');
|
|
68
|
+
const hoverB = await readGroup('B站');
|
|
69
|
+
|
|
70
|
+
await browser.close();
|
|
71
|
+
const results = {
|
|
72
|
+
restADashedThin: restA?.strokeDasharray === '6 6' && restA?.strokeWidth === 1.5,
|
|
73
|
+
restBDashedThin: restB?.strokeDasharray === '6 6' && restB?.strokeWidth === 1.5,
|
|
74
|
+
hoveredASolidThick: hoverA?.strokeDasharray === 'none' && hoverA?.strokeWidth === 2,
|
|
75
|
+
hoveredAAccentStroke: hoverA?.stroke === '#67e8f9', // dark legendAccent
|
|
76
|
+
hoveredABrighterText: hoverA?.textFill === '#e5e7eb', // dark legendHeadline
|
|
77
|
+
otherBDimmedOpacity: Math.abs((hoverB?.opacity ?? 1) - 0.28) < 0.02,
|
|
78
|
+
otherBKeepsDashed: hoverB?.strokeDasharray === '6 6',
|
|
79
|
+
};
|
|
80
|
+
const ok = Object.values(results).every(Boolean);
|
|
81
|
+
console.log(`${ok ? '✅' : '❌'} group hover linkage:`, JSON.stringify(results));
|
|
82
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* Round 152 verification: group-label hit gains onKeyDown.
|
|
2
|
+
*
|
|
3
|
+
* R63 added role="button" + tabIndex=0 + aria-pressed to the
|
|
4
|
+
* group label hit-area, but never wired keyboard activation —
|
|
5
|
+
* focused users could Tab to the label but Enter / Space did
|
|
6
|
+
* nothing. R152 wires onKeyDown to fire the same setPinnedGroup
|
|
7
|
+
* setter onClick uses.
|
|
8
|
+
*
|
|
9
|
+
* This is the last keyboard gap among the role="button" surfaces;
|
|
10
|
+
* pressure-bar segs (R60), legend rows (R61), recent rows (R116),
|
|
11
|
+
* edge count badges (R121), chip-row chips (R136 / R139 / R140),
|
|
12
|
+
* vendor letters (R88), and nodes (R151) all had it. Group labels
|
|
13
|
+
* were the last hold-out.
|
|
14
|
+
*
|
|
15
|
+
* Fleet: 3 working sessions in grid layout to force group boxes
|
|
16
|
+
* (R85). Two prefix groups: agents-* (2) and infra-* (1).
|
|
17
|
+
*
|
|
18
|
+
* Path:
|
|
19
|
+
* 1. Focus agents group label → aria-pressed=false initially
|
|
20
|
+
* 2. Press Enter → aria-pressed=true (pinned)
|
|
21
|
+
* 3. Press Enter again → aria-pressed=false (released)
|
|
22
|
+
* 4. Press Space → aria-pressed=true (pinned)
|
|
23
|
+
* 5. Esc clears → aria-pressed=false
|
|
24
|
+
*/
|
|
25
|
+
import { chromium } from 'playwright';
|
|
26
|
+
import { readFileSync } from 'node:fs';
|
|
27
|
+
|
|
28
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
29
|
+
const browser = await chromium.launch({ headless: true });
|
|
30
|
+
|
|
31
|
+
const ctx = await browser.newContext({ viewport: { width: 1600, height: 1500 } });
|
|
32
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
33
|
+
await ctx.addInitScript(() => {
|
|
34
|
+
try {
|
|
35
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
36
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
37
|
+
// group boxes only render in grid layout
|
|
38
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
39
|
+
} catch {}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
43
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
44
|
+
const r = await route.fetch();
|
|
45
|
+
const b = await r.json();
|
|
46
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
47
|
+
const mk = (alias, status) => ({
|
|
48
|
+
alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
49
|
+
network_id: nid, project_dir: null,
|
|
50
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
51
|
+
});
|
|
52
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
53
|
+
mk('agents-a1', 'working'), mk('agents-a2', 'working'),
|
|
54
|
+
mk('infra-b1', 'working'),
|
|
55
|
+
] } });
|
|
56
|
+
});
|
|
57
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
58
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
59
|
+
|
|
60
|
+
const page = await ctx.newPage();
|
|
61
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
62
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
|
|
63
|
+
await page.waitForSelector('[data-group-label-hit]', { timeout: 10000 });
|
|
64
|
+
await page.waitForTimeout(400);
|
|
65
|
+
|
|
66
|
+
// Find the actual group keys (cluster prefix may differ)
|
|
67
|
+
const groupKeys = await page.evaluate(() =>
|
|
68
|
+
[...document.querySelectorAll('[data-group-label-hit]')].map(g => g.getAttribute('data-group-label-hit')));
|
|
69
|
+
const agentsKey = groupKeys.find(k => k.startsWith('agents')) || groupKeys[0];
|
|
70
|
+
|
|
71
|
+
const readAriaPressed = () => page.evaluate((k) => {
|
|
72
|
+
const g = document.querySelector(`[data-group-label-hit="${k}"]`);
|
|
73
|
+
return g?.getAttribute('aria-pressed');
|
|
74
|
+
}, agentsKey);
|
|
75
|
+
|
|
76
|
+
const before = await readAriaPressed();
|
|
77
|
+
|
|
78
|
+
// Focus the label and press Enter
|
|
79
|
+
await page.locator(`[data-group-label-hit="${agentsKey}"]`).focus();
|
|
80
|
+
await page.keyboard.press('Enter');
|
|
81
|
+
await page.waitForTimeout(200);
|
|
82
|
+
const afterEnter = await readAriaPressed();
|
|
83
|
+
|
|
84
|
+
// Press Enter again → toggle off
|
|
85
|
+
await page.keyboard.press('Enter');
|
|
86
|
+
await page.waitForTimeout(200);
|
|
87
|
+
const afterReEnter = await readAriaPressed();
|
|
88
|
+
|
|
89
|
+
// Press Space → pin again
|
|
90
|
+
await page.locator(`[data-group-label-hit="${agentsKey}"]`).focus();
|
|
91
|
+
await page.keyboard.press(' ');
|
|
92
|
+
await page.waitForTimeout(200);
|
|
93
|
+
const afterSpace = await readAriaPressed();
|
|
94
|
+
|
|
95
|
+
// Esc clears
|
|
96
|
+
await page.keyboard.press('Escape');
|
|
97
|
+
await page.waitForTimeout(200);
|
|
98
|
+
const afterEsc = await readAriaPressed();
|
|
99
|
+
|
|
100
|
+
await browser.close();
|
|
101
|
+
|
|
102
|
+
const results = {
|
|
103
|
+
before_notPinned: before === 'false',
|
|
104
|
+
enter_pins: afterEnter === 'true',
|
|
105
|
+
reEnter_releases: afterReEnter === 'false',
|
|
106
|
+
space_pins: afterSpace === 'true',
|
|
107
|
+
esc_clears: afterEsc === 'false',
|
|
108
|
+
};
|
|
109
|
+
const ok = Object.values(results).every(Boolean);
|
|
110
|
+
console.log(`${ok ? '✅' : '❌'} group label a11y:`, JSON.stringify(results),
|
|
111
|
+
`\n groupKey=${agentsKey}`,
|
|
112
|
+
`\n before=${before} enter=${afterEnter} reEnter=${afterReEnter} space=${afterSpace} esc=${afterEsc}`);
|
|
113
|
+
process.exit(ok ? 0 : 1);
|