@sleep2agi/agent-network-dashboard 0.4.6-preview.26 → 0.4.6-preview.261
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/00.pi0.850kxg.js +1 -0
- package/.next/static/chunks/01lmbsd37fybu.js +1 -0
- package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
- package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
- package/.next/static/chunks/05uk96gc~9mni.js +1 -0
- package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
- package/.next/static/chunks/08y27uzl7w6bx.css +2 -0
- package/.next/static/chunks/0ggj5ummw2a84.js +7 -0
- package/.next/static/chunks/0glmrdj9112tc.js +7 -0
- package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
- package/.next/static/chunks/0s64vw8kd5rn7.js +1 -0
- package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
- package/.next/static/chunks/11drzen-wfaop.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/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 +5291 -237
- package/app/components/utils.ts +8 -7
- package/app/globals.css +110 -5
- package/app/lib/time.ts +36 -0
- package/package.json +2 -2
- 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-self-hover-test.mjs +171 -0
- package/scripts/topo-active-links-chip-test.mjs +73 -0
- package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
- package/scripts/topo-active-links-freshness-test.mjs +113 -0
- package/scripts/topo-active-links-tooltip-test.mjs +79 -0
- package/scripts/topo-active-pulse-ease-test.mjs +120 -0
- package/scripts/topo-arrival-ping-test.mjs +87 -0
- package/scripts/topo-auto-fit-test.mjs +73 -0
- package/scripts/topo-canvas-aria-test.mjs +108 -0
- package/scripts/topo-chat-target-breath-test.mjs +84 -0
- package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
- package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
- package/scripts/topo-chip-empty-recede-test.mjs +116 -0
- package/scripts/topo-chip-focus-test.mjs +95 -0
- package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
- package/scripts/topo-chip-row-tabular-test.mjs +111 -0
- package/scripts/topo-chrome-a11y-test.mjs +107 -0
- package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
- package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
- package/scripts/topo-chrome-press-state-test.mjs +135 -0
- package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
- package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
- package/scripts/topo-click-ripple-ease-test.mjs +94 -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-dense-alias-fill-transition-test.mjs +133 -0
- package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -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-crossfade-test.mjs +138 -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-badge-lift-test.mjs +150 -0
- package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
- package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
- package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
- package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -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-fadein-test.mjs +106 -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-smil-sync-test.mjs +148 -0
- package/scripts/topo-edge-smil-theme-ease-test.mjs +97 -0
- package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
- package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
- package/scripts/topo-edge-tooltip-test.mjs +70 -0
- package/scripts/topo-empty-state-breath-test.mjs +150 -0
- package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
- package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
- package/scripts/topo-endpoint-ring-test.mjs +93 -0
- package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -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-freshness-chip-transition-test.mjs +80 -0
- package/scripts/topo-fullscreen-active-test.mjs +115 -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-label-a11y-test.mjs +113 -0
- package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
- package/scripts/topo-group-label-hover-test.mjs +119 -0
- package/scripts/topo-group-label-pin-spacing-test.mjs +136 -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-pip-fadein-test.mjs +100 -0
- package/scripts/topo-group-pip-tabular-test.mjs +95 -0
- package/scripts/topo-group-status-pips-test.mjs +103 -0
- package/scripts/topo-groupbox-fadein-test.mjs +95 -0
- package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
- package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -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-halo-breath-ease-test.mjs +92 -0
- package/scripts/topo-halo-breath-stagger-test.mjs +89 -0
- 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-hot-tail-crossfade-test.mjs +117 -0
- package/scripts/topo-hub-a11y-test.mjs +112 -0
- package/scripts/topo-hub-busyness-test.mjs +100 -0
- package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
- package/scripts/topo-hub-click-fit-test.mjs +137 -0
- package/scripts/topo-hub-digit-scale-test.mjs +129 -0
- package/scripts/topo-hub-fadein-test.mjs +88 -0
- package/scripts/topo-hub-hover-lift-test.mjs +101 -0
- package/scripts/topo-hub-hover-ring-test.mjs +82 -0
- package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
- package/scripts/topo-hub-spoke-ease-test.mjs +120 -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-card-theme-ease-test.mjs +85 -0
- package/scripts/topo-label-lift-test.mjs +119 -0
- package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
- package/scripts/topo-layout-key-test.mjs +91 -0
- package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
- package/scripts/topo-layout-toggle-test.mjs +131 -0
- package/scripts/topo-legend-count-empty-test.mjs +126 -0
- package/scripts/topo-legend-count-test.mjs +88 -0
- package/scripts/topo-legend-count-tier-fill-test.mjs +121 -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-ring-fade-test.mjs +112 -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-pin-spacing-test.mjs +124 -0
- package/scripts/topo-legend-row-tint-test.mjs +95 -0
- package/scripts/topo-legend-row-title-test.mjs +102 -0
- package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
- package/scripts/topo-minimap-a11y-test.mjs +101 -0
- package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
- package/scripts/topo-minimap-test.mjs +68 -0
- package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -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-label-card-elevation-test.mjs +157 -0
- package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
- package/scripts/topo-node-stroke-transition-test.mjs +103 -0
- package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
- package/scripts/topo-node-title-peers-test.mjs +107 -0
- package/scripts/topo-node-title-test.mjs +101 -0
- package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
- package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
- package/scripts/topo-online-chip-nav-test.mjs +143 -0
- package/scripts/topo-orbit-busyness-test.mjs +96 -0
- package/scripts/topo-orbit-opacity-bucket-test.mjs +124 -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-fadein-test.mjs +102 -0
- package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
- package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
- package/scripts/topo-panel-theme-ease-test.mjs +85 -0
- package/scripts/topo-particle-stagger-test.mjs +82 -0
- package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
- package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
- package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
- package/scripts/topo-pin-intersection-test.mjs +121 -0
- package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
- package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
- package/scripts/topo-pin-persistence-test.mjs +105 -0
- package/scripts/topo-ping-pulse-ease-test.mjs +96 -0
- package/scripts/topo-popover-meta-test.mjs +78 -0
- package/scripts/topo-prepublish-guard-test.mjs +95 -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-self-hover-test.mjs +126 -0
- package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
- package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
- package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
- package/scripts/topo-pulse-rate-test.mjs +71 -0
- package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -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-crossfade-test.mjs +124 -0
- package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -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-count-fill-test.mjs +129 -0
- package/scripts/topo-recent-row-fadein-test.mjs +107 -0
- package/scripts/topo-recent-row-freshness-test.mjs +102 -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-spacing-test.mjs +137 -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-alpha-test.mjs +100 -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-reset-spin-test.mjs +120 -0
- package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
- package/scripts/topo-spoke-busyness-test.mjs +110 -0
- package/scripts/topo-spoke-tint-test.mjs +96 -0
- package/scripts/topo-status-chip-hover-test.mjs +101 -0
- package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
- package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
- package/scripts/topo-svg-focus-test.mjs +112 -0
- package/scripts/topo-sweep-busyness-test.mjs +94 -0
- package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
- package/scripts/topo-tier-ring-fadein-test.mjs +107 -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-chip-self-hover-test.mjs +130 -0
- package/scripts/topo-vendor-chip-tabular-test.mjs +89 -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-view-smooth-test.mjs +131 -0
- package/scripts/topo-wheel-zoom-test.mjs +79 -0
- package/scripts/topo-working-chip-pin-test.mjs +154 -0
- package/scripts/topo-working-halo-breath-test.mjs +86 -0
- package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -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/09nd.t_hd.y6t.css +0 -2
- package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
- 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/0jzqhqbydbrm~.js +0 -1
- package/.next/static/chunks/0lvk7f2_5y8b3.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/12maqvd_4t3sh.js +0 -1
- /package/.next/static/{iC9JNLo0IdNSaeynEneGT → -JO9tjmCkno3inrj-XBxj}/_buildManifest.js +0 -0
- /package/.next/static/{iC9JNLo0IdNSaeynEneGT → -JO9tjmCkno3inrj-XBxj}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{iC9JNLo0IdNSaeynEneGT → -JO9tjmCkno3inrj-XBxj}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* Round 26 verification: hovering a node lifts its label group up by
|
|
2
|
+
* 1.5 px (CSS transform stacked on the SVG positioning translate). Both
|
|
3
|
+
* the full-card path (sparse fleet) and the dense plain-text path
|
|
4
|
+
* (large fleet) should respond. */
|
|
5
|
+
import { chromium } from 'playwright';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
|
|
8
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
9
|
+
const browser = await chromium.launch({ headless: true });
|
|
10
|
+
|
|
11
|
+
async function probe(fleetSize) {
|
|
12
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
|
|
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
|
+
localStorage.setItem('anet-topo-nodescale', '1');
|
|
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
|
+
const sessions = Array.from({ length: fleetSize }, (_, i) => ({
|
|
28
|
+
alias: `lab${i + 1}`, 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((n) => {
|
|
37
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
38
|
+
return !!svg && svg.querySelectorAll('g[data-node]').length === n;
|
|
39
|
+
}, fleetSize, { timeout: 30000 }).catch(() => {});
|
|
40
|
+
await page.waitForTimeout(500);
|
|
41
|
+
|
|
42
|
+
// Re-query the label element fresh each sample — React re-renders on
|
|
43
|
+
// hover (hoveredAlias state change) so any element handle captured
|
|
44
|
+
// before the hover would be detached/stale by the time we read its
|
|
45
|
+
// post-hover position.
|
|
46
|
+
const readLabelTop = () => page.evaluate(() => {
|
|
47
|
+
const node = document.querySelector('g[data-node="lab1"]');
|
|
48
|
+
if (!node) return null;
|
|
49
|
+
const full = [...node.querySelectorAll('g')].find(g => /group-hover:-translate-y/.test(g.getAttribute('class') || ''));
|
|
50
|
+
const el = full || [...node.querySelectorAll('text')].find(t => /group-hover:-translate-y/.test(t.getAttribute('class') || ''));
|
|
51
|
+
return el ? el.getBoundingClientRect().top : null;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const beforeY = await readLabelTop();
|
|
55
|
+
await page.locator('g[data-node="lab1"]').hover();
|
|
56
|
+
await page.waitForTimeout(300);
|
|
57
|
+
const afterY = await readLabelTop();
|
|
58
|
+
|
|
59
|
+
await ctx.close();
|
|
60
|
+
return { beforeY, afterY };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const sparse = await probe(4);
|
|
64
|
+
// Note: in dense mode the same alias's hover triggers `showFullLabel`,
|
|
65
|
+
// which swaps the plain-text element for the full-card <g>. That path
|
|
66
|
+
// switch makes a same-element before/after comparison meaningless.
|
|
67
|
+
// The plain-text element still carries the same hover-lift class, so
|
|
68
|
+
// the lift wiring is verified at the DOM level via the second probe.
|
|
69
|
+
const denseClassPresent = await (async () => {
|
|
70
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
|
|
71
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
72
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
73
|
+
await ctx.addInitScript(() => {
|
|
74
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
75
|
+
localStorage.setItem('anet-topo-nodescale', '1');
|
|
76
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
77
|
+
});
|
|
78
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
79
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
80
|
+
const r = await route.fetch();
|
|
81
|
+
const b = await r.json();
|
|
82
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
83
|
+
const sessions = Array.from({ length: 28 }, (_, i) => ({
|
|
84
|
+
alias: `lab${i + 1}`, status: 'idle', network_id: nid, project_dir: null,
|
|
85
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
86
|
+
}));
|
|
87
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
88
|
+
});
|
|
89
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
90
|
+
const page = await ctx.newPage();
|
|
91
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
92
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 28, { timeout: 30000 }).catch(() => {});
|
|
93
|
+
await page.waitForTimeout(500);
|
|
94
|
+
const has = await page.evaluate(() => {
|
|
95
|
+
const node = document.querySelector('g[data-node="lab2"]'); // not the one we'd hover
|
|
96
|
+
if (!node) return false;
|
|
97
|
+
const t = [...node.querySelectorAll('text')].find(x => /group-hover:-translate-y/.test(x.getAttribute('class') || ''));
|
|
98
|
+
return !!t;
|
|
99
|
+
});
|
|
100
|
+
await ctx.close();
|
|
101
|
+
return has;
|
|
102
|
+
})();
|
|
103
|
+
|
|
104
|
+
await browser.close();
|
|
105
|
+
|
|
106
|
+
// R26 contributes label-only -1.5px lift; R51 (preview.58) adds -2px on the
|
|
107
|
+
// parent <g data-node> on hover, so the label's total measured movement
|
|
108
|
+
// when the node is hovered is ~3.5px. Widen the upper bound to 4 so the
|
|
109
|
+
// two lifts compose without flagging this as a regression.
|
|
110
|
+
const lifted = (m) => m.beforeY != null && m.afterY != null && m.beforeY - m.afterY >= 1 && m.beforeY - m.afterY <= 4;
|
|
111
|
+
|
|
112
|
+
const results = {
|
|
113
|
+
sparseLabelLifts: lifted(sparse),
|
|
114
|
+
densePlainTextHasLiftClass: denseClassPresent,
|
|
115
|
+
};
|
|
116
|
+
const ok = Object.values(results).every(Boolean);
|
|
117
|
+
console.log(`${ok ? '✅' : '❌'} label hover lift:`, JSON.stringify(results),
|
|
118
|
+
`sparse=Δ${(sparse.beforeY - sparse.afterY)?.toFixed(2)}px`);
|
|
119
|
+
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,126 @@
|
|
|
1
|
+
/* Round 170 verification: layout toggle (Ring ↔ Grid) crossfade.
|
|
2
|
+
*
|
|
3
|
+
* Pre-R170 clicking Ring/Grid teleported every node to its new
|
|
4
|
+
* position in one frame — most jarring user action on the canvas.
|
|
5
|
+
*
|
|
6
|
+
* R170 introduces layoutSwitching one-shot flag (same pattern as
|
|
7
|
+
* R168 smoothView but on opacity axis). The viewport <g>:
|
|
8
|
+
* transition: 'opacity 250ms ease-out' (always present)
|
|
9
|
+
* + 'transform 300ms ease-out' (when smoothView armed)
|
|
10
|
+
* opacity: layoutSwitching ? 0.45 : 1
|
|
11
|
+
* data-topo-viewport-layout-switching: 'true' | 'false'
|
|
12
|
+
*
|
|
13
|
+
* Layout state swap is synchronous (setLayout) — the dim masks
|
|
14
|
+
* the snap as a soft blink. Auto-clears 400ms (covers fade-down
|
|
15
|
+
* 250ms + buffer for React commit).
|
|
16
|
+
*
|
|
17
|
+
* Test:
|
|
18
|
+
* 1. Idle (ring layout): layoutSwitching='false', opacity=1
|
|
19
|
+
* 2. Press 'L' (toggleLayout) → flag='true', opacity=0.45
|
|
20
|
+
* 3. Layout state flipped to grid (group boxes visible)
|
|
21
|
+
* 4. Wait 450ms → flag='false', opacity=1
|
|
22
|
+
* 5. Press 'L' again → flag='true' (reverse direction works too)
|
|
23
|
+
*/
|
|
24
|
+
import { chromium } from 'playwright';
|
|
25
|
+
import { readFileSync } from 'node:fs';
|
|
26
|
+
|
|
27
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
28
|
+
const browser = await chromium.launch({ headless: true });
|
|
29
|
+
|
|
30
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
31
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
32
|
+
await ctx.addInitScript(() => {
|
|
33
|
+
try {
|
|
34
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
35
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
36
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
37
|
+
} catch {}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
41
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
42
|
+
const r = await route.fetch();
|
|
43
|
+
const b = await r.json();
|
|
44
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
45
|
+
const mk = (alias) => ({
|
|
46
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
47
|
+
network_id: nid, project_dir: null,
|
|
48
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
49
|
+
});
|
|
50
|
+
// Prefix-clustered aliases so group boxes render in grid layout
|
|
51
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
52
|
+
mk('agents-a1'), mk('agents-a2'), mk('infra-b1'), mk('infra-b2'),
|
|
53
|
+
] } });
|
|
54
|
+
});
|
|
55
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
56
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
57
|
+
|
|
58
|
+
const page = await ctx.newPage();
|
|
59
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
60
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
61
|
+
await page.waitForSelector('[data-topo-viewport]', { timeout: 10000 });
|
|
62
|
+
await page.waitForTimeout(400);
|
|
63
|
+
|
|
64
|
+
const probeViewport = () => page.evaluate(() => {
|
|
65
|
+
const g = document.querySelector('[data-topo-viewport]');
|
|
66
|
+
return {
|
|
67
|
+
layoutSwitching: g?.getAttribute('data-topo-viewport-layout-switching'),
|
|
68
|
+
opacity: g?.style?.opacity || '',
|
|
69
|
+
transition: g?.style?.transition || '',
|
|
70
|
+
hubVisible: !!document.querySelector('[data-topo-hub]'),
|
|
71
|
+
groupBoxes: document.querySelectorAll('[data-group-box-pinned]').length,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const idle = await probeViewport();
|
|
76
|
+
|
|
77
|
+
// Press 'L' to toggle ring → grid. Probe within 400ms window.
|
|
78
|
+
await page.keyboard.press('l');
|
|
79
|
+
await page.waitForTimeout(50);
|
|
80
|
+
const duringToggle1 = await probeViewport();
|
|
81
|
+
|
|
82
|
+
// Wait past 400ms window
|
|
83
|
+
await page.waitForTimeout(420);
|
|
84
|
+
const afterToggle1 = await probeViewport();
|
|
85
|
+
|
|
86
|
+
// Press 'L' again to toggle grid → ring
|
|
87
|
+
await page.keyboard.press('l');
|
|
88
|
+
await page.waitForTimeout(50);
|
|
89
|
+
const duringToggle2 = await probeViewport();
|
|
90
|
+
|
|
91
|
+
await page.waitForTimeout(420);
|
|
92
|
+
const afterToggle2 = await probeViewport();
|
|
93
|
+
|
|
94
|
+
await browser.close();
|
|
95
|
+
|
|
96
|
+
const results = {
|
|
97
|
+
viewport_found: idle !== null,
|
|
98
|
+
idle_layoutSwitching_false: idle.layoutSwitching === 'false',
|
|
99
|
+
idle_opacity_1: idle.opacity === '1' || idle.opacity === '',
|
|
100
|
+
idle_has_opacity_transition: idle.transition.includes('opacity 250ms'),
|
|
101
|
+
idle_in_ring: idle.hubVisible && idle.groupBoxes === 0,
|
|
102
|
+
|
|
103
|
+
toggle1_flag_true: duringToggle1.layoutSwitching === 'true',
|
|
104
|
+
toggle1_opacity_0p45: duringToggle1.opacity === '0.45',
|
|
105
|
+
// Layout state has flipped synchronously inside toggleLayout
|
|
106
|
+
toggle1_now_grid: !duringToggle1.hubVisible && duringToggle1.groupBoxes > 0,
|
|
107
|
+
|
|
108
|
+
after1_flag_false: afterToggle1.layoutSwitching === 'false',
|
|
109
|
+
after1_opacity_1: afterToggle1.opacity === '1',
|
|
110
|
+
after1_still_grid: !afterToggle1.hubVisible && afterToggle1.groupBoxes > 0,
|
|
111
|
+
|
|
112
|
+
toggle2_flag_true: duringToggle2.layoutSwitching === 'true',
|
|
113
|
+
toggle2_opacity_0p45: duringToggle2.opacity === '0.45',
|
|
114
|
+
toggle2_back_to_ring: duringToggle2.hubVisible && duringToggle2.groupBoxes === 0,
|
|
115
|
+
|
|
116
|
+
after2_flag_false: afterToggle2.layoutSwitching === 'false',
|
|
117
|
+
after2_opacity_1: afterToggle2.opacity === '1',
|
|
118
|
+
};
|
|
119
|
+
const ok = Object.values(results).every(Boolean);
|
|
120
|
+
console.log(`${ok ? '✅' : '❌'} layout switch crossfade:`, JSON.stringify(results),
|
|
121
|
+
`\n idle =`, idle,
|
|
122
|
+
`\n during toggle1 (ring→grid) =`, duringToggle1,
|
|
123
|
+
`\n after toggle1 =`, afterToggle1,
|
|
124
|
+
`\n during toggle2 (grid→ring) =`, duringToggle2,
|
|
125
|
+
`\n after toggle2 =`, afterToggle2);
|
|
126
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/* Round 163 verification: Ring/Grid layout toggle buttons pick
|
|
2
|
+
* up the R154 chrome-button polish convention.
|
|
3
|
+
*
|
|
4
|
+
* Pre-R163 the buttons had aria-pressed + transition-colors but:
|
|
5
|
+
* - no focus-visible ring (browser default invisible on canvas)
|
|
6
|
+
* - inactive hover only nudged text-gray-500 → gray-400, no bg
|
|
7
|
+
*
|
|
8
|
+
* R163 adds:
|
|
9
|
+
* data-topo-chrome-layout="ring|grid"
|
|
10
|
+
* data-topo-chrome-layout-active="true|false"
|
|
11
|
+
* focus-visible:ring-2 ring-cyan-400/60 ring-inset
|
|
12
|
+
* inactive: hover:text-gray-300 + hover:bg-cyan-500/5
|
|
13
|
+
* active: hover:bg-cyan-500/20 (slightly hotter)
|
|
14
|
+
*
|
|
15
|
+
* Test:
|
|
16
|
+
* 1. Both buttons present with data-topo-chrome-layout attr
|
|
17
|
+
* 2. Active button has bg-cyan-500/15 class fragment (existing)
|
|
18
|
+
* 3. Focus the inactive button → focus-visible ring class fragment
|
|
19
|
+
* 4. Hover the inactive button → bg color shifts from transparent
|
|
20
|
+
* to a cyan-ish tint (border bg-cyan-500/5)
|
|
21
|
+
*/
|
|
22
|
+
import { chromium } from 'playwright';
|
|
23
|
+
import { readFileSync } from 'node:fs';
|
|
24
|
+
|
|
25
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
26
|
+
const browser = await chromium.launch({ headless: true });
|
|
27
|
+
|
|
28
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
29
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
30
|
+
await ctx.addInitScript(() => {
|
|
31
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); localStorage.setItem('anet-topo-layout', 'ring'); } catch {}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
35
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
36
|
+
const r = await route.fetch();
|
|
37
|
+
const b = await r.json();
|
|
38
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
39
|
+
const mk = (alias) => ({
|
|
40
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
41
|
+
network_id: nid, project_dir: null,
|
|
42
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
43
|
+
});
|
|
44
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta')] } });
|
|
45
|
+
});
|
|
46
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
47
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
48
|
+
|
|
49
|
+
const page = await ctx.newPage();
|
|
50
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
51
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
|
|
52
|
+
await page.waitForSelector('[data-topo-chrome-layout]', { timeout: 10000 });
|
|
53
|
+
await page.waitForTimeout(400);
|
|
54
|
+
|
|
55
|
+
// Probe basic attrs & class membership
|
|
56
|
+
const probe = await page.evaluate(() => {
|
|
57
|
+
const ring = document.querySelector('[data-topo-chrome-layout="ring"]');
|
|
58
|
+
const grid = document.querySelector('[data-topo-chrome-layout="grid"]');
|
|
59
|
+
if (!ring || !grid) return null;
|
|
60
|
+
return {
|
|
61
|
+
ring_active: ring.getAttribute('data-topo-chrome-layout-active'),
|
|
62
|
+
grid_active: grid.getAttribute('data-topo-chrome-layout-active'),
|
|
63
|
+
ring_pressed: ring.getAttribute('aria-pressed'),
|
|
64
|
+
grid_pressed: grid.getAttribute('aria-pressed'),
|
|
65
|
+
ring_class: ring.getAttribute('class') || '',
|
|
66
|
+
grid_class: grid.getAttribute('class') || '',
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Focus the inactive (grid) button — focus-visible ring should kick in.
|
|
71
|
+
// Tab-focus produces the :focus-visible state; .focus() doesn't always.
|
|
72
|
+
// Use page.keyboard.press('Tab') after first focusing somewhere known.
|
|
73
|
+
await page.locator('[data-topo-chrome-layout="grid"]').focus();
|
|
74
|
+
// trigger keyboard navigation to enable focus-visible state heuristic
|
|
75
|
+
await page.evaluate(() => {
|
|
76
|
+
const el = document.querySelector('[data-topo-chrome-layout="grid"]');
|
|
77
|
+
if (el) el.matches(':focus-visible');
|
|
78
|
+
});
|
|
79
|
+
const focusVisibleProbe = await page.evaluate(() => {
|
|
80
|
+
const el = document.querySelector('[data-topo-chrome-layout="grid"]');
|
|
81
|
+
if (!el) return null;
|
|
82
|
+
return {
|
|
83
|
+
isActiveElement: document.activeElement === el,
|
|
84
|
+
// Inspect outline & box-shadow because the Tailwind focus-visible:ring
|
|
85
|
+
// class compiles to a box-shadow. After .focus() Chromium reports
|
|
86
|
+
// :focus-visible heuristically — we verify the class fragment exists
|
|
87
|
+
// in the stylesheet bound to this element rather than relying on the
|
|
88
|
+
// dynamic pseudo-class match.
|
|
89
|
+
classFragments: {
|
|
90
|
+
hasFocusVisibleRingClass: (el.getAttribute('class') || '').includes('focus-visible:ring-cyan-400/60'),
|
|
91
|
+
hasRingInset: (el.getAttribute('class') || '').includes('focus-visible:ring-inset'),
|
|
92
|
+
hasFocusOutlineNone: (el.getAttribute('class') || '').includes('focus:outline-none'),
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Hover the inactive (grid) button — bg should shift away from transparent.
|
|
98
|
+
const gridBgBefore = await page.evaluate(() => {
|
|
99
|
+
const el = document.querySelector('[data-topo-chrome-layout="grid"]');
|
|
100
|
+
return el ? getComputedStyle(el).backgroundColor : null;
|
|
101
|
+
});
|
|
102
|
+
await page.locator('[data-topo-chrome-layout="grid"]').hover();
|
|
103
|
+
await page.waitForTimeout(300);
|
|
104
|
+
const gridBgHover = await page.evaluate(() => {
|
|
105
|
+
const el = document.querySelector('[data-topo-chrome-layout="grid"]');
|
|
106
|
+
return el ? getComputedStyle(el).backgroundColor : null;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await browser.close();
|
|
110
|
+
|
|
111
|
+
const results = {
|
|
112
|
+
both_buttons_found: probe !== null,
|
|
113
|
+
ring_is_active: probe?.ring_active === 'true',
|
|
114
|
+
grid_is_inactive: probe?.grid_active === 'false',
|
|
115
|
+
ring_aria_pressed: probe?.ring_pressed === 'true',
|
|
116
|
+
grid_aria_unpressed: probe?.grid_pressed === 'false',
|
|
117
|
+
active_has_cyan_bg_class: probe?.ring_class.includes('bg-cyan-500/15'),
|
|
118
|
+
inactive_hover_bg_class: probe?.grid_class.includes('hover:bg-cyan-500/5'),
|
|
119
|
+
active_hover_bg_class: probe?.ring_class.includes('hover:bg-cyan-500/20'),
|
|
120
|
+
focus_outline_none: focusVisibleProbe?.classFragments?.hasFocusOutlineNone === true,
|
|
121
|
+
focus_visible_ring_class: focusVisibleProbe?.classFragments?.hasFocusVisibleRingClass === true,
|
|
122
|
+
focus_visible_ring_inset: focusVisibleProbe?.classFragments?.hasRingInset === true,
|
|
123
|
+
inactive_bg_before: gridBgBefore !== null,
|
|
124
|
+
hover_changes_bg: gridBgBefore !== gridBgHover,
|
|
125
|
+
};
|
|
126
|
+
const ok = Object.values(results).every(Boolean);
|
|
127
|
+
console.log(`${ok ? '✅' : '❌'} layout toggle polish:`, JSON.stringify(results),
|
|
128
|
+
`\n probe=`, probe,
|
|
129
|
+
`\n focus=`, focusVisibleProbe,
|
|
130
|
+
`\n bg before/hover=`, gridBgBefore, '→', gridBgHover);
|
|
131
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/* Round 204 verification: legend count text recedes when its status
|
|
2
|
+
* tier is empty (count = 0). Pre-R204 "0" at opacity 0.65 read
|
|
3
|
+
* visually identical to "12" at opacity 0.65; R204 drops empty rows
|
|
4
|
+
* to 0.30 (dark) / 0.28 (light) so the eye gets a "no nodes in this
|
|
5
|
+
* tier" signal without parsing the digit.
|
|
6
|
+
*
|
|
7
|
+
* Test (cyber dark, all 4 sessions = working → idle=0, offline=0):
|
|
8
|
+
* 1. Three legend count texts present with data-legend-count for
|
|
9
|
+
* working / idle / offline.
|
|
10
|
+
* 2. Working row: count='4', data-legend-count-empty='false',
|
|
11
|
+
* opacity = 0.65 (idle, not hovered).
|
|
12
|
+
* 3. Idle row: count='0', data-legend-count-empty='true',
|
|
13
|
+
* opacity ≈ 0.30 (dim recede).
|
|
14
|
+
* 4. Offline row: count='0', data-legend-count-empty='true',
|
|
15
|
+
* opacity ≈ 0.30.
|
|
16
|
+
* 5. Hover working row → its count bumps to opacity ~0.95
|
|
17
|
+
* (existing R95 hover behaviour unbroken).
|
|
18
|
+
* 6. Hover idle row → empty-tier opacity DOES NOT bump (empty
|
|
19
|
+
* gate wins over hover). Recedes regardless.
|
|
20
|
+
* 7. Transition: opacity 150ms ease-out still on all three.
|
|
21
|
+
*/
|
|
22
|
+
import { chromium } from 'playwright';
|
|
23
|
+
import { readFileSync } from 'node:fs';
|
|
24
|
+
|
|
25
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
26
|
+
const browser = await chromium.launch({ headless: true });
|
|
27
|
+
|
|
28
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
29
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
30
|
+
await ctx.addInitScript(() => {
|
|
31
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
35
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
36
|
+
const r = await route.fetch();
|
|
37
|
+
const b = await r.json();
|
|
38
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
39
|
+
// All 4 working — idle / offline tiers empty
|
|
40
|
+
const mk = (alias) => ({
|
|
41
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
42
|
+
network_id: nid, project_dir: null,
|
|
43
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
44
|
+
});
|
|
45
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
46
|
+
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
47
|
+
] } });
|
|
48
|
+
});
|
|
49
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
50
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
51
|
+
|
|
52
|
+
const page = await ctx.newPage();
|
|
53
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
54
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
55
|
+
await page.waitForSelector('[data-legend-count="working"]', { timeout: 10000 });
|
|
56
|
+
await page.waitForTimeout(300);
|
|
57
|
+
|
|
58
|
+
const probe = async () => page.evaluate(() => {
|
|
59
|
+
const pick = (key) => {
|
|
60
|
+
const el = document.querySelector(`[data-legend-count="${key}"]`);
|
|
61
|
+
if (!el) return null;
|
|
62
|
+
return {
|
|
63
|
+
key,
|
|
64
|
+
text: el.textContent,
|
|
65
|
+
empty: el.getAttribute('data-legend-count-empty'),
|
|
66
|
+
opacity: parseFloat(el.getAttribute('opacity') || ''),
|
|
67
|
+
transition: el.style.transition,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
return { working: pick('working'), idle: pick('idle'), offline: pick('offline') };
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const idle = await probe();
|
|
74
|
+
|
|
75
|
+
// Hover the working row — verify R95 hover bump still fires (count > 0)
|
|
76
|
+
await page.hover('[data-legend-status="working"]');
|
|
77
|
+
await page.waitForTimeout(200);
|
|
78
|
+
const hovWorking = await probe();
|
|
79
|
+
|
|
80
|
+
// Move off, then hover the empty (idle=0) row — verify empty gate wins
|
|
81
|
+
await page.mouse.move(0, 0);
|
|
82
|
+
await page.waitForTimeout(200);
|
|
83
|
+
await page.hover('[data-legend-status="idle"]');
|
|
84
|
+
await page.waitForTimeout(200);
|
|
85
|
+
const hovIdle = await probe();
|
|
86
|
+
|
|
87
|
+
await page.mouse.move(0, 0);
|
|
88
|
+
await page.waitForTimeout(200);
|
|
89
|
+
|
|
90
|
+
await browser.close();
|
|
91
|
+
|
|
92
|
+
const opacityNear = (got, want, tol = 0.04) => Math.abs(got - want) < tol;
|
|
93
|
+
|
|
94
|
+
const results = {
|
|
95
|
+
working_present: !!idle?.working,
|
|
96
|
+
idle_present: !!idle?.idle,
|
|
97
|
+
offline_present: !!idle?.offline,
|
|
98
|
+
|
|
99
|
+
working_text_4: idle?.working?.text === '4',
|
|
100
|
+
working_not_empty: idle?.working?.empty === 'false',
|
|
101
|
+
working_opacity_0p65: opacityNear(idle?.working?.opacity ?? 0, 0.65),
|
|
102
|
+
|
|
103
|
+
idle_text_0: idle?.idle?.text === '0',
|
|
104
|
+
idle_empty_true: idle?.idle?.empty === 'true',
|
|
105
|
+
idle_opacity_dim: opacityNear(idle?.idle?.opacity ?? 0, 0.30),
|
|
106
|
+
|
|
107
|
+
offline_text_0: idle?.offline?.text === '0',
|
|
108
|
+
offline_empty_true: idle?.offline?.empty === 'true',
|
|
109
|
+
offline_opacity_dim: opacityNear(idle?.offline?.opacity ?? 0, 0.30),
|
|
110
|
+
|
|
111
|
+
// R95 hover still bumps when row populated
|
|
112
|
+
working_hover_opacity_high: opacityNear(hovWorking?.working?.opacity ?? 0, 0.95),
|
|
113
|
+
|
|
114
|
+
// Empty-gate wins: hovering an empty row does NOT bump its count opacity
|
|
115
|
+
idle_hover_stays_dim: opacityNear(hovIdle?.idle?.opacity ?? 0, 0.30),
|
|
116
|
+
|
|
117
|
+
// Transition still in place across all 3
|
|
118
|
+
all_have_transition: ['working', 'idle', 'offline'].every(k =>
|
|
119
|
+
/opacity\s+(?:150ms|0\.15s)/.test(idle?.[k]?.transition || '')),
|
|
120
|
+
};
|
|
121
|
+
const ok = Object.values(results).every(Boolean);
|
|
122
|
+
console.log(`${ok ? '✅' : '❌'} legend count empty:`, JSON.stringify(results),
|
|
123
|
+
`\n idle:`, idle,
|
|
124
|
+
`\n hovWorking:`, hovWorking,
|
|
125
|
+
`\n hovIdle:`, hovIdle);
|
|
126
|
+
process.exit(ok ? 0 : 1);
|