@sleep2agi/agent-network-dashboard 0.4.6-preview.99 → 0.5.0
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 +2 -0
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +31 -31
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +16 -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/server/[hostname]/agents/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/hub/server/[hostname]/agents/route/build-manifest.json +9 -0
- package/.next/server/app/api/hub/server/[hostname]/agents/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/hub/server/[hostname]/agents/route.js +7 -0
- package/.next/server/app/api/hub/server/[hostname]/agents/route.js.map +5 -0
- package/.next/server/app/api/hub/server/[hostname]/agents/route.js.nft.json +1 -0
- package/.next/server/app/api/hub/server/[hostname]/agents/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route/build-manifest.json +9 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route.js +7 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route.js.map +5 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route.js.nft.json +1 -0
- package/.next/server/app/api/hub/server/[hostname]/health/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 +2 -0
- package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js +3 -0
- package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js.map +1 -0
- package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js +3 -0
- package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__05nyivw._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__05nyivw._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0floe3h._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0floe3h._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -2
- 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_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_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/0aauz~36q5n2a.css +2 -0
- package/.next/static/chunks/0f~0kgy2pycyc.js +1 -0
- package/.next/static/chunks/0l_~q07bhpkcx.js +7 -0
- package/.next/static/chunks/1175-dp18a1e_.js +1 -0
- package/.next/static/chunks/1552j0415oeal.js +4 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +3 -1
- package/.next/types/validator.ts +18 -0
- package/app/api/hub/server/[hostname]/agents/route.ts +62 -0
- package/app/api/hub/server/[hostname]/health/route.ts +58 -0
- package/app/api/hub/servers/route.ts +88 -1
- package/app/components/CommandPalette.tsx +41 -1
- package/app/components/HelpOverlay.tsx +55 -0
- package/app/components/ServersDrawer.tsx +181 -5
- package/app/components/TopoGraph.tsx +5514 -336
- package/app/globals.css +93 -6
- package/app/lib/vendorIdentity.ts +14 -3
- package/docs/rfc/v0.10.0-hero3-dashboard-upgrade.md +230 -0
- package/package.json +2 -2
- package/public/sleep2agi-logo.svg +11 -0
- package/public/vendors/claude.svg +11 -0
- package/public/vendors/minimax.svg +11 -0
- package/public/vendors/openai.svg +12 -0
- package/screenshots/r293-p0/after-full.png +0 -0
- package/screenshots/r293-p0/after-topo.png +0 -0
- package/screenshots/r293-p0/baseline-full.png +0 -0
- package/screenshots/r293-p0/baseline-topo.png +0 -0
- package/screenshots/r293-p0/logo-integrated-full.png +0 -0
- package/screenshots/r293-p0/logo-integrated-topo.png +0 -0
- package/screenshots/r293-p0/servers-drawer-v0.10.0-full.png +0 -0
- package/screenshots/r293-p0/servers-drawer-v0.10.0.png +0 -0
- package/screenshots/r293-p0/wave1-after-full.png +0 -0
- package/screenshots/r293-p0/wave1-after-topo.png +0 -0
- package/scripts/p0-servers-drawer-screenshot.mjs +99 -0
- package/scripts/p0-topo-screenshot.mjs +82 -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-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-brand-logo-transition-test.mjs +94 -0
- package/scripts/topo-brand-watermark-test.mjs +97 -0
- package/scripts/topo-canvas-aria-test.mjs +108 -0
- package/scripts/topo-canvas-brand-fade-test.mjs +87 -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-row-gap-test.mjs +90 -0
- package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
- package/scripts/topo-chip-row-tabular-test.mjs +111 -0
- package/scripts/topo-chip-unit-demotion-test.mjs +93 -0
- package/scripts/topo-chiprow-data-weight-test.mjs +94 -0
- package/scripts/topo-chrome-a11y-test.mjs +107 -0
- package/scripts/topo-chrome-active-weight-test.mjs +104 -0
- package/scripts/topo-chrome-focus-ring-unify-test.mjs +100 -0
- package/scripts/topo-chrome-gap-test.mjs +73 -0
- package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
- package/scripts/topo-chrome-inactive-color-test.mjs +90 -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-strip-semantic-gap-test.mjs +99 -0
- package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -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-chrome-zoom-readout-weight-test.mjs +81 -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-vendor-pin-test.mjs +103 -0
- package/scripts/topo-corner-radius-unify-test.mjs +103 -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-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-fadein-test.mjs +106 -0
- package/scripts/topo-edge-pill-hot-test.mjs +129 -0
- package/scripts/topo-edge-pill-test.mjs +105 -0
- package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
- package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
- package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
- package/scripts/topo-empty-hint-tracking-test.mjs +84 -0
- package/scripts/topo-empty-state-breath-test.mjs +150 -0
- package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
- package/scripts/topo-empty-state-recenter-test.mjs +107 -0
- package/scripts/topo-empty-subhint-tracking-test.mjs +87 -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-filter-pill-aliases-test.mjs +121 -0
- package/scripts/topo-filter-pill-count-tabular-test.mjs +135 -0
- package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
- package/scripts/topo-freshness-chip-weight-test.mjs +90 -0
- package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
- package/scripts/topo-fullscreen-active-test.mjs +115 -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-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-pip-fadein-test.mjs +104 -0
- package/scripts/topo-group-pip-singletier-drop-test.mjs +131 -0
- package/scripts/topo-group-pip-tabular-test.mjs +95 -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-march-busyness-test.mjs +155 -0
- package/scripts/topo-header-gap-test.mjs +94 -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-hot-tail-crossfade-test.mjs +117 -0
- package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
- package/scripts/topo-hub-a11y-test.mjs +112 -0
- package/scripts/topo-hub-center-crossfade-test.mjs +124 -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-working-count-test.mjs +99 -0
- package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
- package/scripts/topo-kicker-color-test.mjs +93 -0
- package/scripts/topo-kicker-tracking-test.mjs +101 -0
- package/scripts/topo-kicker-weight-test.mjs +98 -0
- package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
- package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
- package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
- package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
- package/scripts/topo-layout-toggle-test.mjs +131 -0
- package/scripts/topo-layout-trailer-mr-test.mjs +82 -0
- package/scripts/topo-legend-compress-test.mjs +107 -0
- package/scripts/topo-legend-count-empty-test.mjs +126 -0
- package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
- package/scripts/topo-legend-count-tabular-test.mjs +87 -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-count-weight-test.mjs +86 -0
- package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
- package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
- package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
- package/scripts/topo-legend-offline-label-simplify-test.mjs +86 -0
- package/scripts/topo-legend-panel-count-weight-test.mjs +89 -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-row-hover-lift-test.mjs +109 -0
- package/scripts/topo-legend-row-labels-simplify-test.mjs +80 -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-legend-swatch-radius-test.mjs +78 -0
- package/scripts/topo-minimap-a11y-test.mjs +101 -0
- package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
- package/scripts/topo-minimap-radius-test.mjs +88 -0
- package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
- package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
- package/scripts/topo-monogram-font-test.mjs +112 -0
- package/scripts/topo-monogram-stroke-test.mjs +101 -0
- package/scripts/topo-more-flows-letterspacing-test.mjs +111 -0
- package/scripts/topo-node-a11y-test.mjs +107 -0
- package/scripts/topo-node-alias-chat-pin-test.mjs +95 -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-particles-retired-test.mjs +91 -0
- package/scripts/topo-panel-count-unit-test.mjs +112 -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-rx-test.mjs +90 -0
- package/scripts/topo-panel-theme-ease-test.mjs +85 -0
- package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
- package/scripts/topo-panel-title-tracking-test.mjs +78 -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-prefix-opacity-test.mjs +107 -0
- package/scripts/topo-prepublish-guard-test.mjs +95 -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-dot-retired-test.mjs +86 -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-count-weight-test.mjs +91 -0
- package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
- package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
- package/scripts/topo-recent-panel-hot-tabular-test.mjs +113 -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-panel-readability-bump-test.mjs +151 -0
- package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
- package/scripts/topo-recent-row-count-weight-test.mjs +112 -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-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-tabular-test.mjs +108 -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-reset-spin-test.mjs +120 -0
- package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
- package/scripts/topo-spokes-retired-test.mjs +93 -0
- package/scripts/topo-starfield-density-test.mjs +89 -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-guide-dashes-test.mjs +86 -0
- package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
- package/scripts/topo-tier-ring-tint-test.mjs +110 -0
- package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
- package/scripts/topo-title-tracking-test.mjs +98 -0
- package/scripts/topo-titleblock-gap-test.mjs +86 -0
- package/scripts/topo-titleblock-margin-test.mjs +83 -0
- package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
- package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
- package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
- package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
- package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
- package/scripts/topo-vendor-chip-weight-test.mjs +87 -0
- package/scripts/topo-vendor-count-suffix-test.mjs +95 -0
- package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
- package/scripts/topo-view-smooth-test.mjs +131 -0
- package/scripts/topo-watermark-tracking-test.mjs +99 -0
- package/scripts/topo-working-chip-pin-test.mjs +154 -0
- package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
- package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
- package/scripts/topo-wrapper-radius-test.mjs +73 -0
- package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
- package/.next/static/chunks/03w4rgx_vt8ec.js +0 -1
- package/.next/static/chunks/072so8rfx2tt-.css +0 -2
- package/.next/static/chunks/0o.k-.0kl4bz5.js +0 -2
- package/.next/static/chunks/121d8yvfl8l_..js +0 -7
- package/.next/static/chunks/148o1e4ijgwp1.js +0 -1
- package/scripts/topo-arrival-ping-test.mjs +0 -87
- package/scripts/topo-dispatch-pulse-test.mjs +0 -83
- /package/.next/static/{Dhky2WTh8mvJhw56taHVs → 424zX3i80AJ_wHF_otKuP}/_buildManifest.js +0 -0
- /package/.next/static/{Dhky2WTh8mvJhw56taHVs → 424zX3i80AJ_wHF_otKuP}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{Dhky2WTh8mvJhw56taHVs → 424zX3i80AJ_wHF_otKuP}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* Round 275 verification: FreshnessChip converts from always-render
|
|
2
|
+
* to conditional render (only when stale) — chip-row simplification
|
|
3
|
+
* per Vincent 5214/5215-5217 visual audit ask ("乱" cleanup for
|
|
4
|
+
* Twitter screenshot).
|
|
5
|
+
*
|
|
6
|
+
* Pre-R275 the chip ALWAYS rendered:
|
|
7
|
+
* fresh: gray + "live · {sec}s" (always-present "I'm fine" chip)
|
|
8
|
+
* stale: amber + "lag · {sec}s" (R272 prefix swap)
|
|
9
|
+
*
|
|
10
|
+
* Post-R275:
|
|
11
|
+
* fresh: chip absent (null)
|
|
12
|
+
* stale: amber + "lag · {sec}s" (warning indicator only)
|
|
13
|
+
*
|
|
14
|
+
* Net effect: chip-row at rest has 1 fewer chip — cleaner Twitter
|
|
15
|
+
* screenshot, less right-edge chrome. Stale state still alerts via
|
|
16
|
+
* conditional appearance (amber "lag" warning chip).
|
|
17
|
+
*
|
|
18
|
+
* R272 prefix-swap intent (live↔lag) still in code; only the lag side
|
|
19
|
+
* is rendered now. Fresh state implicitly relies on other liveness
|
|
20
|
+
* signals (recent-signal panel rows, edge animations, count updates).
|
|
21
|
+
*
|
|
22
|
+
* Test scope:
|
|
23
|
+
* 1. At fresh state (default page load, 0s elapsed), chip element
|
|
24
|
+
* is NOT present in DOM. R275 simplification verified.
|
|
25
|
+
* 2. R273 Layout toggle Grid inactive has hover:text-cyan-300
|
|
26
|
+
* (regression).
|
|
27
|
+
* 3. R274 legend per-row count has tabular-nums (regression).
|
|
28
|
+
* 4. Source code regression: FreshnessChip body still contains the
|
|
29
|
+
* "lag" / "live" ternary — verified by reading the bundled JS
|
|
30
|
+
* isn't worth the effort; trust the source diff + this fresh-
|
|
31
|
+
* absence test as proof R275 applied.
|
|
32
|
+
*/
|
|
33
|
+
import { chromium } from 'playwright';
|
|
34
|
+
import { readFileSync } from 'node:fs';
|
|
35
|
+
|
|
36
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
37
|
+
const browser = await chromium.launch({ headless: true });
|
|
38
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
39
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
40
|
+
await ctx.addInitScript(() => {
|
|
41
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
42
|
+
});
|
|
43
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
44
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
45
|
+
const r = await route.fetch();
|
|
46
|
+
const b = await r.json();
|
|
47
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
48
|
+
const mk = (alias) => ({
|
|
49
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
50
|
+
network_id: nid, project_dir: null,
|
|
51
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
52
|
+
});
|
|
53
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
54
|
+
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
55
|
+
] } });
|
|
56
|
+
});
|
|
57
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
58
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.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 === 4, { timeout: 30000 });
|
|
63
|
+
await page.waitForSelector('[data-topo-chrome-layout="grid"]', { timeout: 10000 });
|
|
64
|
+
await page.waitForSelector('[data-legend-count="working"]', { timeout: 10000 });
|
|
65
|
+
// Give the page time to settle so FreshnessChip mount cycle is stable.
|
|
66
|
+
// Note: we are NOT waiting for [data-freshness-chip] — that selector
|
|
67
|
+
// should NOT match in fresh state (chip absent). Use a short fixed
|
|
68
|
+
// wait instead.
|
|
69
|
+
await page.waitForTimeout(500);
|
|
70
|
+
|
|
71
|
+
const probe = await page.evaluate(() => {
|
|
72
|
+
const freshnessChip = document.querySelector('[data-freshness-chip]');
|
|
73
|
+
const grid = document.querySelector('[data-topo-chrome-layout="grid"]');
|
|
74
|
+
const legendCount = document.querySelector('[data-legend-count="working"]');
|
|
75
|
+
return {
|
|
76
|
+
chipPresentAtFresh: freshnessChip !== null,
|
|
77
|
+
gridClasses: grid ? grid.className.toString() : null,
|
|
78
|
+
legendCountFVN: legendCount ? window.getComputedStyle(legendCount).fontVariantNumeric : null,
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
await browser.close();
|
|
82
|
+
|
|
83
|
+
const results = {
|
|
84
|
+
chip_absent_at_fresh: probe.chipPresentAtFresh === false,
|
|
85
|
+
r273_grid_has_cyan_hover_text: (probe.gridClasses || '').includes('hover:text-cyan-300'),
|
|
86
|
+
r274_legend_count_tabular: probe.legendCountFVN != null && probe.legendCountFVN.includes('tabular-nums'),
|
|
87
|
+
};
|
|
88
|
+
const ok = Object.values(results).every(Boolean);
|
|
89
|
+
console.log(`${ok ? '✅' : '❌'} freshness conditional render:`, JSON.stringify(results),
|
|
90
|
+
'\n chip present at fresh:', probe.chipPresentAtFresh, '(expected: false)',
|
|
91
|
+
'\n legend count FVN:', probe.legendCountFVN);
|
|
92
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/* Round 178 verification: fullscreen chrome button picks up the
|
|
2
|
+
* active-state visual indicator R163 introduced for layout toggle.
|
|
3
|
+
*
|
|
4
|
+
* Pre-R178 the button changed icon when isFullscreen flipped but
|
|
5
|
+
* background + foreground stayed unchanged. R178 adds:
|
|
6
|
+
* active: bg-cyan-500/15 text-cyan-300 hover:bg-cyan-500/20
|
|
7
|
+
* inactive: hover:bg-white/5 + palette-driven inline bg/color
|
|
8
|
+
*
|
|
9
|
+
* Mirrors R163 Ring/Grid pattern.
|
|
10
|
+
*
|
|
11
|
+
* Test:
|
|
12
|
+
* 1. Probe inactive state (default — not in fullscreen):
|
|
13
|
+
* data-topo-chrome-fullscreen-active='false'
|
|
14
|
+
* className does NOT include 'bg-cyan-500/15'
|
|
15
|
+
* className includes 'hover:bg-white/5'
|
|
16
|
+
* inline style.background is set (from palette)
|
|
17
|
+
* 2. Fake fullscreen state via dispatching fullscreenchange
|
|
18
|
+
* with mocked document.fullscreenElement, then re-probe:
|
|
19
|
+
* data-topo-chrome-fullscreen-active='true'
|
|
20
|
+
* className includes 'bg-cyan-500/15' + 'text-cyan-300'
|
|
21
|
+
* className DOES NOT include 'hover:bg-white/5'
|
|
22
|
+
* inline style.background is unset (Tailwind wins)
|
|
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 { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
37
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
38
|
+
const r = await route.fetch();
|
|
39
|
+
const b = await r.json();
|
|
40
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
41
|
+
const mk = (alias) => ({
|
|
42
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
43
|
+
network_id: nid, project_dir: null,
|
|
44
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
45
|
+
});
|
|
46
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta'), mk('gamma')] } });
|
|
47
|
+
});
|
|
48
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
49
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
50
|
+
|
|
51
|
+
const page = await ctx.newPage();
|
|
52
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
53
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
|
|
54
|
+
await page.waitForSelector('[data-topo-chrome-fullscreen]', { timeout: 10000 });
|
|
55
|
+
await page.waitForTimeout(400);
|
|
56
|
+
|
|
57
|
+
const probe = () => page.evaluate(() => {
|
|
58
|
+
const btn = document.querySelector('[data-topo-chrome-fullscreen]');
|
|
59
|
+
if (!btn) return null;
|
|
60
|
+
return {
|
|
61
|
+
activeAttr: btn.getAttribute('data-topo-chrome-fullscreen-active'),
|
|
62
|
+
className: btn.getAttribute('class') || '',
|
|
63
|
+
inlineBg: btn.style.background || '',
|
|
64
|
+
inlineColor: btn.style.color || '',
|
|
65
|
+
ariaLabel: btn.getAttribute('aria-label'),
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const inactive = await probe();
|
|
70
|
+
|
|
71
|
+
// Fake fullscreen state: define document.fullscreenElement to point at
|
|
72
|
+
// the container, then dispatch fullscreenchange. The listener at
|
|
73
|
+
// TopoGraph.tsx line ~1125 calls
|
|
74
|
+
// setIsFullscreen(document.fullscreenElement === containerRef.current)
|
|
75
|
+
// We can find the container via the chrome's parent (the chrome <div>
|
|
76
|
+
// sits inside the container <div ref={containerRef}>).
|
|
77
|
+
await page.evaluate(() => {
|
|
78
|
+
const chrome = document.querySelector('[data-topo-chrome]');
|
|
79
|
+
const container = chrome?.parentElement;
|
|
80
|
+
if (!container) throw new Error('container not found');
|
|
81
|
+
Object.defineProperty(document, 'fullscreenElement', {
|
|
82
|
+
configurable: true,
|
|
83
|
+
get: () => container,
|
|
84
|
+
});
|
|
85
|
+
document.dispatchEvent(new Event('fullscreenchange'));
|
|
86
|
+
});
|
|
87
|
+
await page.waitForTimeout(250);
|
|
88
|
+
const active = await probe();
|
|
89
|
+
|
|
90
|
+
await browser.close();
|
|
91
|
+
|
|
92
|
+
const results = {
|
|
93
|
+
// Inactive defaults
|
|
94
|
+
inactive_attr_false: inactive.activeAttr === 'false',
|
|
95
|
+
inactive_no_cyan_bg_class: !inactive.className.includes('bg-cyan-500/15'),
|
|
96
|
+
inactive_has_white_hover: inactive.className.includes('hover:bg-white/5'),
|
|
97
|
+
inactive_inline_bg_set: inactive.inlineBg !== '',
|
|
98
|
+
inactive_inline_color_set: inactive.inlineColor !== '',
|
|
99
|
+
inactive_aria_label_enter: /Enter fullscreen/.test(inactive.ariaLabel || ''),
|
|
100
|
+
|
|
101
|
+
// Active state (after faking fullscreenchange)
|
|
102
|
+
active_attr_true: active.activeAttr === 'true',
|
|
103
|
+
active_has_cyan_bg_class: active.className.includes('bg-cyan-500/15'),
|
|
104
|
+
active_has_cyan_text_class: active.className.includes('text-cyan-300'),
|
|
105
|
+
active_has_cyan_hover_class: active.className.includes('hover:bg-cyan-500/20'),
|
|
106
|
+
active_no_white_hover: !active.className.includes('hover:bg-white/5'),
|
|
107
|
+
active_inline_bg_empty: active.inlineBg === '',
|
|
108
|
+
active_inline_color_empty: active.inlineColor === '',
|
|
109
|
+
active_aria_label_exit: /Exit fullscreen/.test(active.ariaLabel || ''),
|
|
110
|
+
};
|
|
111
|
+
const ok = Object.values(results).every(Boolean);
|
|
112
|
+
console.log(`${ok ? '✅' : '❌'} fullscreen active state:`, JSON.stringify(results),
|
|
113
|
+
`\n inactive =`, inactive,
|
|
114
|
+
`\n active =`, active);
|
|
115
|
+
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);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* Round 229 verification: group-label member-count tspan drops
|
|
2
|
+
* its explicit fill (now inherits from parent <text>) and picks up
|
|
3
|
+
* fontVariantNumeric: 'tabular-nums'.
|
|
4
|
+
*
|
|
5
|
+
* Two scopes:
|
|
6
|
+
* 1) Hover-deepen-own-hue family — count chip color now follows
|
|
7
|
+
* parent name's R142 hover transition (legendText → legend-
|
|
8
|
+
* Headline). Tonal hierarchy (count < name brightness) is
|
|
9
|
+
* preserved across both rest AND hover instead of inverting
|
|
10
|
+
* on hover.
|
|
11
|
+
* 2) Info-density tabular-nums — 5th surface after R224 edge
|
|
12
|
+
* badge / R225 hub + panel header + recent row.
|
|
13
|
+
*
|
|
14
|
+
* Scenario: 6 prefix-shared aliases (alpha-1..alpha-3, beta-1..
|
|
15
|
+
* beta-3) → R106 cluster-by-prefix forms two groups, each with
|
|
16
|
+
* count=3. Grid layout has explicit group label rendering (#111).
|
|
17
|
+
*
|
|
18
|
+
* Verifications:
|
|
19
|
+
* - data-group-label-count attr present (probe selector)
|
|
20
|
+
* - tspan has NO explicit fill attribute (inheriting)
|
|
21
|
+
* - getComputedStyle().fontVariantNumeric contains 'tabular-nums'
|
|
22
|
+
* - tspan computed fill matches PARENT <text> computed fill
|
|
23
|
+
* (R142 rest-state hierarchy preserved)
|
|
24
|
+
* - count text matches '· 3'
|
|
25
|
+
* - data-group-label-count-value reflects {box.count}
|
|
26
|
+
*/
|
|
27
|
+
import { chromium } from 'playwright';
|
|
28
|
+
import { readFileSync } from 'node:fs';
|
|
29
|
+
|
|
30
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
31
|
+
const browser = await chromium.launch({ headless: true });
|
|
32
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
33
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
34
|
+
await ctx.addInitScript(() => {
|
|
35
|
+
try {
|
|
36
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
37
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
38
|
+
// R226 grid layout — group labels render here. Storage key inferred
|
|
39
|
+
// from data-topo-layout-toggle persistence pattern.
|
|
40
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
41
|
+
} catch {}
|
|
42
|
+
});
|
|
43
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
44
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
45
|
+
const r = await route.fetch();
|
|
46
|
+
const b = await r.json();
|
|
47
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
48
|
+
const mk = (alias) => ({
|
|
49
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
50
|
+
network_id: nid, project_dir: null,
|
|
51
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
52
|
+
});
|
|
53
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
54
|
+
mk('alpha-1'), mk('alpha-2'), mk('alpha-3'),
|
|
55
|
+
mk('beta-1'), mk('beta-2'), mk('beta-3'),
|
|
56
|
+
] } });
|
|
57
|
+
});
|
|
58
|
+
await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
|
|
59
|
+
await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
|
|
60
|
+
|
|
61
|
+
const page = await ctx.newPage();
|
|
62
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
63
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 6, { timeout: 30000 });
|
|
64
|
+
await page.waitForSelector('[data-group-label-count]', { timeout: 10000, state: 'attached' });
|
|
65
|
+
await page.waitForTimeout(400);
|
|
66
|
+
|
|
67
|
+
const out = await page.evaluate(() => {
|
|
68
|
+
const counts = Array.from(document.querySelectorAll('[data-group-label-count]'));
|
|
69
|
+
return counts.map((tspan) => {
|
|
70
|
+
const parent = tspan.parentElement; // the <text data-group-label>
|
|
71
|
+
return {
|
|
72
|
+
key: tspan.getAttribute('data-group-label-count'),
|
|
73
|
+
countValue: tspan.getAttribute('data-group-label-count-value'),
|
|
74
|
+
hasFillAttr: tspan.hasAttribute('fill'),
|
|
75
|
+
text: tspan.textContent,
|
|
76
|
+
fontVarNumeric: getComputedStyle(tspan).fontVariantNumeric,
|
|
77
|
+
tspanFill: getComputedStyle(tspan).fill,
|
|
78
|
+
parentFill: parent ? getComputedStyle(parent).fill : null,
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
await browser.close();
|
|
83
|
+
|
|
84
|
+
const hasTab = (s) => /tabular-nums/.test(s || '');
|
|
85
|
+
const present = out.length > 0;
|
|
86
|
+
|
|
87
|
+
const results = {
|
|
88
|
+
groups_found: out.length === 2,
|
|
89
|
+
all_have_key: present && out.every(o => typeof o.key === 'string' && o.key.length > 0),
|
|
90
|
+
all_count_3: present && out.every(o => o.countValue === '3'),
|
|
91
|
+
all_text_dot_3: present && out.every(o => /·\s*3$/.test((o.text || '').trim())),
|
|
92
|
+
all_no_fill_attr: present && out.every(o => !o.hasFillAttr),
|
|
93
|
+
all_tabular_nums: present && out.every(o => hasTab(o.fontVarNumeric)),
|
|
94
|
+
fill_inherits_parent: present && out.every(o => o.tspanFill === o.parentFill),
|
|
95
|
+
};
|
|
96
|
+
const ok = Object.values(results).every(Boolean);
|
|
97
|
+
console.log(`${ok ? '✅' : '❌'} group-label count inherit + tabular:`, JSON.stringify(results),
|
|
98
|
+
'\n probed:', out);
|
|
99
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/* Round 218 verification: group label text gains letter-spacing
|
|
2
|
+
* transition on pin — 0px → 0.5px when group is pinned. Hover and
|
|
3
|
+
* pin share fill colour (R63 legendHeadline) so pre-R218 they
|
|
4
|
+
* looked typographically identical. R218 spreads the pinned text
|
|
5
|
+
* to read "locked in" at the text level.
|
|
6
|
+
*
|
|
7
|
+
* Test (cyber dark, 17 sessions in 2 prefix groups → 'group-A' +
|
|
8
|
+
* 'group-B' groupBoxes after switch to grid layout):
|
|
9
|
+
* 1. Switch to grid layout to trigger groupBoxes rendering.
|
|
10
|
+
* 2. Both group labels render with data-group-label.
|
|
11
|
+
* 3. Idle: data-group-label-pinned='false', computed
|
|
12
|
+
* letter-spacing ~ 0px.
|
|
13
|
+
* 4. Inline transition includes 'letter-spacing 200ms'.
|
|
14
|
+
* 5. Click one group label → that group becomes pinned.
|
|
15
|
+
* 6. Pinned label: data-group-label-pinned='true', computed
|
|
16
|
+
* letter-spacing ~ 0.5px.
|
|
17
|
+
* 7. Other (un-pinned) group stays at 0px letter-spacing.
|
|
18
|
+
* 8. Click pinned again → unpin → back to 0px.
|
|
19
|
+
*/
|
|
20
|
+
import { chromium } from 'playwright';
|
|
21
|
+
import { readFileSync } from 'node:fs';
|
|
22
|
+
|
|
23
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
24
|
+
const browser = await chromium.launch({ headless: true });
|
|
25
|
+
|
|
26
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
27
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
28
|
+
await ctx.addInitScript(() => {
|
|
29
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
33
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
34
|
+
const r = await route.fetch();
|
|
35
|
+
const b = await r.json();
|
|
36
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
37
|
+
const mk = (alias) => ({
|
|
38
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
39
|
+
network_id: nid, project_dir: null,
|
|
40
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
41
|
+
});
|
|
42
|
+
// Two prefix groups: 'alpha-*' and 'beta-*' (each ≥2 → forms a group)
|
|
43
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
44
|
+
mk('alpha-1'), mk('alpha-2'), mk('alpha-3'),
|
|
45
|
+
mk('beta-1'), mk('beta-2'), mk('beta-3'),
|
|
46
|
+
] } });
|
|
47
|
+
});
|
|
48
|
+
await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
|
|
49
|
+
await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
|
|
50
|
+
|
|
51
|
+
const page = await ctx.newPage();
|
|
52
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
53
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 6, { timeout: 30000 });
|
|
54
|
+
await page.waitForSelector('[data-topo-chrome-layout="grid"]', { timeout: 10000 });
|
|
55
|
+
await page.waitForTimeout(200);
|
|
56
|
+
|
|
57
|
+
// Switch to grid layout to trigger groupBoxes
|
|
58
|
+
await page.click('[data-topo-chrome-layout="grid"]');
|
|
59
|
+
await page.waitForTimeout(600);
|
|
60
|
+
await page.waitForSelector('[data-group-label]', { timeout: 5000, state: 'attached' });
|
|
61
|
+
await page.waitForTimeout(300);
|
|
62
|
+
|
|
63
|
+
const probe = async () => page.evaluate(() => {
|
|
64
|
+
return [...document.querySelectorAll('[data-group-label]')].map(el => {
|
|
65
|
+
const cs = getComputedStyle(el);
|
|
66
|
+
return {
|
|
67
|
+
key: el.getAttribute('data-group-label'),
|
|
68
|
+
pinned: el.getAttribute('data-group-label-pinned'),
|
|
69
|
+
spacing: cs.letterSpacing,
|
|
70
|
+
transition: el.style.transition,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const idle = await probe();
|
|
76
|
+
|
|
77
|
+
// Click first group label to pin it. The label hit area is the
|
|
78
|
+
// invisible rect — but the <text> itself often gets the click via
|
|
79
|
+
// the wrapper <g> with onClick. Find the parent <g role='button'>
|
|
80
|
+
// (data-group-label-hit) and click it.
|
|
81
|
+
const firstKey = idle[0]?.key;
|
|
82
|
+
if (firstKey) {
|
|
83
|
+
await page.click(`[data-group-label-hit="${firstKey}"]`);
|
|
84
|
+
await page.waitForTimeout(260); // past 200ms transition
|
|
85
|
+
}
|
|
86
|
+
const pinned = await probe();
|
|
87
|
+
|
|
88
|
+
// Click again to unpin
|
|
89
|
+
if (firstKey) {
|
|
90
|
+
await page.click(`[data-group-label-hit="${firstKey}"]`);
|
|
91
|
+
await page.waitForTimeout(260);
|
|
92
|
+
}
|
|
93
|
+
const unpinned = await probe();
|
|
94
|
+
|
|
95
|
+
await browser.close();
|
|
96
|
+
|
|
97
|
+
// CSS resolves `letterSpacing: '0px'` to the keyword 'normal' in
|
|
98
|
+
// computed style — they're equivalent. Accept both forms as "zero
|
|
99
|
+
// spacing"; numeric values still parse cleanly via parseFloat.
|
|
100
|
+
const near = (s, want, tol = 0.15) => {
|
|
101
|
+
if (want === 0 && (s === 'normal' || s === '0px' || s === '0' || !s)) return true;
|
|
102
|
+
const got = parseFloat(s || '0');
|
|
103
|
+
return Math.abs(got - want) < tol;
|
|
104
|
+
};
|
|
105
|
+
const hasLetterSpacingT = (s) => /letter-spacing\s+(?:200ms|0\.2s)/.test(s || '');
|
|
106
|
+
|
|
107
|
+
const firstIdle = idle[0];
|
|
108
|
+
const firstPinned = pinned.find(p => p.key === firstKey);
|
|
109
|
+
const otherIdle = idle[1];
|
|
110
|
+
const otherPinned = pinned.find(p => p.key !== firstKey);
|
|
111
|
+
const firstUnpin = unpinned.find(p => p.key === firstKey);
|
|
112
|
+
|
|
113
|
+
const results = {
|
|
114
|
+
two_groups_present: idle.length >= 2,
|
|
115
|
+
|
|
116
|
+
// Idle: all unpinned, spacing 0, transition present
|
|
117
|
+
all_idle_unpinned: idle.every(p => p.pinned === 'false'),
|
|
118
|
+
all_idle_spacing_zero: idle.every(p => near(p.spacing, 0)),
|
|
119
|
+
all_have_transition: idle.every(p => hasLetterSpacingT(p.transition)),
|
|
120
|
+
|
|
121
|
+
// First pinned, second still idle
|
|
122
|
+
first_pinned_attr: firstPinned?.pinned === 'true',
|
|
123
|
+
first_pinned_spacing: near(firstPinned?.spacing, 0.5),
|
|
124
|
+
other_stays_unpinned: otherPinned?.pinned === 'false',
|
|
125
|
+
other_spacing_unchanged: near(otherPinned?.spacing, 0),
|
|
126
|
+
|
|
127
|
+
// After unpin: spacing back to 0
|
|
128
|
+
unpinned_attr_false: firstUnpin?.pinned === 'false',
|
|
129
|
+
unpinned_spacing_zero: near(firstUnpin?.spacing, 0),
|
|
130
|
+
};
|
|
131
|
+
const ok = Object.values(results).every(Boolean);
|
|
132
|
+
console.log(`${ok ? '✅' : '❌'} group-label pin spacing:`, JSON.stringify(results),
|
|
133
|
+
`\n idle:`, idle,
|
|
134
|
+
`\n pinned:`, pinned,
|
|
135
|
+
`\n unpinned:`, unpinned);
|
|
136
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* Round 107 verification: group-label hit `<rect>` picks up a tint
|
|
2
|
+
* on hover and pin — extends the R104 (recent-signal) / R105
|
|
3
|
+
* (legend) list-item-tint pattern to the SVG group labels. The tint
|
|
4
|
+
* is pal.legendAccent (cyan) since groups don't carry an inherent
|
|
5
|
+
* swatch; same colour family R68 already uses for hovered/pinned
|
|
6
|
+
* group-box strokes.
|
|
7
|
+
*
|
|
8
|
+
* Verifies idle / hover / pinned via the new data-group-label-tinted
|
|
9
|
+
* attribute + fill/opacity. Pin opacity stronger than hover.
|
|
10
|
+
*/
|
|
11
|
+
import { chromium } from 'playwright';
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
|
|
14
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
15
|
+
const browser = await chromium.launch({ headless: true });
|
|
16
|
+
|
|
17
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
18
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
19
|
+
await ctx.addInitScript(() => {
|
|
20
|
+
try {
|
|
21
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
22
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
23
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
24
|
+
sessionStorage.removeItem('anet-topo-pinned-group');
|
|
25
|
+
} catch {}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
29
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
30
|
+
const r = await route.fetch();
|
|
31
|
+
const b = await r.json();
|
|
32
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
33
|
+
const mk = (alias) => ({
|
|
34
|
+
alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
35
|
+
network_id: nid, project_dir: null,
|
|
36
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
37
|
+
});
|
|
38
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
39
|
+
mk('alpha1'), mk('alpha2'), mk('alpha3'),
|
|
40
|
+
mk('beta1'), mk('beta2'),
|
|
41
|
+
] } });
|
|
42
|
+
});
|
|
43
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
44
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
45
|
+
const page = await ctx.newPage();
|
|
46
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
47
|
+
await page.waitForFunction(() => document.querySelectorAll('[data-group-label-hit]').length >= 2, { timeout: 30000 });
|
|
48
|
+
await page.waitForTimeout(400);
|
|
49
|
+
|
|
50
|
+
const readLabel = (key) => page.evaluate((k) => {
|
|
51
|
+
const g = document.querySelector(`[data-group-label-hit="${k}"]`);
|
|
52
|
+
if (!g) return null;
|
|
53
|
+
const rect = g.querySelector('rect');
|
|
54
|
+
return {
|
|
55
|
+
tinted: rect?.getAttribute('data-group-label-tinted') || '',
|
|
56
|
+
fill: rect?.getAttribute('fill') || '',
|
|
57
|
+
opacity: rect?.getAttribute('opacity') || '',
|
|
58
|
+
};
|
|
59
|
+
}, key);
|
|
60
|
+
|
|
61
|
+
const beforeAlpha = await readLabel('alpha');
|
|
62
|
+
const beforeBeta = await readLabel('beta');
|
|
63
|
+
|
|
64
|
+
// Hover alpha label.
|
|
65
|
+
await page.locator('[data-group-label-hit="alpha"]').hover();
|
|
66
|
+
await page.waitForTimeout(250);
|
|
67
|
+
const onHoverAlpha = await readLabel('alpha');
|
|
68
|
+
const onHoverBeta = await readLabel('beta');
|
|
69
|
+
|
|
70
|
+
// Move pointer far away.
|
|
71
|
+
await page.mouse.move(10, 10);
|
|
72
|
+
await page.waitForTimeout(250);
|
|
73
|
+
|
|
74
|
+
// Pin beta.
|
|
75
|
+
await page.locator('[data-group-label-hit="beta"]').click();
|
|
76
|
+
await page.mouse.move(10, 10);
|
|
77
|
+
await page.waitForTimeout(250);
|
|
78
|
+
const pinBeta = await readLabel('beta');
|
|
79
|
+
const pinAlphaUnchanged = await readLabel('alpha');
|
|
80
|
+
|
|
81
|
+
await browser.close();
|
|
82
|
+
|
|
83
|
+
const opacity = (s) => parseFloat(s) || 0;
|
|
84
|
+
const results = {
|
|
85
|
+
before_alphaTransparent: beforeAlpha?.tinted === 'none' && beforeAlpha?.fill === 'transparent',
|
|
86
|
+
before_betaTransparent: beforeBeta?.tinted === 'none' && beforeBeta?.fill === 'transparent',
|
|
87
|
+
hover_alphaTinted: onHoverAlpha?.tinted === 'hover' && onHoverAlpha?.fill !== 'transparent',
|
|
88
|
+
hover_alphaOpacityLow: opacity(onHoverAlpha?.opacity || '') > 0 && opacity(onHoverAlpha?.opacity || '') < 0.2,
|
|
89
|
+
hover_betaUnchanged: onHoverBeta?.tinted === 'none',
|
|
90
|
+
pin_betaPinned: pinBeta?.tinted === 'pinned' && pinBeta?.fill !== 'transparent',
|
|
91
|
+
pin_strongerThanHover: opacity(pinBeta?.opacity || '') > opacity(onHoverAlpha?.opacity || ''),
|
|
92
|
+
pin_alphaClean: pinAlphaUnchanged?.tinted === 'none',
|
|
93
|
+
};
|
|
94
|
+
const ok = Object.values(results).every(Boolean);
|
|
95
|
+
console.log(`${ok ? '✅' : '❌'} group-label tint:`, JSON.stringify(results),
|
|
96
|
+
`\n beforeAlpha=`, beforeAlpha,
|
|
97
|
+
`\n onHoverAlpha=`, onHoverAlpha,
|
|
98
|
+
`\n pinBeta=`, pinBeta);
|
|
99
|
+
process.exit(ok ? 0 : 1);
|