@sleep2agi/agent-network-dashboard 0.4.6 → 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 +107 -0
- package/app/components/HelpOverlay.tsx +109 -1
- package/app/components/ServersDrawer.tsx +181 -5
- package/app/components/TopoGraph.tsx +6409 -286
- package/app/globals.css +110 -5
- 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-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-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-pin-mirror-test.mjs +113 -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-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-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-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 +13 -5
- 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-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-tabular-test.mjs +135 -0
- package/scripts/topo-filter-pill-count-test.mjs +110 -0
- package/scripts/topo-filter-pill-mobile-test.mjs +90 -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-box-pinned-state-test.mjs +95 -0
- package/scripts/topo-group-count-test.mjs +6 -1
- 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 +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-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-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-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-html-overlay-padding-unify-test.mjs +120 -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 +7 -2
- package/scripts/topo-hub-working-count-test.mjs +99 -0
- package/scripts/topo-ingroup-edge-test.mjs +103 -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-label-lift-test.mjs +5 -1
- 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-hover-test.mjs +113 -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-pin-test.mjs +128 -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-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-particles-retired-test.mjs +91 -0
- package/scripts/topo-panel-count-unit-test.mjs +112 -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-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-pin-persistence-test.mjs +105 -0
- package/scripts/topo-prefix-opacity-test.mjs +107 -0
- package/scripts/topo-prepublish-guard-test.mjs +95 -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-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-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-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-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-guide-dashes-test.mjs +86 -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-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-hover-test.mjs +111 -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-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-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/088ouqy7tekty.js +0 -2
- package/.next/static/chunks/0hyl7mdapsz38.js +0 -1
- package/.next/static/chunks/0mqwn-l7wtcdo.js +0 -7
- package/.next/static/chunks/127kw7~wsnyu7.css +0 -2
- package/.next/static/chunks/16yfxp9ak2gdm.js +0 -1
- /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_buildManifest.js +0 -0
- /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* Round 281 verification: vendor letters chip threshold tightened
|
|
2
|
+
* from `vendorDist.length > 1` to `vendorDist.length > 2`.
|
|
3
|
+
*
|
|
4
|
+
* Pre-R281: chip rendered whenever ≥2 vendor types existed in the
|
|
5
|
+
* fleet. For a typical Twitter demo (claude + gpt = 2 types), the
|
|
6
|
+
* chip rendered "A:N O:M" adding ~50-80px to the chip-row width.
|
|
7
|
+
*
|
|
8
|
+
* Post-R281: chip only renders when ≥3 vendor types. The typical
|
|
9
|
+
* 1-2 vendor case (most demos) hides the chip; only fleets with
|
|
10
|
+
* actual diverse multi-vendor composition (3+ vendor types) show it.
|
|
11
|
+
*
|
|
12
|
+
* Scenario A: 2 vendor types (claude + gpt) — chip should be ABSENT
|
|
13
|
+
* Scenario B: 3 vendor types (claude + gpt + InternLM) — chip PRESENT
|
|
14
|
+
*/
|
|
15
|
+
import { chromium } from 'playwright';
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
|
|
18
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
19
|
+
const browser = await chromium.launch({ headless: true });
|
|
20
|
+
|
|
21
|
+
async function probeForScenario(sessions) {
|
|
22
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
23
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
24
|
+
await ctx.addInitScript(() => {
|
|
25
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
26
|
+
});
|
|
27
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
28
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
29
|
+
const r = await route.fetch();
|
|
30
|
+
const b = await r.json();
|
|
31
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
32
|
+
const mk = (alias, model) => ({
|
|
33
|
+
alias, status: 'working', model, runtime: 'claude-code-cli',
|
|
34
|
+
network_id: nid, project_dir: null,
|
|
35
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
36
|
+
});
|
|
37
|
+
await route.fulfill({ response: r, json: { ...b, sessions: sessions.map(s => mk(s.alias, s.model)) } });
|
|
38
|
+
});
|
|
39
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
40
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
41
|
+
|
|
42
|
+
const page = await ctx.newPage();
|
|
43
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
44
|
+
await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n, sessions.length, { timeout: 30000 });
|
|
45
|
+
await page.waitForTimeout(400);
|
|
46
|
+
const result = await page.evaluate(() => {
|
|
47
|
+
const chips = document.querySelectorAll('[data-vendor-letter]');
|
|
48
|
+
return { chipCount: chips.length };
|
|
49
|
+
});
|
|
50
|
+
await ctx.close();
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const twoVendors = await probeForScenario([
|
|
55
|
+
{ alias: 'alpha', model: 'claude-opus-4' },
|
|
56
|
+
{ alias: 'beta', model: 'claude-sonnet-4' },
|
|
57
|
+
{ alias: 'gamma', model: 'gpt-4o' },
|
|
58
|
+
{ alias: 'delta', model: 'gpt-4' },
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
const threeVendors = await probeForScenario([
|
|
62
|
+
{ alias: 'alpha', model: 'claude-opus-4' },
|
|
63
|
+
{ alias: 'beta', model: 'gpt-4o' },
|
|
64
|
+
{ alias: 'gamma', model: 'internlm/internlm2' },
|
|
65
|
+
{ alias: 'delta', model: 'some-unknown' },
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
await browser.close();
|
|
69
|
+
|
|
70
|
+
const results = {
|
|
71
|
+
two_vendor_chip_absent: twoVendors.chipCount === 0,
|
|
72
|
+
three_vendor_chip_present: threeVendors.chipCount >= 3,
|
|
73
|
+
};
|
|
74
|
+
const ok = Object.values(results).every(Boolean);
|
|
75
|
+
console.log(`${ok ? '✅' : '❌'} vendor chip threshold:`, JSON.stringify(results),
|
|
76
|
+
'\n 2-vendor scenario chip count (expect 0):', twoVendors.chipCount,
|
|
77
|
+
'\n 3-vendor scenario chip count (expect >=3):', threeVendors.chipCount);
|
|
78
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* Round 314 verification: vendor letter chips (e.g. 'A:N', 'O:N')
|
|
2
|
+
* pick up font-medium (500), completing the R312-R313 'HTML-context
|
|
3
|
+
* data chip = font-medium' family. R313 covered the 3 main chips
|
|
4
|
+
* (working/online/active-links); R314 closes the chip-row weight
|
|
5
|
+
* sweep at the vendor letters.
|
|
6
|
+
*
|
|
7
|
+
* Test fixture: 3 vendor types (claude + gpt + intern) to surface
|
|
8
|
+
* the chip row per R281 threshold (vendorDist.length > 2).
|
|
9
|
+
*
|
|
10
|
+
* Contract:
|
|
11
|
+
* - All [data-vendor-letter] chips have className containing
|
|
12
|
+
* 'font-medium' + 'tabular-nums'.
|
|
13
|
+
* - Computed font-weight === 500 for each.
|
|
14
|
+
* - R313 working/online/active chips still font-medium.
|
|
15
|
+
* - R312 chrome zoom still font-medium.
|
|
16
|
+
* - R311 recent count fw=600 + R309 row counts fw=600 + R294
|
|
17
|
+
* pulse absent all preserved.
|
|
18
|
+
*/
|
|
19
|
+
import { chromium } from 'playwright';
|
|
20
|
+
import { readFileSync } from 'node:fs';
|
|
21
|
+
|
|
22
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
23
|
+
const browser = await chromium.launch({ headless: true });
|
|
24
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
25
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
26
|
+
await ctx.addInitScript(() => {
|
|
27
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
28
|
+
});
|
|
29
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
30
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
31
|
+
const r = await route.fetch();
|
|
32
|
+
const b = await r.json();
|
|
33
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
34
|
+
const mk = (alias, model) => ({
|
|
35
|
+
alias, status: 'working', model, runtime: 'claude-code-cli',
|
|
36
|
+
network_id: nid, project_dir: null,
|
|
37
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
38
|
+
});
|
|
39
|
+
// 3 vendor types → R281 vendorDist.length > 2 threshold → chips render.
|
|
40
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
41
|
+
mk('alpha', 'claude-opus-4'),
|
|
42
|
+
mk('beta', 'gpt-4o'),
|
|
43
|
+
mk('gamma', 'internlm/internlm2'),
|
|
44
|
+
] } });
|
|
45
|
+
});
|
|
46
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
47
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.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.waitForSelector('[data-vendor-letter]', { timeout: 15000 });
|
|
52
|
+
await page.waitForTimeout(300);
|
|
53
|
+
|
|
54
|
+
const probe = await page.evaluate(() => {
|
|
55
|
+
const sel = (s) => document.querySelector(s);
|
|
56
|
+
const chips = [...document.querySelectorAll('[data-vendor-letter]')];
|
|
57
|
+
const samples = chips.map(c => ({
|
|
58
|
+
initial: c.getAttribute('data-vendor-letter'),
|
|
59
|
+
className: c.className,
|
|
60
|
+
fontWeight: getComputedStyle(c).fontWeight,
|
|
61
|
+
}));
|
|
62
|
+
return {
|
|
63
|
+
samples,
|
|
64
|
+
workingChipCls: sel('[data-working-chip]')?.className ?? '',
|
|
65
|
+
chromeZoomCls: sel('[data-topo-chrome-zoom-level]')?.className ?? '',
|
|
66
|
+
recentCountFw: sel('[data-recent-panel-count]')?.getAttribute('font-weight') ?? null,
|
|
67
|
+
rowWorkingFw: sel('[data-legend-count="working"]')?.getAttribute('font-weight') ?? null,
|
|
68
|
+
pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
await browser.close();
|
|
72
|
+
|
|
73
|
+
const results = {
|
|
74
|
+
vendor_chips_present: probe.samples.length >= 3,
|
|
75
|
+
all_have_font_medium_cls: probe.samples.every(s => s.className.includes('font-medium')),
|
|
76
|
+
all_have_tabular_nums_cls: probe.samples.every(s => s.className.includes('tabular-nums')),
|
|
77
|
+
all_computed_fw_500: probe.samples.every(s => String(s.fontWeight) === '500'),
|
|
78
|
+
r313_working_chip_fw_500: probe.workingChipCls.includes('font-medium'),
|
|
79
|
+
r312_chrome_zoom_fw_500: probe.chromeZoomCls.includes('font-medium'),
|
|
80
|
+
r311_recent_count_fw_600: probe.recentCountFw === '600',
|
|
81
|
+
r309_row_working_fw_600: probe.rowWorkingFw === '600',
|
|
82
|
+
r294_pulse_absent: probe.pulseCount === 0,
|
|
83
|
+
};
|
|
84
|
+
const ok = Object.values(results).every(Boolean);
|
|
85
|
+
console.log(`${ok ? '✅' : '❌'} vendor chip weight:`, JSON.stringify(results),
|
|
86
|
+
'\n vendor chips:', probe.samples.map(s => `${s.initial}=fw${s.fontWeight}`).join(', '));
|
|
87
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/* Round 333 verification: vendor letter chip count suffix `:{N}`
|
|
2
|
+
* picks up two polishes:
|
|
3
|
+
* 1. text-gray-500 → text-gray-400 (R317 subordinate-text-lift family)
|
|
4
|
+
* 2. tabular-nums (digit width-lock when count crosses 9→10 etc)
|
|
5
|
+
*
|
|
6
|
+
* Both surface a new test attr `data-vendor-letter-count` on the
|
|
7
|
+
* suffix span.
|
|
8
|
+
*
|
|
9
|
+
* Contract:
|
|
10
|
+
* - [data-vendor-letter-count-suffix] present (at least one vendor row).
|
|
11
|
+
* - Its className contains both 'text-gray-400' and 'tabular-nums'.
|
|
12
|
+
* - Computed fontVariantNumeric === 'tabular-nums'.
|
|
13
|
+
* - R332 minimap rounded-lg + R330 wrapper rounded-xl regressions.
|
|
14
|
+
* - R317 / R318 / R294 chrome + pulse regressions intact.
|
|
15
|
+
*/
|
|
16
|
+
import { chromium } from 'playwright';
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
|
|
19
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
20
|
+
const browser = await chromium.launch({ headless: true });
|
|
21
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
22
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
23
|
+
await ctx.addInitScript(() => {
|
|
24
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
25
|
+
});
|
|
26
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
27
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
28
|
+
const r = await route.fetch();
|
|
29
|
+
const b = await r.json();
|
|
30
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
31
|
+
const mk = (alias, model) => ({
|
|
32
|
+
alias, status: 'working', model, runtime: 'claude-code-cli',
|
|
33
|
+
network_id: nid, project_dir: null,
|
|
34
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
35
|
+
});
|
|
36
|
+
// R281 vendorDist threshold is `vendorDist.length > 2` — need 3+
|
|
37
|
+
// distinct vendor types to surface the vendor letter chip row.
|
|
38
|
+
// Anthropic + OpenAI + internlm = 3 distinct vendors (matches the
|
|
39
|
+
// pre-existing topo-vendor-chip-weight-test fixture pattern).
|
|
40
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
41
|
+
mk('alpha', 'claude-opus-4'),
|
|
42
|
+
mk('beta', 'gpt-4o'),
|
|
43
|
+
mk('gamma', 'internlm/internlm2'),
|
|
44
|
+
] } });
|
|
45
|
+
});
|
|
46
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
47
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
48
|
+
|
|
49
|
+
const page = await ctx.newPage();
|
|
50
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
51
|
+
// Wait for at least one data-vendor-letter (the parent chip) — the
|
|
52
|
+
// child count suffix lands as a child whether or not the chip is in
|
|
53
|
+
// the visible viewport. Use `state: attached` so the test passes when
|
|
54
|
+
// the vendor chip exists in the DOM (chip-row rendered) even if any
|
|
55
|
+
// individual responsive class hides one specific vendor.
|
|
56
|
+
await page.waitForSelector('[data-vendor-letter]', { timeout: 15000, state: 'attached' });
|
|
57
|
+
await page.waitForSelector('[data-vendor-letter-count-suffix]', { timeout: 5000, state: 'attached' });
|
|
58
|
+
await page.waitForTimeout(300);
|
|
59
|
+
|
|
60
|
+
const probe = await page.evaluate(() => {
|
|
61
|
+
const suffixes = Array.from(document.querySelectorAll('[data-vendor-letter-count-suffix]'));
|
|
62
|
+
return {
|
|
63
|
+
suffixCount: suffixes.length,
|
|
64
|
+
suffixClasses: suffixes.map(s => s.className),
|
|
65
|
+
suffixTabulars: suffixes.map(s => getComputedStyle(s).fontVariantNumeric),
|
|
66
|
+
suffixTexts: suffixes.map(s => s.textContent),
|
|
67
|
+
layoutInactiveCls: document.querySelector('[data-topo-chrome-layout="grid"]')?.className ?? '',
|
|
68
|
+
layoutActiveCls: document.querySelector('[data-topo-chrome-layout="ring"]')?.className ?? '',
|
|
69
|
+
pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
|
|
70
|
+
wrapperClass: document.querySelector('[data-topo-wrapper]')?.className ?? '',
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
await browser.close();
|
|
74
|
+
|
|
75
|
+
const hasTab = (s) => /tabular-nums/.test(s || '');
|
|
76
|
+
const hasGray400 = (cls) => /(^|\s)text-gray-400(\s|$)/.test(cls || '');
|
|
77
|
+
const hasTabClass = (cls) => /(^|\s)tabular-nums(\s|$)/.test(cls || '');
|
|
78
|
+
|
|
79
|
+
const results = {
|
|
80
|
+
suffix_at_least_one: probe.suffixCount >= 1,
|
|
81
|
+
all_have_gray_400: probe.suffixClasses.every(hasGray400),
|
|
82
|
+
all_have_tabular_class: probe.suffixClasses.every(hasTabClass),
|
|
83
|
+
all_have_computed_tabular: probe.suffixTabulars.every(hasTab),
|
|
84
|
+
// R330 wrapper regression.
|
|
85
|
+
r330_wrapper_rounded_xl: probe.wrapperClass.includes('rounded-xl'),
|
|
86
|
+
// R317 / R318 chrome regression.
|
|
87
|
+
r317_inactive_gray_400: probe.layoutInactiveCls.includes('text-gray-400'),
|
|
88
|
+
r318_active_font_medium: probe.layoutActiveCls.includes('font-medium'),
|
|
89
|
+
r294_pulse_absent: probe.pulseCount === 0,
|
|
90
|
+
};
|
|
91
|
+
const ok = Object.values(results).every(Boolean);
|
|
92
|
+
console.log(`${ok ? '✅' : '❌'} vendor count suffix gray-400+tabular:`, JSON.stringify(results),
|
|
93
|
+
'\n suffix texts:', probe.suffixTexts,
|
|
94
|
+
'\n suffix tabular:', probe.suffixTabulars);
|
|
95
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* Round 101 verification: vendor-letter chip <span>s carry an alias-
|
|
2
|
+
* list tooltip. Completes the R97/R98/R99 info-density sweep — every
|
|
3
|
+
* surface that shows "X:N" should hover-explain which N.
|
|
4
|
+
*
|
|
5
|
+
* Fleet: 2 Claude (alpha, gamma), 2 GPT (beta, delta). Vendor chip
|
|
6
|
+
* shows "A:2 O:2". A letter's tooltip should list alpha, gamma;
|
|
7
|
+
* O letter's should list beta, delta. Pin state flips the trailing
|
|
8
|
+
* action hint ("click to pin" → "click again or Esc to clear").
|
|
9
|
+
*/
|
|
10
|
+
import { chromium } from 'playwright';
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
|
|
13
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
14
|
+
const browser = await chromium.launch({ headless: true });
|
|
15
|
+
|
|
16
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
17
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
18
|
+
await ctx.addInitScript(() => {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
21
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
22
|
+
sessionStorage.removeItem('anet-topo-pinned-vendor');
|
|
23
|
+
} catch {}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
27
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
28
|
+
const r = await route.fetch();
|
|
29
|
+
const b = await r.json();
|
|
30
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
31
|
+
const mk = (alias, model) => ({
|
|
32
|
+
alias, status: 'idle', model, runtime: 'cli-claude-code',
|
|
33
|
+
network_id: nid, project_dir: null,
|
|
34
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
35
|
+
});
|
|
36
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
37
|
+
mk('alpha', 'claude-opus-4'),
|
|
38
|
+
mk('beta', 'gpt-5'),
|
|
39
|
+
mk('gamma', 'claude-opus-4'),
|
|
40
|
+
mk('delta', 'gpt-5'),
|
|
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('g[data-node]').length === 4, { timeout: 30000 });
|
|
48
|
+
await page.waitForTimeout(400);
|
|
49
|
+
|
|
50
|
+
const read = (initial) => page.evaluate((i) => {
|
|
51
|
+
const el = document.querySelector(`[data-vendor-letter="${i}"]`);
|
|
52
|
+
if (!el) return null;
|
|
53
|
+
return {
|
|
54
|
+
title: el.getAttribute('title') || '',
|
|
55
|
+
aliases: el.getAttribute('data-vendor-aliases') || '',
|
|
56
|
+
pinned: el.getAttribute('data-vendor-pinned') || '',
|
|
57
|
+
};
|
|
58
|
+
}, initial);
|
|
59
|
+
|
|
60
|
+
const aSnap = await read('A');
|
|
61
|
+
const oSnap = await read('O');
|
|
62
|
+
|
|
63
|
+
// Pin A — title should flip.
|
|
64
|
+
await page.locator('[data-vendor-letter="A"]').click();
|
|
65
|
+
await page.mouse.move(10, 10);
|
|
66
|
+
await page.waitForTimeout(250);
|
|
67
|
+
const aPinned = await read('A');
|
|
68
|
+
|
|
69
|
+
await browser.close();
|
|
70
|
+
|
|
71
|
+
const sameSet = (csv, expected) => {
|
|
72
|
+
const got = new Set(csv.split(',').filter(Boolean));
|
|
73
|
+
const exp = new Set(expected);
|
|
74
|
+
return got.size === exp.size && [...exp].every(x => got.has(x));
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const results = {
|
|
78
|
+
a_aliasesMatch: sameSet(aSnap?.aliases || '', ['alpha', 'gamma']),
|
|
79
|
+
a_titleListsThem: /alpha.*gamma|gamma.*alpha/.test(aSnap?.title || ''),
|
|
80
|
+
a_titleClickToPin: /click to pin/.test(aSnap?.title || ''),
|
|
81
|
+
o_aliasesMatch: sameSet(oSnap?.aliases || '', ['beta', 'delta']),
|
|
82
|
+
o_titleListsThem: /beta.*delta|delta.*beta/.test(oSnap?.title || ''),
|
|
83
|
+
pinnedA_aliasesUnchanged: sameSet(aPinned?.aliases || '', ['alpha', 'gamma']),
|
|
84
|
+
pinnedA_titleHintFlipped: /click again or Esc to clear/.test(aPinned?.title || ''),
|
|
85
|
+
pinnedA_pinnedAttrTrue: aPinned?.pinned === 'true',
|
|
86
|
+
};
|
|
87
|
+
const ok = Object.values(results).every(Boolean);
|
|
88
|
+
console.log(`${ok ? '✅' : '❌'} vendor-letter tooltip:`, JSON.stringify(results),
|
|
89
|
+
`\n A:`, aSnap,
|
|
90
|
+
`\n O:`, oSnap,
|
|
91
|
+
`\n A-pinned:`, aPinned);
|
|
92
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* Round 89 verification: pinning a vendor renders a "filter: X · N ×"
|
|
2
|
+
* pill in the chip-row, mirroring R64 status + group pills. R88 added
|
|
3
|
+
* the pin but the affordance lived only on the letter — body-click /
|
|
4
|
+
* × button on a dedicated pill was missing. R89 closes that.
|
|
5
|
+
*
|
|
6
|
+
* Also verifies the stale-purge useEffect: if the fleet's vendor
|
|
7
|
+
* distribution loses the pinned vendor (last node disconnects), the
|
|
8
|
+
* pin clears automatically so the pill doesn't show "filter: A · 0"
|
|
9
|
+
* forever. Same defensive pattern pinnedGroup uses.
|
|
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: 1200 } });
|
|
18
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
19
|
+
await ctx.addInitScript(() => {
|
|
20
|
+
try {
|
|
21
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
22
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
23
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
24
|
+
sessionStorage.removeItem('anet-topo-pinned-status');
|
|
25
|
+
sessionStorage.removeItem('anet-topo-pinned-group');
|
|
26
|
+
sessionStorage.removeItem('anet-topo-pinned-vendor');
|
|
27
|
+
} catch {}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
31
|
+
let dropGPT = false;
|
|
32
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
33
|
+
const r = await route.fetch();
|
|
34
|
+
const b = await r.json();
|
|
35
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
36
|
+
const mk = (alias, model) => ({
|
|
37
|
+
alias, status: 'idle', model, runtime: 'cli-claude-code',
|
|
38
|
+
network_id: nid, project_dir: null,
|
|
39
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
40
|
+
});
|
|
41
|
+
const sessions = [
|
|
42
|
+
mk('alpha', 'claude-opus-4'),
|
|
43
|
+
mk('gamma', 'claude-opus-4'),
|
|
44
|
+
];
|
|
45
|
+
if (!dropGPT) sessions.splice(1, 0, mk('beta', 'gpt-5'));
|
|
46
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
47
|
+
});
|
|
48
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
49
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
50
|
+
const page = await ctx.newPage();
|
|
51
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
52
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
|
|
53
|
+
await page.waitForTimeout(400);
|
|
54
|
+
|
|
55
|
+
const pill = () => page.evaluate(() => {
|
|
56
|
+
const p = document.querySelector('[data-active-filter="vendor"]');
|
|
57
|
+
if (!p) return null;
|
|
58
|
+
return {
|
|
59
|
+
text: (p.textContent || '').trim(),
|
|
60
|
+
count: p.getAttribute('data-filter-match-count'),
|
|
61
|
+
color: getComputedStyle(p).color,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
const storage = () => page.evaluate(() => sessionStorage.getItem('anet-topo-pinned-vendor'));
|
|
65
|
+
|
|
66
|
+
// Pin O (gpt-5 → OpenAI) via click.
|
|
67
|
+
const before = await pill();
|
|
68
|
+
await page.locator('[data-vendor-letter="O"]').click();
|
|
69
|
+
await page.mouse.move(10, 10);
|
|
70
|
+
await page.waitForTimeout(250);
|
|
71
|
+
const afterPinO = await pill();
|
|
72
|
+
const afterPinOStorage = await storage();
|
|
73
|
+
|
|
74
|
+
// Clicking the pill body clears the pin.
|
|
75
|
+
await page.locator('[data-active-filter="vendor"]').click();
|
|
76
|
+
await page.waitForTimeout(250);
|
|
77
|
+
const afterBodyClick = await pill();
|
|
78
|
+
|
|
79
|
+
// Pin A (Anthropic) — verify the × button works.
|
|
80
|
+
await page.locator('[data-vendor-letter="A"]').click();
|
|
81
|
+
await page.mouse.move(10, 10);
|
|
82
|
+
await page.waitForTimeout(250);
|
|
83
|
+
const afterPinA = await pill();
|
|
84
|
+
await page.locator('[data-active-filter="vendor"] button').click();
|
|
85
|
+
await page.waitForTimeout(250);
|
|
86
|
+
const afterXButton = await pill();
|
|
87
|
+
|
|
88
|
+
// Pin O again, then mock the GPT node away → stale-purge fires.
|
|
89
|
+
await page.locator('[data-vendor-letter="O"]').click();
|
|
90
|
+
await page.waitForTimeout(250);
|
|
91
|
+
const beforePurge = await pill();
|
|
92
|
+
dropGPT = true;
|
|
93
|
+
// Force a refetch via swr revalidation — easiest is page.reload, but
|
|
94
|
+
// that wipes sessionStorage. Instead, just await the next swr cycle
|
|
95
|
+
// (5s by default). To speed things up, dispatch a focus event which
|
|
96
|
+
// triggers swr revalidateOnFocus. We expect the pin to clear once
|
|
97
|
+
// the vendor leaves the distribution.
|
|
98
|
+
await page.evaluate(() => window.dispatchEvent(new Event('focus')));
|
|
99
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
|
|
100
|
+
await page.waitForTimeout(400);
|
|
101
|
+
const afterPurge = await pill();
|
|
102
|
+
const afterPurgeStorage = await storage();
|
|
103
|
+
|
|
104
|
+
await browser.close();
|
|
105
|
+
|
|
106
|
+
const results = {
|
|
107
|
+
before_noPill: before === null,
|
|
108
|
+
pinO_pillRendered: afterPinO !== null && /filter:?\s*O/.test(afterPinO.text),
|
|
109
|
+
pinO_countCorrect: afterPinO?.count === '1',
|
|
110
|
+
pinO_storage: afterPinOStorage === 'O',
|
|
111
|
+
bodyClick_clears: afterBodyClick === null,
|
|
112
|
+
pinA_pillRendered: afterPinA !== null && /filter:?\s*A/.test(afterPinA.text),
|
|
113
|
+
pinA_countCorrect: afterPinA?.count === '2',
|
|
114
|
+
xButton_clears: afterXButton === null,
|
|
115
|
+
beforePurge_present: beforePurge !== null && /filter:?\s*O/.test(beforePurge.text),
|
|
116
|
+
purge_pillGone: afterPurge === null,
|
|
117
|
+
purge_storageGone: afterPurgeStorage === null,
|
|
118
|
+
};
|
|
119
|
+
const ok = Object.values(results).every(Boolean);
|
|
120
|
+
console.log(`${ok ? '✅' : '❌'} vendor pill:`, JSON.stringify(results),
|
|
121
|
+
`\n afterPinO=`, afterPinO,
|
|
122
|
+
`\n afterPinA=`, afterPinA,
|
|
123
|
+
`\n beforePurge=`, beforePurge,
|
|
124
|
+
`\n afterPurge=`, afterPurge);
|
|
125
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* Round 88 verification: clicking a vendor letter in the distribution
|
|
2
|
+
* chip toggles a sticky pin — closes the only filter dimension that
|
|
3
|
+
* still released on mouseleave (R60 status, R63 group, R69 Cmd+K all
|
|
4
|
+
* support pin). Composes via activeVendor = hoveredVendor ??
|
|
5
|
+
* pinnedVendor so hover still wins for spot comparison.
|
|
6
|
+
*
|
|
7
|
+
* Fleet: 2 Claude (alpha, gamma) + 1 GPT (beta). Click A → alpha + gamma
|
|
8
|
+
* stay bright across mouseleave; pinned-style boxShadow on the A
|
|
9
|
+
* letter; aria-pressed="true". Hover O while A pinned → transient O
|
|
10
|
+
* preview (alpha/gamma dim, beta lit); mouseleave → A pin restores.
|
|
11
|
+
* Esc clears the pin (R62/R63 universal-cancel extends to vendor).
|
|
12
|
+
* SessionStorage round-trips.
|
|
13
|
+
*/
|
|
14
|
+
import { chromium } from 'playwright';
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
|
|
17
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
18
|
+
const browser = await chromium.launch({ headless: true });
|
|
19
|
+
|
|
20
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1200 } });
|
|
21
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
22
|
+
await ctx.addInitScript(() => {
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
25
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
26
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
27
|
+
sessionStorage.removeItem('anet-topo-pinned-status');
|
|
28
|
+
sessionStorage.removeItem('anet-topo-pinned-group');
|
|
29
|
+
sessionStorage.removeItem('anet-topo-pinned-vendor');
|
|
30
|
+
} catch {}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
34
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
35
|
+
const r = await route.fetch();
|
|
36
|
+
const b = await r.json();
|
|
37
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
38
|
+
const sessions = [
|
|
39
|
+
{ alias: 'alpha', status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
40
|
+
network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
41
|
+
{ alias: 'beta', status: 'idle', model: 'gpt-5', runtime: 'codex-cli',
|
|
42
|
+
network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
43
|
+
{ alias: 'gamma', status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
44
|
+
network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
45
|
+
];
|
|
46
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
47
|
+
});
|
|
48
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
49
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
50
|
+
const page = await ctx.newPage();
|
|
51
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
52
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
|
|
53
|
+
await page.waitForTimeout(400);
|
|
54
|
+
|
|
55
|
+
const ops = () => page.evaluate(() => {
|
|
56
|
+
const o = {};
|
|
57
|
+
for (const a of ['alpha', 'beta', 'gamma']) {
|
|
58
|
+
const g = document.querySelector(`g[data-node="${a}"]`);
|
|
59
|
+
o[a] = g ? +(g.style.opacity || '1') : null;
|
|
60
|
+
}
|
|
61
|
+
return o;
|
|
62
|
+
});
|
|
63
|
+
const meta = () => page.evaluate(() => {
|
|
64
|
+
const a = document.querySelector('[data-vendor-letter="A"]');
|
|
65
|
+
const o = document.querySelector('[data-vendor-letter="O"]');
|
|
66
|
+
return {
|
|
67
|
+
A: { pinned: a?.getAttribute('data-vendor-pinned'), aria: a?.getAttribute('aria-pressed'), shadow: (a?.getAttribute('style') || '').includes('inset') },
|
|
68
|
+
O: { pinned: o?.getAttribute('data-vendor-pinned'), aria: o?.getAttribute('aria-pressed'), shadow: (o?.getAttribute('style') || '').includes('inset') },
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
const storage = () => page.evaluate(() => sessionStorage.getItem('anet-topo-pinned-vendor'));
|
|
72
|
+
|
|
73
|
+
const before = await ops();
|
|
74
|
+
const beforeMeta = await meta();
|
|
75
|
+
|
|
76
|
+
// Click A to pin. Mouse moves away — pin survives.
|
|
77
|
+
await page.locator('[data-vendor-letter="A"]').click();
|
|
78
|
+
await page.mouse.move(10, 10);
|
|
79
|
+
await page.waitForTimeout(300);
|
|
80
|
+
const afterClickA = await ops();
|
|
81
|
+
const afterClickAMeta = await meta();
|
|
82
|
+
const afterClickAStorage = await storage();
|
|
83
|
+
|
|
84
|
+
// Hover O while A pinned — transient O preview wins.
|
|
85
|
+
await page.locator('[data-vendor-letter="O"]').hover();
|
|
86
|
+
await page.waitForTimeout(250);
|
|
87
|
+
const hoverOWhilePinA = await ops();
|
|
88
|
+
|
|
89
|
+
// Leave — A pin restores.
|
|
90
|
+
await page.mouse.move(10, 10);
|
|
91
|
+
await page.waitForTimeout(250);
|
|
92
|
+
const leaveOWhilePinA = await ops();
|
|
93
|
+
|
|
94
|
+
// Esc clears the vendor pin.
|
|
95
|
+
await page.keyboard.press('Escape');
|
|
96
|
+
await page.waitForTimeout(250);
|
|
97
|
+
const afterEsc = await ops();
|
|
98
|
+
const afterEscMeta = await meta();
|
|
99
|
+
const afterEscStorage = await storage();
|
|
100
|
+
|
|
101
|
+
await browser.close();
|
|
102
|
+
|
|
103
|
+
const bright = (v) => v != null && v >= 0.55;
|
|
104
|
+
const dim = (v) => v != null && v < 0.4;
|
|
105
|
+
const results = {
|
|
106
|
+
before_baseline: bright(before.alpha) && bright(before.beta) && bright(before.gamma),
|
|
107
|
+
before_noPinMeta: beforeMeta.A.pinned === 'false' && beforeMeta.A.aria === 'false' && !beforeMeta.A.shadow,
|
|
108
|
+
pinA_keepsClaudes: bright(afterClickA.alpha) && bright(afterClickA.gamma),
|
|
109
|
+
pinA_dimsGPT: dim(afterClickA.beta),
|
|
110
|
+
pinA_metaSet: afterClickAMeta.A.pinned === 'true' && afterClickAMeta.A.aria === 'true' && afterClickAMeta.A.shadow,
|
|
111
|
+
pinA_storagePersisted: afterClickAStorage === 'A',
|
|
112
|
+
hoverOPinA_transientO: bright(hoverOWhilePinA.beta) && dim(hoverOWhilePinA.alpha) && dim(hoverOWhilePinA.gamma),
|
|
113
|
+
leaveOPinA_aRestored: bright(leaveOWhilePinA.alpha) && bright(leaveOWhilePinA.gamma) && dim(leaveOWhilePinA.beta),
|
|
114
|
+
esc_releasesPin: bright(afterEsc.alpha) && bright(afterEsc.beta) && bright(afterEsc.gamma),
|
|
115
|
+
esc_metaCleared: afterEscMeta.A.pinned === 'false' && afterEscMeta.A.aria === 'false' && !afterEscMeta.A.shadow,
|
|
116
|
+
esc_storageCleared: afterEscStorage === null,
|
|
117
|
+
};
|
|
118
|
+
const ok = Object.values(results).every(Boolean);
|
|
119
|
+
console.log(`${ok ? '✅' : '❌'} vendor pin:`, JSON.stringify(results),
|
|
120
|
+
`\n before=`, before,
|
|
121
|
+
`\n afterClickA=`, afterClickA,
|
|
122
|
+
`\n hoverOPinA=`, hoverOWhilePinA,
|
|
123
|
+
`\n leaveOPinA=`, leaveOWhilePinA,
|
|
124
|
+
`\n afterEsc=`, afterEsc);
|
|
125
|
+
process.exit(ok ? 0 : 1);
|