@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,82 @@
|
|
|
1
|
+
/* Round 103 verification: flow-particle `<animateMotion begin>` is
|
|
2
|
+
* unique per edge, staggered by `-(index × 0.37) % duration s`. Pre-
|
|
3
|
+
* R103 all edges' particles fired at t=0 in lockstep; the stagger
|
|
4
|
+
* gives the canvas an organic "many independent rivers" feel.
|
|
5
|
+
*
|
|
6
|
+
* Verifies:
|
|
7
|
+
* - every edge has a `begin` attribute on its <animateMotion>
|
|
8
|
+
* - all begin values are distinct (no two edges share a phase)
|
|
9
|
+
* - the format is "-Ns" (negative offset for retroactive cycle start)
|
|
10
|
+
* - reducedMotion still suppresses the particle entirely
|
|
11
|
+
*/
|
|
12
|
+
import { chromium } from 'playwright';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
|
|
15
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
16
|
+
const browser = await chromium.launch({ headless: true });
|
|
17
|
+
const fresh = new Date(Date.now() - 30 * 1000).toISOString();
|
|
18
|
+
|
|
19
|
+
const probe = async (reducedMotion) => {
|
|
20
|
+
const ctx = await browser.newContext({
|
|
21
|
+
viewport: { width: 1400, height: 1500 },
|
|
22
|
+
reducedMotion: reducedMotion ? 'reduce' : 'no-preference',
|
|
23
|
+
});
|
|
24
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
25
|
+
await ctx.addInitScript(() => {
|
|
26
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
27
|
+
});
|
|
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) => ({
|
|
33
|
+
alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
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: [mk('alpha'), mk('beta'), mk('gamma'), mk('delta'), mk('epsilon')] } });
|
|
38
|
+
});
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
const mkMsg = (id, from_alias, to_alias, ageMs) => ({
|
|
41
|
+
id, from_alias, to_alias, content: 'hi', network_id: 'default',
|
|
42
|
+
created_at: new Date(now - ageMs).toISOString(),
|
|
43
|
+
});
|
|
44
|
+
// Five distinct edges → five animations.
|
|
45
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
|
|
46
|
+
mkMsg('m1', 'alpha', 'beta', 30 * 1000),
|
|
47
|
+
mkMsg('m2', 'gamma', 'delta', 35 * 1000),
|
|
48
|
+
mkMsg('m3', 'beta', 'gamma', 40 * 1000),
|
|
49
|
+
mkMsg('m4', 'delta', 'epsilon', 45 * 1000),
|
|
50
|
+
mkMsg('m5', 'epsilon', 'alpha', 50 * 1000),
|
|
51
|
+
] } }));
|
|
52
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
53
|
+
const page = await ctx.newPage();
|
|
54
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
55
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 5, { timeout: 30000 });
|
|
56
|
+
await page.waitForTimeout(400);
|
|
57
|
+
|
|
58
|
+
const beginValues = await page.evaluate(() => {
|
|
59
|
+
return [...document.querySelectorAll('animateMotion')]
|
|
60
|
+
.map(a => a.getAttribute('begin'));
|
|
61
|
+
});
|
|
62
|
+
await ctx.close();
|
|
63
|
+
return beginValues;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const normal = await probe(false);
|
|
67
|
+
const reduced = await probe(true);
|
|
68
|
+
await browser.close();
|
|
69
|
+
|
|
70
|
+
const results = {
|
|
71
|
+
normal_hasAtLeast5: normal.length >= 5,
|
|
72
|
+
normal_allHaveBegin: normal.every(b => typeof b === 'string' && b.length > 0),
|
|
73
|
+
normal_allNegativeOffset: normal.every(b => /^-\d/.test(b || '')),
|
|
74
|
+
normal_allDistinct: new Set(normal).size === normal.length,
|
|
75
|
+
normal_firstIsZero: normal[0] === '-0.000s',
|
|
76
|
+
reduced_noParticles: reduced.length === 0,
|
|
77
|
+
};
|
|
78
|
+
const ok = Object.values(results).every(Boolean);
|
|
79
|
+
console.log(`${ok ? '✅' : '❌'} particle stagger:`, JSON.stringify(results),
|
|
80
|
+
`\n normal=`, normal,
|
|
81
|
+
`\n reduced=`, reduced);
|
|
82
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/* Round 236 verification: pin-intersection chip gets a smooth
|
|
2
|
+
* colour-easing transition on the empty↔non-empty crossing.
|
|
3
|
+
*
|
|
4
|
+
* Pre-R236 the chip's bg/color/borderColor snap-flipped between
|
|
5
|
+
* slate (non-empty intersection) and amber (empty — the 'pinned
|
|
6
|
+
* filters cancel out' warning). R236 adds a 200ms ease-out
|
|
7
|
+
* transition so the colour shift eases through the boundary.
|
|
8
|
+
*
|
|
9
|
+
* Scenario: 4 sessions × 2 vendors × 2 statuses; pin two filter
|
|
10
|
+
* dims that yield an intersection > 0 first (non-empty: slate),
|
|
11
|
+
* then verify the chip's inline transition includes all three
|
|
12
|
+
* color-related properties.
|
|
13
|
+
*
|
|
14
|
+
* Probing the transition wiring is enough — the visual ease itself
|
|
15
|
+
* is a CSS browser detail. We just need the transition string to
|
|
16
|
+
* include background-color + color + border-color with 200ms.
|
|
17
|
+
*/
|
|
18
|
+
import { chromium } from 'playwright';
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
|
|
21
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
22
|
+
const browser = await chromium.launch({ headless: true });
|
|
23
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
24
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
25
|
+
await ctx.addInitScript(() => {
|
|
26
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
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, model, status) => ({
|
|
34
|
+
alias, status, model, runtime: 'claude-code-cli',
|
|
35
|
+
network_id: nid, project_dir: null,
|
|
36
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
37
|
+
});
|
|
38
|
+
// Scenario tuned so we can flip intersection 1 → 0 by swapping
|
|
39
|
+
// the pinned vendor: alpha is the ONLY working anthropic; gpt
|
|
40
|
+
// sessions are both idle. Pin working+A → alpha (non-empty).
|
|
41
|
+
// Then re-pin vendor to 'O' (vendor is single-value, so 'A' is
|
|
42
|
+
// released and 'O' takes its place) → working+O = 0 (empty).
|
|
43
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
44
|
+
mk('alpha', 'claude-opus-4', 'working'),
|
|
45
|
+
mk('beta', 'claude-opus-4', 'idle'),
|
|
46
|
+
mk('gamma', 'gpt-4', 'idle'),
|
|
47
|
+
mk('delta', 'gpt-4', 'idle'),
|
|
48
|
+
] } });
|
|
49
|
+
});
|
|
50
|
+
await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
|
|
51
|
+
await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
|
|
52
|
+
|
|
53
|
+
const page = await ctx.newPage();
|
|
54
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
55
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
56
|
+
|
|
57
|
+
// Pin working + anthropic (intersection = alpha, non-empty)
|
|
58
|
+
await page.locator('[data-pressure-seg="working"]').first().click();
|
|
59
|
+
await page.locator('[data-vendor-letter="A"]').first().click();
|
|
60
|
+
await page.waitForSelector('[data-pin-intersection]', { timeout: 5000, state: 'attached' });
|
|
61
|
+
await page.waitForTimeout(300);
|
|
62
|
+
|
|
63
|
+
const nonEmpty = await page.evaluate(() => {
|
|
64
|
+
const chip = document.querySelector('[data-pin-intersection]');
|
|
65
|
+
if (!chip) return null;
|
|
66
|
+
return {
|
|
67
|
+
empty: chip.getAttribute('data-pin-intersection-empty'),
|
|
68
|
+
transition: chip.style.transition,
|
|
69
|
+
bg: chip.style.background,
|
|
70
|
+
color: chip.style.color,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Re-pin vendor 'O' — pinnedVendor is single-value, so 'A' releases
|
|
75
|
+
// and 'O' takes its place. working ∩ openai = 0 (gpt sessions are
|
|
76
|
+
// all idle) → chip flips to empty=true.
|
|
77
|
+
await page.locator('[data-vendor-letter="O"]').first().click();
|
|
78
|
+
await page.waitForTimeout(300);
|
|
79
|
+
|
|
80
|
+
const empty = await page.evaluate(() => {
|
|
81
|
+
const chip = document.querySelector('[data-pin-intersection]');
|
|
82
|
+
if (!chip) return null;
|
|
83
|
+
return {
|
|
84
|
+
empty: chip.getAttribute('data-pin-intersection-empty'),
|
|
85
|
+
transition: chip.style.transition,
|
|
86
|
+
bg: chip.style.background,
|
|
87
|
+
color: chip.style.color,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
await browser.close();
|
|
91
|
+
|
|
92
|
+
const hasTransition = (s, prop) => new RegExp(`${prop.replace('-', '-')}\\s+200ms`).test(s || '');
|
|
93
|
+
|
|
94
|
+
const results = {
|
|
95
|
+
nonempty_mounted: nonEmpty !== null,
|
|
96
|
+
nonempty_empty_attr: nonEmpty?.empty === 'false',
|
|
97
|
+
nonempty_has_bg_transition: hasTransition(nonEmpty?.transition, 'background-color'),
|
|
98
|
+
nonempty_has_color_transition: hasTransition(nonEmpty?.transition, 'color'),
|
|
99
|
+
nonempty_has_border_transition: hasTransition(nonEmpty?.transition, 'border-color'),
|
|
100
|
+
empty_mounted: empty !== null,
|
|
101
|
+
empty_empty_attr: empty?.empty === 'true',
|
|
102
|
+
empty_has_bg_transition: hasTransition(empty?.transition, 'background-color'),
|
|
103
|
+
empty_has_color_transition: hasTransition(empty?.transition, 'color'),
|
|
104
|
+
empty_has_border_transition: hasTransition(empty?.transition, 'border-color'),
|
|
105
|
+
color_changed_on_flip: nonEmpty?.color !== empty?.color,
|
|
106
|
+
};
|
|
107
|
+
const ok = Object.values(results).every(Boolean);
|
|
108
|
+
console.log(`${ok ? '✅' : '❌'} pin-intersection color ease:`, JSON.stringify(results),
|
|
109
|
+
'\n nonEmpty:', nonEmpty,
|
|
110
|
+
'\n empty: ', empty);
|
|
111
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* Round 125 verification: pin-intersection chip warning tint when
|
|
2
|
+
* the intersection is zero.
|
|
3
|
+
*
|
|
4
|
+
* R124 added the "match: N pins · K" chip after the last filter
|
|
5
|
+
* pill, gated to ≥ 2 active pin dims. It stayed neutral gray
|
|
6
|
+
* regardless of whether K was 12 or 0 — but K=0 means "your pins
|
|
7
|
+
* cancel out" and the canvas dims to 0.28 everywhere with no
|
|
8
|
+
* positive signal. The neutral "· 0" tail reads as just another
|
|
9
|
+
* number, indistinguishable from "· 12".
|
|
10
|
+
*
|
|
11
|
+
* R125 flips the chip to amber + adds a ⚠ glyph when K=0. Tooltip
|
|
12
|
+
* grows the "release one to widen" hint. Color choice: amber
|
|
13
|
+
* (#d97706 light / #fbbf24 dark) — distinct from every pill color
|
|
14
|
+
* AND from the gray non-empty intersection state.
|
|
15
|
+
*
|
|
16
|
+
* Fleet:
|
|
17
|
+
* alpha (working, vendor=A)
|
|
18
|
+
* beta (working, vendor=O)
|
|
19
|
+
* gamma (idle, vendor=A)
|
|
20
|
+
* delta (idle, vendor=O)
|
|
21
|
+
*
|
|
22
|
+
* Path:
|
|
23
|
+
* 1. Pin status=working → chip hidden (1 pin)
|
|
24
|
+
* 2. + Pin vendor=A → intersection {alpha} = 1 → chip gray, no ⚠
|
|
25
|
+
* 3. Esc, then pin status=offline + vendor=A → intersection 0
|
|
26
|
+
* (alpha+gamma have vendor=A but neither is offline)
|
|
27
|
+
* → chip amber + ⚠ glyph + tooltip mentions "release one to widen"
|
|
28
|
+
*/
|
|
29
|
+
import { chromium } from 'playwright';
|
|
30
|
+
import { readFileSync } from 'node:fs';
|
|
31
|
+
|
|
32
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
33
|
+
const browser = await chromium.launch({ headless: true });
|
|
34
|
+
|
|
35
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1100 } });
|
|
36
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
37
|
+
await ctx.addInitScript(() => {
|
|
38
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
42
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
43
|
+
const r = await route.fetch();
|
|
44
|
+
const b = await r.json();
|
|
45
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
46
|
+
const mk = (alias, status, model) => ({
|
|
47
|
+
alias, status, model, runtime: 'cli-claude-code',
|
|
48
|
+
network_id: nid, project_dir: null,
|
|
49
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
50
|
+
});
|
|
51
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
52
|
+
mk('alpha', 'working', 'claude-opus-4'),
|
|
53
|
+
mk('beta', 'working', 'gpt-4o'),
|
|
54
|
+
mk('gamma', 'idle', 'claude-sonnet-4'),
|
|
55
|
+
mk('delta', 'idle', 'gpt-4o-mini'),
|
|
56
|
+
] } });
|
|
57
|
+
});
|
|
58
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
59
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.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 === 4, { timeout: 30000 });
|
|
64
|
+
await page.waitForTimeout(400);
|
|
65
|
+
|
|
66
|
+
const readChip = () => page.evaluate(() => {
|
|
67
|
+
const el = document.querySelector('[data-pin-intersection]');
|
|
68
|
+
if (!el) return null;
|
|
69
|
+
// background color comes from inline style; parse for amber substring.
|
|
70
|
+
const inline = el.getAttribute('style') || '';
|
|
71
|
+
return {
|
|
72
|
+
dimCount: parseInt(el.getAttribute('data-pin-dim-count') || '0', 10),
|
|
73
|
+
matchCount: parseInt(el.getAttribute('data-pin-intersection-count') || '0', 10),
|
|
74
|
+
empty: el.getAttribute('data-pin-intersection-empty'),
|
|
75
|
+
text: el.textContent?.trim(),
|
|
76
|
+
title: el.getAttribute('title'),
|
|
77
|
+
// Browser normalises inline hex → rgb() when reading via
|
|
78
|
+
// getAttribute('style'). Cyber theme: #fbbf24 → rgb(251, 191, 36);
|
|
79
|
+
// gray #94a3b8 → rgb(148, 163, 184). Light theme would emit
|
|
80
|
+
// rgb(217, 119, 6) and rgb(71, 85, 105) — accept both.
|
|
81
|
+
// Inline background uses rgba(...) because of the alpha suffix
|
|
82
|
+
// we pass; foreground color is rgb(...). Match both with rgba?.
|
|
83
|
+
styleHasAmber: /rgba?\(251,\s*191,\s*36/i.test(inline) || /rgba?\(217,\s*119,\s*6/i.test(inline),
|
|
84
|
+
styleHasGray: /rgba?\(148,\s*163,\s*184/i.test(inline) || /rgba?\(71,\s*85,\s*105/i.test(inline),
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// All four sessions are online in this fleet, so the offline
|
|
89
|
+
// pressure-seg doesn't render. Use the R69 CustomEvent contract
|
|
90
|
+
// to drive pins — same handler at line 920+ of TopoGraph.tsx.
|
|
91
|
+
const pin = (detail) => page.evaluate(d => {
|
|
92
|
+
window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: d }));
|
|
93
|
+
}, detail);
|
|
94
|
+
|
|
95
|
+
// State 1 — only status pinned
|
|
96
|
+
await pin({ kind: 'status', value: 'working' });
|
|
97
|
+
await page.waitForTimeout(200);
|
|
98
|
+
const s1 = await readChip();
|
|
99
|
+
|
|
100
|
+
// State 2 — + vendor=A (working ∩ A = {alpha}) → 1 match, gray
|
|
101
|
+
await pin({ kind: 'vendor', value: 'A' });
|
|
102
|
+
await page.waitForTimeout(200);
|
|
103
|
+
const s2 = await readChip();
|
|
104
|
+
|
|
105
|
+
// Esc, then pin status=offline + vendor=A → 0 matches (alpha/gamma are
|
|
106
|
+
// A but online; beta/delta are O). Empty intersection → amber + ⚠.
|
|
107
|
+
await pin({ kind: 'clear' });
|
|
108
|
+
await page.waitForTimeout(200);
|
|
109
|
+
await pin({ kind: 'status', value: 'offline' });
|
|
110
|
+
await pin({ kind: 'vendor', value: 'A' });
|
|
111
|
+
await page.waitForTimeout(200);
|
|
112
|
+
const s3 = await readChip();
|
|
113
|
+
|
|
114
|
+
await browser.close();
|
|
115
|
+
|
|
116
|
+
const results = {
|
|
117
|
+
s1_onePin_chipHidden: s1 === null,
|
|
118
|
+
s2_twoPins_chipShown: !!s2 && s2.dimCount === 2 && s2.matchCount === 1,
|
|
119
|
+
s2_nonEmpty_grayBg: !!s2 && s2.empty === 'false' && s2.styleHasGray && !s2.styleHasAmber,
|
|
120
|
+
s2_noWarnGlyph: !!s2 && !s2.text?.includes('⚠'),
|
|
121
|
+
s3_twoPins_zeroMatch: !!s3 && s3.dimCount === 2 && s3.matchCount === 0,
|
|
122
|
+
s3_empty_amberBg: !!s3 && s3.empty === 'true' && s3.styleHasAmber && !s3.styleHasGray,
|
|
123
|
+
s3_text_hasWarnGlyph: !!s3 && (s3.text || '').includes('⚠'),
|
|
124
|
+
s3_tooltip_widenHint: !!s3 && (s3.title || '').includes('release one to widen'),
|
|
125
|
+
};
|
|
126
|
+
const ok = Object.values(results).every(Boolean);
|
|
127
|
+
console.log(`${ok ? '✅' : '❌'} pin-intersection empty:`, JSON.stringify(results),
|
|
128
|
+
`\n s1=`, s1, `\n s2=`, s2, `\n s3=`, s3);
|
|
129
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* Round 235 verification: pin-intersection chip (the
|
|
2
|
+
* '{pinDimCount} pins · {matchAliases.length}' chip that surfaces
|
|
3
|
+
* when multiple filter dimensions are pinned simultaneously) picks
|
|
4
|
+
* up the Tailwind `tabular-nums` utility so its two visible
|
|
5
|
+
* digits don't reflow the chip on counter changes.
|
|
6
|
+
*
|
|
7
|
+
* 9th surface in the info-density tabular-nums sweep — the third
|
|
8
|
+
* HTML chip surface, completing chip-row + vendor-row + pin-
|
|
9
|
+
* intersection chip coverage.
|
|
10
|
+
*
|
|
11
|
+
* Scenario: needs >= 2 pinned filter dimensions to trigger the
|
|
12
|
+
* chip render. Simplest path: 4 sessions split across two
|
|
13
|
+
* vendors (anthropic + openai) and two statuses (working +
|
|
14
|
+
* idle). Then in the page we set pinnedStatus + pinnedVendor
|
|
15
|
+
* via localStorage so two filter dims pin simultaneously
|
|
16
|
+
* without needing to click the chips.
|
|
17
|
+
*
|
|
18
|
+
* The TopoGraph reads pinnedStatus + pinnedVendor from session-
|
|
19
|
+
* Storage keys (R88 / R60 persistence — let me trust that's
|
|
20
|
+
* how the state hydrates from storage at mount). If not, fall
|
|
21
|
+
* back to programmatic click on the working chip + vendor letter.
|
|
22
|
+
*/
|
|
23
|
+
import { chromium } from 'playwright';
|
|
24
|
+
import { readFileSync } from 'node:fs';
|
|
25
|
+
|
|
26
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
27
|
+
const browser = await chromium.launch({ headless: true });
|
|
28
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
29
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
30
|
+
await ctx.addInitScript(() => {
|
|
31
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
32
|
+
});
|
|
33
|
+
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 mk = (alias, model, status) => ({
|
|
39
|
+
alias, status, model, runtime: 'claude-code-cli',
|
|
40
|
+
network_id: nid, project_dir: null,
|
|
41
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
42
|
+
});
|
|
43
|
+
// 2 vendors × 2 statuses so multiple filter dims have something
|
|
44
|
+
// to bite on (chip strip renders, pressure-bar segments populate).
|
|
45
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
46
|
+
mk('alpha', 'claude-opus-4', 'working'),
|
|
47
|
+
mk('beta', 'claude-opus-4', 'idle'),
|
|
48
|
+
mk('gamma', 'gpt-4', 'working'),
|
|
49
|
+
mk('delta', 'gpt-4', 'idle'),
|
|
50
|
+
] } });
|
|
51
|
+
});
|
|
52
|
+
await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
|
|
53
|
+
await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
|
|
54
|
+
|
|
55
|
+
const page = await ctx.newPage();
|
|
56
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
57
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
58
|
+
await page.waitForSelector('[data-vendor-letter]', { timeout: 10000, state: 'attached' });
|
|
59
|
+
|
|
60
|
+
// Pin two filter dimensions: click the working pressure-bar segment
|
|
61
|
+
// + click the anthropic vendor letter. Both should fire setPinned*
|
|
62
|
+
// state which surfaces the pin-intersection chip.
|
|
63
|
+
await page.locator('[data-pressure-seg="working"]').first().click();
|
|
64
|
+
await page.locator('[data-vendor-letter="A"]').first().click();
|
|
65
|
+
await page.waitForSelector('[data-pin-intersection]', { timeout: 5000, state: 'attached' });
|
|
66
|
+
await page.waitForTimeout(300);
|
|
67
|
+
|
|
68
|
+
const out = await page.evaluate(() => {
|
|
69
|
+
const chip = document.querySelector('[data-pin-intersection]');
|
|
70
|
+
if (!chip) return null;
|
|
71
|
+
return {
|
|
72
|
+
pinDim: chip.getAttribute('data-pin-dim-count'),
|
|
73
|
+
matchCount: chip.getAttribute('data-pin-intersection-count'),
|
|
74
|
+
empty: chip.getAttribute('data-pin-intersection-empty'),
|
|
75
|
+
classListHas: chip.className.split(/\s+/).includes('tabular-nums'),
|
|
76
|
+
fontVarNumeric: getComputedStyle(chip).fontVariantNumeric,
|
|
77
|
+
text: (chip.textContent || '').trim(),
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
await browser.close();
|
|
81
|
+
|
|
82
|
+
const hasTab = (s) => /tabular-nums/.test(s || '');
|
|
83
|
+
|
|
84
|
+
const results = {
|
|
85
|
+
chip_mounted: out !== null,
|
|
86
|
+
pin_dim_2: out?.pinDim === '2',
|
|
87
|
+
match_present: typeof out?.matchCount === 'string' && /^\d+$/.test(out.matchCount),
|
|
88
|
+
empty_attr_present: out?.empty === 'true' || out?.empty === 'false',
|
|
89
|
+
class_utility_present: out?.classListHas === true,
|
|
90
|
+
fvn_tabular_nums: hasTab(out?.fontVarNumeric),
|
|
91
|
+
text_has_pins_dot: /\d+\s*pins?\s*·\s*\d+/.test(out?.text || ''),
|
|
92
|
+
};
|
|
93
|
+
const ok = Object.values(results).every(Boolean);
|
|
94
|
+
console.log(`${ok ? '✅' : '❌'} pin-intersection tabular:`, JSON.stringify(results),
|
|
95
|
+
'\n chip:', out);
|
|
96
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/* Round 124 verification: pin-intersection summary chip.
|
|
2
|
+
*
|
|
3
|
+
* The four filter pills (R64 status / R63 group / R89 vendor /
|
|
4
|
+
* R119 edge) each show their own dim's match count in isolation.
|
|
5
|
+
* But the node-opacity chain (lines 2723-2741 of TopoGraph.tsx)
|
|
6
|
+
* AND-composes all four pin dimensions — every miss dims the node.
|
|
7
|
+
* So two active pins produce an intersection ≤ either individual
|
|
8
|
+
* count, and the existing pills don't surface that.
|
|
9
|
+
*
|
|
10
|
+
* R124 adds a small "match: N pins · K" chip after the last pill
|
|
11
|
+
* when ≥ 2 pin dims are active. K = nodes that satisfy ALL active
|
|
12
|
+
* pins. Single pin keeps its old pill story (chip hidden).
|
|
13
|
+
*
|
|
14
|
+
* Fleet:
|
|
15
|
+
* alpha (working, vendor=A, group=agents)
|
|
16
|
+
* beta (working, vendor=O, group=agents)
|
|
17
|
+
* gamma (idle, vendor=A, group=agents)
|
|
18
|
+
* delta (working, vendor=A, group=infra)
|
|
19
|
+
*
|
|
20
|
+
* Test path:
|
|
21
|
+
* 1. No pin → chip hidden
|
|
22
|
+
* 2. Pin status=working → chip hidden (1 pin)
|
|
23
|
+
* 3. + Pin vendor=A → chip "2 pins · 2"
|
|
24
|
+
* (working AND vendor=A: alpha, delta)
|
|
25
|
+
* 4. + Pin group=agents → chip "3 pins · 1"
|
|
26
|
+
* (working AND A AND agents: alpha only)
|
|
27
|
+
* 5. Esc → chip hidden
|
|
28
|
+
*/
|
|
29
|
+
import { chromium } from 'playwright';
|
|
30
|
+
import { readFileSync } from 'node:fs';
|
|
31
|
+
|
|
32
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
33
|
+
const browser = await chromium.launch({ headless: true });
|
|
34
|
+
|
|
35
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1100 } });
|
|
36
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
37
|
+
await ctx.addInitScript(() => {
|
|
38
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
42
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
43
|
+
const r = await route.fetch();
|
|
44
|
+
const b = await r.json();
|
|
45
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
46
|
+
const mk = (alias, status, model) => ({
|
|
47
|
+
alias, status, model, runtime: 'cli-claude-code',
|
|
48
|
+
network_id: nid, project_dir: alias.startsWith('infra-') ? '/srv/infra' : '/srv/agents',
|
|
49
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
50
|
+
});
|
|
51
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
52
|
+
mk('agents-alpha', 'working', 'claude-opus-4'),
|
|
53
|
+
mk('agents-beta', 'working', 'gpt-4o'),
|
|
54
|
+
mk('agents-gamma', 'idle', 'claude-sonnet-4'),
|
|
55
|
+
mk('infra-delta', 'working', 'claude-haiku-4'),
|
|
56
|
+
] } });
|
|
57
|
+
});
|
|
58
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
59
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.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 === 4, { timeout: 30000 });
|
|
64
|
+
await page.waitForTimeout(400);
|
|
65
|
+
|
|
66
|
+
const readChip = () => page.evaluate(() => {
|
|
67
|
+
const el = document.querySelector('[data-pin-intersection]');
|
|
68
|
+
if (!el) return null;
|
|
69
|
+
return {
|
|
70
|
+
dimCount: parseInt(el.getAttribute('data-pin-dim-count') || '0', 10),
|
|
71
|
+
matchCount: parseInt(el.getAttribute('data-pin-intersection-count') || '0', 10),
|
|
72
|
+
aliases: (el.getAttribute('data-pin-intersection-aliases') || '').split(',').filter(Boolean),
|
|
73
|
+
text: el.textContent?.trim(),
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// State 1 — no pins
|
|
78
|
+
const s1 = await readChip();
|
|
79
|
+
|
|
80
|
+
// State 2 — pin status=working via pressure-bar segment
|
|
81
|
+
await page.locator('[data-pressure-seg="working"]').click();
|
|
82
|
+
await page.waitForTimeout(200);
|
|
83
|
+
const s2 = await readChip();
|
|
84
|
+
|
|
85
|
+
// State 3 — + pin vendor=A
|
|
86
|
+
await page.locator('[data-vendor-letter="A"]').click();
|
|
87
|
+
await page.waitForTimeout(200);
|
|
88
|
+
const s3 = await readChip();
|
|
89
|
+
|
|
90
|
+
// State 4 — + pin group=agents via the R69 CustomEvent contract.
|
|
91
|
+
// Direct dispatch is mode-independent (group-label hit-rects only
|
|
92
|
+
// render in grid layout; default is ring). The handler at line 945
|
|
93
|
+
// of TopoGraph.tsx accepts { kind: 'group', value: <key> }.
|
|
94
|
+
await page.evaluate(() => {
|
|
95
|
+
window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'group', value: 'agents' } }));
|
|
96
|
+
});
|
|
97
|
+
await page.waitForTimeout(200);
|
|
98
|
+
const s4 = await readChip();
|
|
99
|
+
|
|
100
|
+
// State 5 — Esc clears all pins
|
|
101
|
+
await page.keyboard.press('Escape');
|
|
102
|
+
await page.waitForTimeout(200);
|
|
103
|
+
const s5 = await readChip();
|
|
104
|
+
|
|
105
|
+
await browser.close();
|
|
106
|
+
|
|
107
|
+
const results = {
|
|
108
|
+
s1_noPins_chipHidden: s1 === null,
|
|
109
|
+
s2_onePin_chipHidden: s2 === null,
|
|
110
|
+
s3_twoPins_chipShown: !!s3 && s3.dimCount === 2,
|
|
111
|
+
s3_intersection_2: !!s3 && s3.matchCount === 2,
|
|
112
|
+
s3_aliases_alphaDelta: !!s3 && s3.aliases.includes('agents-alpha') && s3.aliases.includes('infra-delta'),
|
|
113
|
+
s4_threePins_chipShown: !!s4 && s4.dimCount === 3,
|
|
114
|
+
s4_intersection_1: !!s4 && s4.matchCount === 1,
|
|
115
|
+
s4_aliases_alphaOnly: !!s4 && s4.aliases.length === 1 && s4.aliases[0] === 'agents-alpha',
|
|
116
|
+
s5_escClears_chipHidden: s5 === null,
|
|
117
|
+
};
|
|
118
|
+
const ok = Object.values(results).every(Boolean);
|
|
119
|
+
console.log(`${ok ? '✅' : '❌'} pin-intersection chip:`, JSON.stringify(results),
|
|
120
|
+
`\n s1=`, s1, `\n s2=`, s2, `\n s3=`, s3, `\n s4=`, s4, `\n s5=`, s5);
|
|
121
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/* Round 237 verification: pin-intersection chip's ⚠ warning glyph
|
|
2
|
+
* always-mounts with opacity gated on isEmpty, instead of
|
|
3
|
+
* conditional render on isEmpty. Closes the snap-mount + layout-
|
|
4
|
+
* shift jank that survived R236's color easing.
|
|
5
|
+
*
|
|
6
|
+
* Test scope:
|
|
7
|
+
* - non-empty state: glyph in DOM, data-pin-intersection-warning
|
|
8
|
+
* ='false', opacity=0, transition includes 'opacity 200ms'
|
|
9
|
+
* - empty state: glyph in DOM, data-pin-intersection-warning
|
|
10
|
+
* ='true', opacity=1, transition still present
|
|
11
|
+
* - chip width: stable across the crossing (no layout-shift)
|
|
12
|
+
*
|
|
13
|
+
* Reuses the R236 scenario tuning — alpha is the only working
|
|
14
|
+
* anthropic, gpt sessions all idle; pin working+A (non-empty)
|
|
15
|
+
* then re-pin vendor to O (empty).
|
|
16
|
+
*/
|
|
17
|
+
import { chromium } from 'playwright';
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
19
|
+
|
|
20
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
21
|
+
const browser = await chromium.launch({ headless: true });
|
|
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, status) => ({
|
|
33
|
+
alias, status, 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: [
|
|
38
|
+
mk('alpha', 'claude-opus-4', 'working'),
|
|
39
|
+
mk('beta', 'claude-opus-4', 'idle'),
|
|
40
|
+
mk('gamma', 'gpt-4', 'idle'),
|
|
41
|
+
mk('delta', 'gpt-4', 'idle'),
|
|
42
|
+
] } });
|
|
43
|
+
});
|
|
44
|
+
await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
|
|
45
|
+
await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
|
|
46
|
+
|
|
47
|
+
const page = await ctx.newPage();
|
|
48
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
49
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
50
|
+
|
|
51
|
+
await page.locator('[data-pressure-seg="working"]').first().click();
|
|
52
|
+
await page.locator('[data-vendor-letter="A"]').first().click();
|
|
53
|
+
await page.waitForSelector('[data-pin-intersection-warning]', { timeout: 5000, state: 'attached' });
|
|
54
|
+
await page.waitForTimeout(300);
|
|
55
|
+
|
|
56
|
+
const probe = (page) => page.evaluate(() => {
|
|
57
|
+
const glyph = document.querySelector('[data-pin-intersection-warning]');
|
|
58
|
+
const chip = document.querySelector('[data-pin-intersection]');
|
|
59
|
+
if (!glyph || !chip) return null;
|
|
60
|
+
return {
|
|
61
|
+
present: true,
|
|
62
|
+
warning: glyph.getAttribute('data-pin-intersection-warning'),
|
|
63
|
+
opacity: parseFloat(glyph.style.opacity || '1'),
|
|
64
|
+
transition: glyph.style.transition,
|
|
65
|
+
text: glyph.textContent,
|
|
66
|
+
chipWidthPx: Math.round(chip.getBoundingClientRect().width * 100) / 100,
|
|
67
|
+
chipEmpty: chip.getAttribute('data-pin-intersection-empty'),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const nonEmpty = await probe(page);
|
|
72
|
+
|
|
73
|
+
// Flip vendor pin to 'O' — working ∩ openai = 0 (gpt all idle) → empty
|
|
74
|
+
await page.locator('[data-vendor-letter="O"]').first().click();
|
|
75
|
+
await page.waitForTimeout(300);
|
|
76
|
+
const empty = await probe(page);
|
|
77
|
+
|
|
78
|
+
await browser.close();
|
|
79
|
+
|
|
80
|
+
const widthDelta = nonEmpty && empty
|
|
81
|
+
? Math.abs(nonEmpty.chipWidthPx - empty.chipWidthPx)
|
|
82
|
+
: Infinity;
|
|
83
|
+
|
|
84
|
+
const results = {
|
|
85
|
+
nonempty_in_dom: nonEmpty?.present === true,
|
|
86
|
+
nonempty_warning_false: nonEmpty?.warning === 'false',
|
|
87
|
+
nonempty_opacity_0: nonEmpty?.opacity === 0,
|
|
88
|
+
nonempty_has_transition: /opacity\s+200ms/.test(nonEmpty?.transition || ''),
|
|
89
|
+
nonempty_text_is_warning: nonEmpty?.text === '⚠',
|
|
90
|
+
nonempty_chip_empty_false: nonEmpty?.chipEmpty === 'false',
|
|
91
|
+
|
|
92
|
+
empty_in_dom: empty?.present === true,
|
|
93
|
+
empty_warning_true: empty?.warning === 'true',
|
|
94
|
+
empty_opacity_1: empty?.opacity === 1,
|
|
95
|
+
empty_has_transition: /opacity\s+200ms/.test(empty?.transition || ''),
|
|
96
|
+
empty_chip_empty_true: empty?.chipEmpty === 'true',
|
|
97
|
+
|
|
98
|
+
// Chip width stable across the crossing (no layout-shift jank).
|
|
99
|
+
// 1px tolerance for fractional rounding only.
|
|
100
|
+
chip_width_stable: widthDelta <= 1,
|
|
101
|
+
};
|
|
102
|
+
const ok = Object.values(results).every(Boolean);
|
|
103
|
+
console.log(`${ok ? '✅' : '❌'} pin-intersection warning crossfade:`, JSON.stringify(results),
|
|
104
|
+
'\n nonEmpty:', nonEmpty,
|
|
105
|
+
'\n empty: ', empty,
|
|
106
|
+
'\n widthDelta:', widthDelta);
|
|
107
|
+
process.exit(ok ? 0 : 1);
|