@sleep2agi/agent-network-dashboard 0.4.6-preview.99 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +2 -0
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +31 -31
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +16 -0
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +12 -12
- package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +2 -2
- package/.next/server/app/admin.rsc +14 -14
- package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
- package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
- package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/api/hub/server/[hostname]/agents/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/hub/server/[hostname]/agents/route/build-manifest.json +9 -0
- package/.next/server/app/api/hub/server/[hostname]/agents/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/hub/server/[hostname]/agents/route.js +7 -0
- package/.next/server/app/api/hub/server/[hostname]/agents/route.js.map +5 -0
- package/.next/server/app/api/hub/server/[hostname]/agents/route.js.nft.json +1 -0
- package/.next/server/app/api/hub/server/[hostname]/agents/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route/build-manifest.json +9 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route.js +7 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route.js.map +5 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route.js.nft.json +1 -0
- package/.next/server/app/api/hub/server/[hostname]/health/route_client-reference-manifest.js +3 -0
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +14 -14
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +7 -7
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +14 -14
- package/.next/server/app/login.segments/_full.segment.rsc +14 -14
- package/.next/server/app/login.segments/_head.segment.rsc +4 -4
- package/.next/server/app/login.segments/_index.segment.rsc +7 -7
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/login.segments/login.segment.rsc +3 -3
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +2 -2
- package/.next/server/app/logs.rsc +14 -14
- package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +2 -2
- package/.next/server/app/messages.rsc +14 -14
- package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
- package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
- package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +2 -2
- package/.next/server/app/node.rsc +14 -14
- package/.next/server/app/node.segments/_full.segment.rsc +14 -14
- package/.next/server/app/node.segments/_head.segment.rsc +4 -4
- package/.next/server/app/node.segments/_index.segment.rsc +7 -7
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/node.segments/node.segment.rsc +3 -3
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +2 -2
- package/.next/server/app/nodes.rsc +14 -14
- package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
- package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +2 -2
- package/.next/server/app/server-logs.rsc +14 -14
- package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +2 -2
- package/.next/server/app/settings/networks.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +2 -2
- package/.next/server/app/settings/tokens.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +14 -14
- package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +2 -2
- package/.next/server/app/tasks.rsc +14 -14
- package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/.next/server/app-paths-manifest.json +2 -0
- package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js +3 -0
- package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js.map +1 -0
- package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js +3 -0
- package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__05nyivw._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__05nyivw._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0floe3h._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0floe3h._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -2
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/0aauz~36q5n2a.css +2 -0
- package/.next/static/chunks/0f~0kgy2pycyc.js +1 -0
- package/.next/static/chunks/0l_~q07bhpkcx.js +7 -0
- package/.next/static/chunks/1175-dp18a1e_.js +1 -0
- package/.next/static/chunks/1552j0415oeal.js +4 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +3 -1
- package/.next/types/validator.ts +18 -0
- package/app/api/hub/server/[hostname]/agents/route.ts +62 -0
- package/app/api/hub/server/[hostname]/health/route.ts +58 -0
- package/app/api/hub/servers/route.ts +88 -1
- package/app/components/CommandPalette.tsx +41 -1
- package/app/components/HelpOverlay.tsx +55 -0
- package/app/components/ServersDrawer.tsx +181 -5
- package/app/components/TopoGraph.tsx +5514 -336
- package/app/globals.css +93 -6
- package/app/lib/vendorIdentity.ts +14 -3
- package/docs/rfc/v0.10.0-hero3-dashboard-upgrade.md +230 -0
- package/package.json +2 -2
- package/public/sleep2agi-logo.svg +11 -0
- package/public/vendors/claude.svg +11 -0
- package/public/vendors/minimax.svg +11 -0
- package/public/vendors/openai.svg +12 -0
- package/screenshots/r293-p0/after-full.png +0 -0
- package/screenshots/r293-p0/after-topo.png +0 -0
- package/screenshots/r293-p0/baseline-full.png +0 -0
- package/screenshots/r293-p0/baseline-topo.png +0 -0
- package/screenshots/r293-p0/logo-integrated-full.png +0 -0
- package/screenshots/r293-p0/logo-integrated-topo.png +0 -0
- package/screenshots/r293-p0/servers-drawer-v0.10.0-full.png +0 -0
- package/screenshots/r293-p0/servers-drawer-v0.10.0.png +0 -0
- package/screenshots/r293-p0/wave1-after-full.png +0 -0
- package/screenshots/r293-p0/wave1-after-topo.png +0 -0
- package/scripts/p0-servers-drawer-screenshot.mjs +99 -0
- package/scripts/p0-topo-screenshot.mjs +82 -0
- package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
- package/scripts/topo-active-links-chip-self-hover-test.mjs +171 -0
- package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
- package/scripts/topo-active-links-freshness-test.mjs +113 -0
- package/scripts/topo-active-links-tooltip-test.mjs +79 -0
- package/scripts/topo-active-pulse-ease-test.mjs +120 -0
- package/scripts/topo-brand-logo-transition-test.mjs +94 -0
- package/scripts/topo-brand-watermark-test.mjs +97 -0
- package/scripts/topo-canvas-aria-test.mjs +108 -0
- package/scripts/topo-canvas-brand-fade-test.mjs +87 -0
- package/scripts/topo-chat-target-breath-test.mjs +84 -0
- package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
- package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
- package/scripts/topo-chip-empty-recede-test.mjs +116 -0
- package/scripts/topo-chip-focus-test.mjs +95 -0
- package/scripts/topo-chip-row-gap-test.mjs +90 -0
- package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
- package/scripts/topo-chip-row-tabular-test.mjs +111 -0
- package/scripts/topo-chip-unit-demotion-test.mjs +93 -0
- package/scripts/topo-chiprow-data-weight-test.mjs +94 -0
- package/scripts/topo-chrome-a11y-test.mjs +107 -0
- package/scripts/topo-chrome-active-weight-test.mjs +104 -0
- package/scripts/topo-chrome-focus-ring-unify-test.mjs +100 -0
- package/scripts/topo-chrome-gap-test.mjs +73 -0
- package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
- package/scripts/topo-chrome-inactive-color-test.mjs +90 -0
- package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
- package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
- package/scripts/topo-chrome-press-state-test.mjs +135 -0
- package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
- package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
- package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
- package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
- package/scripts/topo-chrome-zoom-readout-weight-test.mjs +81 -0
- package/scripts/topo-click-ripple-ease-test.mjs +94 -0
- package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
- package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
- package/scripts/topo-corner-radius-unify-test.mjs +103 -0
- package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
- package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
- package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
- package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
- package/scripts/topo-edge-badge-hot-test.mjs +115 -0
- package/scripts/topo-edge-badge-hover-test.mjs +85 -0
- package/scripts/topo-edge-badge-lift-test.mjs +150 -0
- package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
- package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
- package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
- package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
- package/scripts/topo-edge-count-badge-test.mjs +86 -0
- package/scripts/topo-edge-fadein-test.mjs +106 -0
- package/scripts/topo-edge-pill-hot-test.mjs +129 -0
- package/scripts/topo-edge-pill-test.mjs +105 -0
- package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
- package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
- package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
- package/scripts/topo-empty-hint-tracking-test.mjs +84 -0
- package/scripts/topo-empty-state-breath-test.mjs +150 -0
- package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
- package/scripts/topo-empty-state-recenter-test.mjs +107 -0
- package/scripts/topo-empty-subhint-tracking-test.mjs +87 -0
- package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
- package/scripts/topo-endpoint-ring-test.mjs +93 -0
- package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
- package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
- package/scripts/topo-filter-pill-count-tabular-test.mjs +135 -0
- package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
- package/scripts/topo-freshness-chip-weight-test.mjs +90 -0
- package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
- package/scripts/topo-fullscreen-active-test.mjs +115 -0
- package/scripts/topo-group-label-a11y-test.mjs +113 -0
- package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
- package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
- package/scripts/topo-group-label-tint-test.mjs +99 -0
- package/scripts/topo-group-label-title-test.mjs +83 -0
- package/scripts/topo-group-pip-fadein-test.mjs +104 -0
- package/scripts/topo-group-pip-singletier-drop-test.mjs +131 -0
- package/scripts/topo-group-pip-tabular-test.mjs +95 -0
- package/scripts/topo-groupbox-fadein-test.mjs +95 -0
- package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
- package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
- package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
- package/scripts/topo-header-gap-test.mjs +94 -0
- package/scripts/topo-help-overlay-r109-test.mjs +67 -0
- package/scripts/topo-help-overlay-r118-test.mjs +69 -0
- package/scripts/topo-help-overlay-r123-test.mjs +69 -0
- package/scripts/topo-help-overlay-r134-test.mjs +74 -0
- package/scripts/topo-help-overlay-r137-test.mjs +81 -0
- package/scripts/topo-help-overlay-r141-test.mjs +86 -0
- package/scripts/topo-help-overlay-r153-test.mjs +85 -0
- package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
- package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
- package/scripts/topo-hub-a11y-test.mjs +112 -0
- package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
- package/scripts/topo-hub-digit-scale-test.mjs +129 -0
- package/scripts/topo-hub-fadein-test.mjs +88 -0
- package/scripts/topo-hub-hover-lift-test.mjs +101 -0
- package/scripts/topo-hub-hover-ring-test.mjs +82 -0
- package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
- package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
- package/scripts/topo-hub-working-count-test.mjs +99 -0
- package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
- package/scripts/topo-kicker-color-test.mjs +93 -0
- package/scripts/topo-kicker-tracking-test.mjs +101 -0
- package/scripts/topo-kicker-weight-test.mjs +98 -0
- package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
- package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
- package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
- package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
- package/scripts/topo-layout-toggle-test.mjs +131 -0
- package/scripts/topo-layout-trailer-mr-test.mjs +82 -0
- package/scripts/topo-legend-compress-test.mjs +107 -0
- package/scripts/topo-legend-count-empty-test.mjs +126 -0
- package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
- package/scripts/topo-legend-count-tabular-test.mjs +87 -0
- package/scripts/topo-legend-count-test.mjs +88 -0
- package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
- package/scripts/topo-legend-count-weight-test.mjs +86 -0
- package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
- package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
- package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
- package/scripts/topo-legend-offline-label-simplify-test.mjs +86 -0
- package/scripts/topo-legend-panel-count-weight-test.mjs +89 -0
- package/scripts/topo-legend-panel-header-test.mjs +83 -0
- package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
- package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
- package/scripts/topo-legend-row-labels-simplify-test.mjs +80 -0
- package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
- package/scripts/topo-legend-row-tint-test.mjs +95 -0
- package/scripts/topo-legend-row-title-test.mjs +102 -0
- package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
- package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
- package/scripts/topo-minimap-a11y-test.mjs +101 -0
- package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
- package/scripts/topo-minimap-radius-test.mjs +88 -0
- package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
- package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
- package/scripts/topo-monogram-font-test.mjs +112 -0
- package/scripts/topo-monogram-stroke-test.mjs +101 -0
- package/scripts/topo-more-flows-letterspacing-test.mjs +111 -0
- package/scripts/topo-node-a11y-test.mjs +107 -0
- package/scripts/topo-node-alias-chat-pin-test.mjs +95 -0
- package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
- package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
- package/scripts/topo-node-stroke-transition-test.mjs +103 -0
- package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
- package/scripts/topo-node-title-peers-test.mjs +107 -0
- package/scripts/topo-node-title-test.mjs +101 -0
- package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
- package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
- package/scripts/topo-online-chip-nav-test.mjs +143 -0
- package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
- package/scripts/topo-panel-count-unit-test.mjs +112 -0
- package/scripts/topo-panel-fadein-test.mjs +102 -0
- package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
- package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
- package/scripts/topo-panel-rx-test.mjs +90 -0
- package/scripts/topo-panel-theme-ease-test.mjs +85 -0
- package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
- package/scripts/topo-panel-title-tracking-test.mjs +78 -0
- package/scripts/topo-particle-stagger-test.mjs +82 -0
- package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
- package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
- package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
- package/scripts/topo-pin-intersection-test.mjs +121 -0
- package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
- package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
- package/scripts/topo-prefix-opacity-test.mjs +107 -0
- package/scripts/topo-prepublish-guard-test.mjs +95 -0
- package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
- package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
- package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
- package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
- package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
- package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
- package/scripts/topo-recent-panel-count-test.mjs +72 -0
- package/scripts/topo-recent-panel-count-weight-test.mjs +91 -0
- package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
- package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
- package/scripts/topo-recent-panel-hot-tabular-test.mjs +113 -0
- package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
- package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
- package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
- package/scripts/topo-recent-panel-more-test.mjs +106 -0
- package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
- package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
- package/scripts/topo-recent-row-count-weight-test.mjs +112 -0
- package/scripts/topo-recent-row-fadein-test.mjs +107 -0
- package/scripts/topo-recent-row-freshness-test.mjs +102 -0
- package/scripts/topo-recent-row-hot-test.mjs +110 -0
- package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
- package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
- package/scripts/topo-recent-row-pin-test.mjs +126 -0
- package/scripts/topo-recent-row-tint-test.mjs +91 -0
- package/scripts/topo-recent-row-title-test.mjs +112 -0
- package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
- package/scripts/topo-recent-row-ts-tabular-test.mjs +108 -0
- package/scripts/topo-recent-row-ts-test.mjs +95 -0
- package/scripts/topo-recent-row-typography-test.mjs +119 -0
- package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
- package/scripts/topo-reset-spin-test.mjs +120 -0
- package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
- package/scripts/topo-spokes-retired-test.mjs +93 -0
- package/scripts/topo-starfield-density-test.mjs +89 -0
- package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
- package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
- package/scripts/topo-svg-focus-test.mjs +112 -0
- package/scripts/topo-sweep-busyness-test.mjs +94 -0
- package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
- package/scripts/topo-tier-guide-dashes-test.mjs +86 -0
- package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
- package/scripts/topo-tier-ring-tint-test.mjs +110 -0
- package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
- package/scripts/topo-title-tracking-test.mjs +98 -0
- package/scripts/topo-titleblock-gap-test.mjs +86 -0
- package/scripts/topo-titleblock-margin-test.mjs +83 -0
- package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
- package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
- package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
- package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
- package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
- package/scripts/topo-vendor-chip-weight-test.mjs +87 -0
- package/scripts/topo-vendor-count-suffix-test.mjs +95 -0
- package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
- package/scripts/topo-view-smooth-test.mjs +131 -0
- package/scripts/topo-watermark-tracking-test.mjs +99 -0
- package/scripts/topo-working-chip-pin-test.mjs +154 -0
- package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
- package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
- package/scripts/topo-wrapper-radius-test.mjs +73 -0
- package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
- package/.next/static/chunks/03w4rgx_vt8ec.js +0 -1
- package/.next/static/chunks/072so8rfx2tt-.css +0 -2
- package/.next/static/chunks/0o.k-.0kl4bz5.js +0 -2
- package/.next/static/chunks/121d8yvfl8l_..js +0 -7
- package/.next/static/chunks/148o1e4ijgwp1.js +0 -1
- package/scripts/topo-arrival-ping-test.mjs +0 -87
- package/scripts/topo-dispatch-pulse-test.mjs +0 -83
- /package/.next/static/{Dhky2WTh8mvJhw56taHVs → 424zX3i80AJ_wHF_otKuP}/_buildManifest.js +0 -0
- /package/.next/static/{Dhky2WTh8mvJhw56taHVs → 424zX3i80AJ_wHF_otKuP}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{Dhky2WTh8mvJhw56taHVs → 424zX3i80AJ_wHF_otKuP}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* Round 309 verification: legend per-row count gains fontWeight=600
|
|
2
|
+
* (semibold). The count is the DATA in each row; semibold gives it
|
|
3
|
+
* glanceable priority over the default-weight row label.
|
|
4
|
+
*
|
|
5
|
+
* Contract:
|
|
6
|
+
* - [data-legend-count='working'] attr font-weight === '600'.
|
|
7
|
+
* - [data-legend-count='idle'] attr font-weight === '600'.
|
|
8
|
+
* - [data-legend-count='offline'] attr font-weight === '600'.
|
|
9
|
+
* - All three preserve tabular-nums (R274 family).
|
|
10
|
+
* - R308 row labels still 'working' / 'idle' / 'offline'.
|
|
11
|
+
* - R306 chrome focus-ring-1 + R304 sub-hint + R294 pulse intact.
|
|
12
|
+
*/
|
|
13
|
+
import { chromium } from 'playwright';
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
17
|
+
const browser = await chromium.launch({ headless: true });
|
|
18
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
19
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
20
|
+
await ctx.addInitScript(() => {
|
|
21
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
22
|
+
});
|
|
23
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
24
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
25
|
+
const r = await route.fetch();
|
|
26
|
+
const b = await r.json();
|
|
27
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
28
|
+
const mk = (alias, model, status) => ({
|
|
29
|
+
alias, status, model, runtime: 'claude-code-cli',
|
|
30
|
+
network_id: nid, project_dir: null,
|
|
31
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
32
|
+
});
|
|
33
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
34
|
+
mk('alpha', 'claude-opus-4', 'working'),
|
|
35
|
+
mk('beta', 'gpt-4o', 'idle'),
|
|
36
|
+
mk('gamma', 'claude-sonnet-4', 'offline'),
|
|
37
|
+
] } });
|
|
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.waitForSelector('[data-legend-count="working"]', { timeout: 15000 });
|
|
45
|
+
await page.waitForTimeout(300);
|
|
46
|
+
|
|
47
|
+
const probe = await page.evaluate(() => {
|
|
48
|
+
const sel = (s) => document.querySelector(s);
|
|
49
|
+
const working = sel('[data-legend-count="working"]');
|
|
50
|
+
const idle = sel('[data-legend-count="idle"]');
|
|
51
|
+
const offline = sel('[data-legend-count="offline"]');
|
|
52
|
+
const fvn = (el) => el ? getComputedStyle(el).fontVariantNumeric : null;
|
|
53
|
+
return {
|
|
54
|
+
workingFw: working?.getAttribute('font-weight') ?? null,
|
|
55
|
+
idleFw: idle?.getAttribute('font-weight') ?? null,
|
|
56
|
+
offlineFw: offline?.getAttribute('font-weight') ?? null,
|
|
57
|
+
workingFvn: fvn(working),
|
|
58
|
+
workingLabel: sel('[data-legend-row-label="working"]')?.textContent?.trim() ?? null,
|
|
59
|
+
idleLabel: sel('[data-legend-row-label="idle"]')?.textContent?.trim() ?? null,
|
|
60
|
+
offlineLabel: sel('[data-legend-row-label="offline"]')?.textContent?.trim() ?? null,
|
|
61
|
+
layoutRingCls: sel('[data-topo-chrome-layout="ring"]')?.className ?? '',
|
|
62
|
+
subhintLs: sel('[data-recent-signal-empty-hint]')?.getAttribute('letter-spacing') ?? null,
|
|
63
|
+
pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
await browser.close();
|
|
67
|
+
|
|
68
|
+
const hasTab = (s) => /tabular-nums/.test(s || '');
|
|
69
|
+
|
|
70
|
+
const results = {
|
|
71
|
+
working_count_fw_600: probe.workingFw === '600',
|
|
72
|
+
idle_count_fw_600: probe.idleFw === '600',
|
|
73
|
+
offline_count_fw_600: probe.offlineFw === '600',
|
|
74
|
+
count_tabular_kept: hasTab(probe.workingFvn),
|
|
75
|
+
r308_working_label: probe.workingLabel === 'working',
|
|
76
|
+
r308_idle_label: probe.idleLabel === 'idle',
|
|
77
|
+
r307_offline_label: probe.offlineLabel === 'offline',
|
|
78
|
+
r306_focus_ring_1: probe.layoutRingCls.includes('focus-visible:ring-1'),
|
|
79
|
+
r304_subhint_ls_0_15: probe.subhintLs === '0.15',
|
|
80
|
+
r294_pulse_absent: probe.pulseCount === 0,
|
|
81
|
+
};
|
|
82
|
+
const ok = Object.values(results).every(Boolean);
|
|
83
|
+
console.log(`${ok ? '✅' : '❌'} legend count weight:`, JSON.stringify(results),
|
|
84
|
+
'\n counts fw:', probe.workingFw, '/', probe.idleFw, '/', probe.offlineFw,
|
|
85
|
+
'\n labels:', probe.workingLabel, '/', probe.idleLabel, '/', probe.offlineLabel);
|
|
86
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* Round 257 verification: legend panel header symmetric 13L/13R inner-
|
|
2
|
+
* padding, matching the recent-signal panel pattern.
|
|
3
|
+
*
|
|
4
|
+
* Pre-R257 the two corner panels had inconsistent header inner-padding:
|
|
5
|
+
*
|
|
6
|
+
* Recent-signal panel (width 230):
|
|
7
|
+
* · "recent signal" at x=13 → 13px left inset
|
|
8
|
+
* · "{N} flows ..." at x=217 → 13px right inset (end-anchored)
|
|
9
|
+
* → 13L / 13R · symmetric ✅
|
|
10
|
+
*
|
|
11
|
+
* Legend panel (width 224):
|
|
12
|
+
* · "legend" at x=13 → 13px left inset
|
|
13
|
+
* · "{N} nodes" at x=215 → 9px right inset (end-anchored)
|
|
14
|
+
* → 13L / 9R · asymmetric ❌
|
|
15
|
+
*
|
|
16
|
+
* R257 changes legend header count x=215 → x=211 (= 224-13), so the
|
|
17
|
+
* legend panel header now reads 13L / 13R, matching recent-signal.
|
|
18
|
+
*
|
|
19
|
+
* The per-row count text at x=215 (offline-row level, line ~6321)
|
|
20
|
+
* stays — it's paired with the flow-arrow swatch geometry
|
|
21
|
+
* (path "M140,80 Q164,56 196,80" + marker tip ≈ x=202). Moving the
|
|
22
|
+
* row-level count to x=211 would tighten the swatch→count gap from
|
|
23
|
+
* 13px to 9px, visibly pinching the indicator. R257 only adjusts the
|
|
24
|
+
* standalone header count.
|
|
25
|
+
*
|
|
26
|
+
* Test scope:
|
|
27
|
+
* 1. Legend "legend" title x === 13.
|
|
28
|
+
* 2. Legend "{N} nodes" header count x === 211 (was 215).
|
|
29
|
+
* 3. Recent-signal title x === 13 (regression).
|
|
30
|
+
* 4. Recent-signal flow-count tspan parent <text> x === 217 (regression).
|
|
31
|
+
* 5. Legend row count (offline) x === 215 — UNCHANGED (paired with
|
|
32
|
+
* flow-arrow swatch).
|
|
33
|
+
*/
|
|
34
|
+
import { chromium } from 'playwright';
|
|
35
|
+
import { readFileSync } from 'node:fs';
|
|
36
|
+
|
|
37
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
38
|
+
const browser = await chromium.launch({ headless: true });
|
|
39
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
40
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
41
|
+
await ctx.addInitScript(() => {
|
|
42
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
43
|
+
});
|
|
44
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
45
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
46
|
+
const r = await route.fetch();
|
|
47
|
+
const b = await r.json();
|
|
48
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
49
|
+
const mk = (alias) => ({
|
|
50
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
51
|
+
network_id: nid, project_dir: null,
|
|
52
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
53
|
+
});
|
|
54
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
55
|
+
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
56
|
+
] } });
|
|
57
|
+
});
|
|
58
|
+
await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
|
|
59
|
+
await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
|
|
60
|
+
|
|
61
|
+
const page = await ctx.newPage();
|
|
62
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
63
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
64
|
+
await page.waitForSelector('[data-legend-panel-count]', { timeout: 10000 });
|
|
65
|
+
await page.waitForSelector('[data-recent-panel-count]', { timeout: 10000 });
|
|
66
|
+
await page.waitForSelector('[data-legend-status="offline"] [data-legend-count="offline"]', { timeout: 10000 });
|
|
67
|
+
await page.waitForTimeout(300);
|
|
68
|
+
|
|
69
|
+
const probe = await page.evaluate(() => {
|
|
70
|
+
// Legend panel
|
|
71
|
+
const legendPanelG = document.querySelector('[data-topo-panel="legend"]');
|
|
72
|
+
const legendRectW = +legendPanelG?.querySelector('rect').getAttribute('width');
|
|
73
|
+
const legendTitle = [...legendPanelG.querySelectorAll('text')].find(t => t.textContent.trim() === 'legend');
|
|
74
|
+
const legendCount = document.querySelector('[data-legend-panel-count]');
|
|
75
|
+
// Recent-signal panel
|
|
76
|
+
const recentPanelG = document.querySelector('[data-topo-panel="recent"]');
|
|
77
|
+
const recentRectW = +recentPanelG?.querySelector('rect').getAttribute('width');
|
|
78
|
+
const recentTitle = [...recentPanelG.querySelectorAll('text')].find(t => t.textContent.trim().startsWith('recent signal'));
|
|
79
|
+
const recentFlowCount = document.querySelector('[data-recent-panel-count]');
|
|
80
|
+
// Recent flow count tspan's parent <text> carries the x attr
|
|
81
|
+
const recentFlowCountParentX = recentFlowCount ? recentFlowCount.parentElement.getAttribute('x') : null;
|
|
82
|
+
// Legend offline-row count (unchanged @ x=215)
|
|
83
|
+
const legendOfflineCount = document.querySelector('[data-legend-count="offline"]');
|
|
84
|
+
return {
|
|
85
|
+
legendRectW, recentRectW,
|
|
86
|
+
legendTitleX: legendTitle ? +legendTitle.getAttribute('x') : null,
|
|
87
|
+
legendCountX: legendCount ? +legendCount.getAttribute('x') : null,
|
|
88
|
+
recentTitleX: recentTitle ? +recentTitle.getAttribute('x') : null,
|
|
89
|
+
recentFlowCountX: recentFlowCountParentX ? +recentFlowCountParentX : null,
|
|
90
|
+
legendOfflineCountX: legendOfflineCount ? +legendOfflineCount.getAttribute('x') : null,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
await browser.close();
|
|
94
|
+
|
|
95
|
+
const results = {
|
|
96
|
+
legend_title_x_is_13: probe.legendTitleX === 13,
|
|
97
|
+
legend_count_x_moved_to_211: probe.legendCountX === 211,
|
|
98
|
+
legend_symmetric_inset_13L_13R: probe.legendRectW != null && probe.legendCountX != null &&
|
|
99
|
+
probe.legendTitleX === 13 &&
|
|
100
|
+
(probe.legendRectW - probe.legendCountX) === 13,
|
|
101
|
+
recent_title_x_is_13_unchanged: probe.recentTitleX === 13,
|
|
102
|
+
recent_flow_count_x_217_unchanged: probe.recentFlowCountX === 217,
|
|
103
|
+
recent_symmetric_inset_13L_13R: probe.recentRectW != null && probe.recentFlowCountX != null &&
|
|
104
|
+
(probe.recentRectW - probe.recentFlowCountX) === 13,
|
|
105
|
+
legend_offline_count_x_215_unchanged: probe.legendOfflineCountX === 215,
|
|
106
|
+
};
|
|
107
|
+
const ok = Object.values(results).every(Boolean);
|
|
108
|
+
console.log(`${ok ? '✅' : '❌'} legend header symmetric inset:`, JSON.stringify(results),
|
|
109
|
+
'\n legend rect_w=', probe.legendRectW, ' title_x=', probe.legendTitleX, ' count_x=', probe.legendCountX,
|
|
110
|
+
'\n recent rect_w=', probe.recentRectW, ' title_x=', probe.recentTitleX, ' flow_count_x=', probe.recentFlowCountX,
|
|
111
|
+
'\n legend offline_count_x=', probe.legendOfflineCountX, '(should stay 215)');
|
|
112
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* Round 271 verification: legend row hitbox centers exactly on the
|
|
2
|
+
* swatch dot's cy.
|
|
3
|
+
*
|
|
4
|
+
* Pre-R271:
|
|
5
|
+
* Hitbox y = row.y0 - 12, height = 22 → center at row.y0 - 1
|
|
6
|
+
* Swatch cy = row.y0
|
|
7
|
+
* ⇒ Swatch sits 1 px above hitbox center (asymmetric tint band)
|
|
8
|
+
*
|
|
9
|
+
* Post-R271:
|
|
10
|
+
* Hitbox y = row.y0 - 11, height = 22 → center at row.y0
|
|
11
|
+
* Swatch cy = row.y0
|
|
12
|
+
* ⇒ Swatch sits exactly at hitbox center (symmetric tint band)
|
|
13
|
+
*
|
|
14
|
+
* Test scope:
|
|
15
|
+
* 1. Hover the working row → tint rect appears (data-legend-row-tinted='hover').
|
|
16
|
+
* 2. Tinted rect's getBBox.y === row.y0 - 11 (for working row.y0=32 → y=21).
|
|
17
|
+
* 3. Tinted rect's getBBox.height === 22 (unchanged).
|
|
18
|
+
* 4. Tinted rect's vertical center === swatch cy.
|
|
19
|
+
* 5. R270 fullscreen inactive cyan hover regression intact.
|
|
20
|
+
*/
|
|
21
|
+
import { chromium } from 'playwright';
|
|
22
|
+
import { readFileSync } from 'node:fs';
|
|
23
|
+
|
|
24
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
25
|
+
const browser = await chromium.launch({ headless: true });
|
|
26
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
27
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
28
|
+
await ctx.addInitScript(() => {
|
|
29
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
30
|
+
});
|
|
31
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
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) => ({
|
|
37
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
38
|
+
network_id: nid, project_dir: null,
|
|
39
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
40
|
+
});
|
|
41
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
42
|
+
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
43
|
+
] } });
|
|
44
|
+
});
|
|
45
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
46
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
47
|
+
|
|
48
|
+
const page = await ctx.newPage();
|
|
49
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
50
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
51
|
+
await page.waitForSelector('[data-legend-status="working"]', { timeout: 10000 });
|
|
52
|
+
await page.waitForSelector('[data-legend-swatch="working"]', { timeout: 10000 });
|
|
53
|
+
await page.waitForSelector('[data-topo-chrome-fullscreen]', { timeout: 10000 });
|
|
54
|
+
await page.waitForTimeout(300);
|
|
55
|
+
|
|
56
|
+
// Hover the working row to force the tint rect to render
|
|
57
|
+
await page.locator('[data-legend-status="working"]').hover();
|
|
58
|
+
await page.waitForTimeout(250);
|
|
59
|
+
|
|
60
|
+
const probe = await page.evaluate(() => {
|
|
61
|
+
const row = document.querySelector('[data-legend-status="working"]');
|
|
62
|
+
if (!row) return null;
|
|
63
|
+
// The hitbox rect is the FIRST <rect> inside the row group
|
|
64
|
+
const rect = row.querySelector('rect');
|
|
65
|
+
const swatch = row.querySelector('[data-legend-swatch="working"]');
|
|
66
|
+
const fullscreen = document.querySelector('[data-topo-chrome-fullscreen]');
|
|
67
|
+
return {
|
|
68
|
+
rectY: rect ? +rect.getAttribute('y') : null,
|
|
69
|
+
rectHeight: rect ? +rect.getAttribute('height') : null,
|
|
70
|
+
rectTinted: rect ? rect.getAttribute('data-legend-row-tinted') : null,
|
|
71
|
+
swatchCy: swatch ? +swatch.getAttribute('cy') : null,
|
|
72
|
+
fullscreenClasses: fullscreen ? fullscreen.className.toString() : null,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
await browser.close();
|
|
76
|
+
|
|
77
|
+
const rectCenter = (probe.rectY != null && probe.rectHeight != null) ? probe.rectY + probe.rectHeight / 2 : null;
|
|
78
|
+
const centerMatchesSwatch = (rectCenter != null && probe.swatchCy != null) && rectCenter === probe.swatchCy;
|
|
79
|
+
|
|
80
|
+
const results = {
|
|
81
|
+
rect_y_is_21: probe.rectY === 21,
|
|
82
|
+
rect_height_22: probe.rectHeight === 22,
|
|
83
|
+
rect_tinted_on_hover: probe.rectTinted === 'hover',
|
|
84
|
+
swatch_cy_32: probe.swatchCy === 32,
|
|
85
|
+
rect_center_matches_swatch: centerMatchesSwatch,
|
|
86
|
+
r270_fullscreen_inactive_cyan: (probe.fullscreenClasses || '').includes('hover:bg-cyan-500/5'),
|
|
87
|
+
};
|
|
88
|
+
const ok = Object.values(results).every(Boolean);
|
|
89
|
+
console.log(`${ok ? '✅' : '❌'} legend hitbox swatch center:`, JSON.stringify(results),
|
|
90
|
+
'\n rect y/height:', probe.rectY, probe.rectHeight, '(center', rectCenter, ')',
|
|
91
|
+
'\n swatch cy:', probe.swatchCy,
|
|
92
|
+
'\n rect tinted:', probe.rectTinted);
|
|
93
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* Round 269 verification: legend offline-row label " / " → " · "
|
|
2
|
+
* delimiter unification — closes R138 sweep.
|
|
3
|
+
*
|
|
4
|
+
* Pre-R269 the legend's offline row read "offline / no SSE" (ASCII
|
|
5
|
+
* forward-slash separator). R138 already swept the recent-signal
|
|
6
|
+
* row separators to " · " (typographic middot) along with filter
|
|
7
|
+
* pills, node tooltips, edge badges, and active-links tooltip. The
|
|
8
|
+
* legend offline label was the last hardcoded " / " in TopoGraph.
|
|
9
|
+
*
|
|
10
|
+
* R269 changes the label string from "offline / no SSE" to
|
|
11
|
+
* "offline · no SSE" — same monospace cell width (no layout shift),
|
|
12
|
+
* matches the dashboard-wide typographic delimiter convention.
|
|
13
|
+
*
|
|
14
|
+
* Test scope:
|
|
15
|
+
* 1. Legend offline row label text contains the typographic middot.
|
|
16
|
+
* 2. Label DOES NOT contain " / " (the ASCII separator).
|
|
17
|
+
* 3. Working + idle row labels unchanged (regression: they never
|
|
18
|
+
* used " / ").
|
|
19
|
+
* 4. R257 legend header symmetric inset intact (legend count x === 211).
|
|
20
|
+
*/
|
|
21
|
+
import { chromium } from 'playwright';
|
|
22
|
+
import { readFileSync } from 'node:fs';
|
|
23
|
+
|
|
24
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
25
|
+
const browser = await chromium.launch({ headless: true });
|
|
26
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
27
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
28
|
+
await ctx.addInitScript(() => {
|
|
29
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
30
|
+
});
|
|
31
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
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, status = 'working') => ({
|
|
37
|
+
alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
38
|
+
network_id: nid, project_dir: null,
|
|
39
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
40
|
+
});
|
|
41
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
42
|
+
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
43
|
+
] } });
|
|
44
|
+
});
|
|
45
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
46
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
47
|
+
|
|
48
|
+
const page = await ctx.newPage();
|
|
49
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
50
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
51
|
+
await page.waitForSelector('[data-legend-row-label="offline"]', { timeout: 10000 });
|
|
52
|
+
await page.waitForSelector('[data-legend-row-label="working"]', { timeout: 10000 });
|
|
53
|
+
await page.waitForSelector('[data-legend-row-label="idle"]', { timeout: 10000 });
|
|
54
|
+
await page.waitForSelector('[data-legend-panel-count]', { timeout: 10000 });
|
|
55
|
+
await page.waitForTimeout(300);
|
|
56
|
+
|
|
57
|
+
const probe = await page.evaluate(() => {
|
|
58
|
+
const offline = document.querySelector('[data-legend-row-label="offline"]');
|
|
59
|
+
const working = document.querySelector('[data-legend-row-label="working"]');
|
|
60
|
+
const idle = document.querySelector('[data-legend-row-label="idle"]');
|
|
61
|
+
const count = document.querySelector('[data-legend-panel-count]');
|
|
62
|
+
return {
|
|
63
|
+
offlineText: offline ? offline.textContent : null,
|
|
64
|
+
workingText: working ? working.textContent : null,
|
|
65
|
+
idleText: idle ? idle.textContent : null,
|
|
66
|
+
countX: count ? +count.getAttribute('x') : null,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
await browser.close();
|
|
70
|
+
|
|
71
|
+
const results = {
|
|
72
|
+
offline_uses_middot: probe.offlineText === 'offline · no SSE',
|
|
73
|
+
offline_no_ascii_slash: probe.offlineText != null && !probe.offlineText.includes(' / '),
|
|
74
|
+
working_text_unchanged: probe.workingText === 'working node',
|
|
75
|
+
idle_text_unchanged: probe.idleText === 'online idle',
|
|
76
|
+
r257_legend_count_x_211: probe.countX === 211,
|
|
77
|
+
};
|
|
78
|
+
const ok = Object.values(results).every(Boolean);
|
|
79
|
+
console.log(`${ok ? '✅' : '❌'} legend offline delimiter:`, JSON.stringify(results),
|
|
80
|
+
'\n offline:', JSON.stringify(probe.offlineText),
|
|
81
|
+
'\n working:', JSON.stringify(probe.workingText),
|
|
82
|
+
'\n idle: ', JSON.stringify(probe.idleText),
|
|
83
|
+
'\n count x:', probe.countX);
|
|
84
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* Round 307 verification: legend offline row label drops the
|
|
2
|
+
* '· no SSE' qualifier. 'offline · no SSE' (16 chars) → 'offline'
|
|
3
|
+
* (7 chars). Visual already communicates the disconnected state
|
|
4
|
+
* via status ring dashed pattern + gray fill + gray swatch.
|
|
5
|
+
*
|
|
6
|
+
* Contract:
|
|
7
|
+
* - [data-legend-row-label='offline'] text content === 'offline'.
|
|
8
|
+
* - Does NOT contain 'no SSE' substring.
|
|
9
|
+
* - Working row label still 'working node'.
|
|
10
|
+
* - Idle row label still 'online idle'.
|
|
11
|
+
* - R306 focus-ring-1 + R305 alias chat-target attr + R304/R302/
|
|
12
|
+
* R301/R294 regressions intact.
|
|
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
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
20
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
21
|
+
await ctx.addInitScript(() => {
|
|
22
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
23
|
+
});
|
|
24
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
25
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
26
|
+
const r = await route.fetch();
|
|
27
|
+
const b = await r.json();
|
|
28
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
29
|
+
const mk = (alias, model, status) => ({
|
|
30
|
+
alias, status, model, runtime: 'claude-code-cli',
|
|
31
|
+
network_id: nid, project_dir: null,
|
|
32
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
33
|
+
});
|
|
34
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
35
|
+
mk('alpha', 'claude-opus-4', 'working'),
|
|
36
|
+
mk('beta', 'gpt-4o', 'idle'),
|
|
37
|
+
mk('gamma', 'claude-sonnet-4', 'offline'),
|
|
38
|
+
] } });
|
|
39
|
+
});
|
|
40
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
41
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
42
|
+
|
|
43
|
+
const page = await ctx.newPage();
|
|
44
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
45
|
+
await page.waitForSelector('[data-legend-row-label="offline"]', { timeout: 15000 });
|
|
46
|
+
await page.waitForTimeout(300);
|
|
47
|
+
|
|
48
|
+
const probe = await page.evaluate(() => {
|
|
49
|
+
const sel = (s) => document.querySelector(s);
|
|
50
|
+
const txt = (el) => (el?.textContent || '').trim();
|
|
51
|
+
const offlineLabel = sel('[data-legend-row-label="offline"]');
|
|
52
|
+
const workingLabel = sel('[data-legend-row-label="working"]');
|
|
53
|
+
const idleLabel = sel('[data-legend-row-label="idle"]');
|
|
54
|
+
const layoutRing = sel('[data-topo-chrome-layout="ring"]');
|
|
55
|
+
const alias = sel('[data-node-alias-text]');
|
|
56
|
+
const subhint = sel('[data-recent-signal-empty-hint]');
|
|
57
|
+
return {
|
|
58
|
+
offlineText: txt(offlineLabel),
|
|
59
|
+
workingText: txt(workingLabel),
|
|
60
|
+
idleText: txt(idleLabel),
|
|
61
|
+
layoutRingCls: layoutRing?.className ?? '',
|
|
62
|
+
aliasChatTargetAttr: alias?.hasAttribute('data-node-alias-chat-target') ?? false,
|
|
63
|
+
subhintLs: subhint?.getAttribute('letter-spacing') ?? null,
|
|
64
|
+
pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
await browser.close();
|
|
68
|
+
|
|
69
|
+
const results = {
|
|
70
|
+
offline_label_simplified: probe.offlineText === 'offline',
|
|
71
|
+
offline_label_no_sse_dropped: !/no SSE/i.test(probe.offlineText),
|
|
72
|
+
// R308 superseded the working/idle assertions — both labels were
|
|
73
|
+
// simplified further (working node → working, online idle → idle).
|
|
74
|
+
// Keep this test focused on R307's offline simplification.
|
|
75
|
+
r306_layout_ring_1_kept: probe.layoutRingCls.includes('focus-visible:ring-1'),
|
|
76
|
+
r306_layout_no_ring_2: !probe.layoutRingCls.includes('focus-visible:ring-2'),
|
|
77
|
+
r305_alias_chat_target_attr: probe.aliasChatTargetAttr,
|
|
78
|
+
r304_subhint_ls_0_15: probe.subhintLs === '0.15',
|
|
79
|
+
r294_pulse_absent: probe.pulseCount === 0,
|
|
80
|
+
};
|
|
81
|
+
const ok = Object.values(results).every(Boolean);
|
|
82
|
+
console.log(`${ok ? '✅' : '❌'} legend offline label simplify:`, JSON.stringify(results),
|
|
83
|
+
'\n offline:', JSON.stringify(probe.offlineText),
|
|
84
|
+
'\n working:', JSON.stringify(probe.workingText),
|
|
85
|
+
'\n idle:', JSON.stringify(probe.idleText));
|
|
86
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/* Round 310 verification: legend panel-header count 'N nodes' gains
|
|
2
|
+
* fontWeight=600 for parity with R309 per-row count weight. Same
|
|
3
|
+
* "digit semibold > label regular" hierarchy applied to the panel-
|
|
4
|
+
* summary scope.
|
|
5
|
+
*
|
|
6
|
+
* Contract:
|
|
7
|
+
* - [data-legend-panel-count] attr font-weight === '600'.
|
|
8
|
+
* - Text content matches '{N} node[s]' pattern.
|
|
9
|
+
* - R266 fill transition + R292 tabular-nums preserved.
|
|
10
|
+
* - R309 per-row counts still font-weight='600' (regression).
|
|
11
|
+
* - R308 row labels + R307 offline label + R306 chrome focus-ring-1
|
|
12
|
+
* + R304 sub-hint + R294 pulse all preserved.
|
|
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
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
20
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
21
|
+
await ctx.addInitScript(() => {
|
|
22
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
23
|
+
});
|
|
24
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
25
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
26
|
+
const r = await route.fetch();
|
|
27
|
+
const b = await r.json();
|
|
28
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
29
|
+
const mk = (alias, model, status) => ({
|
|
30
|
+
alias, status, model, runtime: 'claude-code-cli',
|
|
31
|
+
network_id: nid, project_dir: null,
|
|
32
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
33
|
+
});
|
|
34
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
35
|
+
mk('alpha', 'claude-opus-4', 'working'),
|
|
36
|
+
mk('beta', 'gpt-4o', 'idle'),
|
|
37
|
+
mk('gamma', 'claude-sonnet-4', 'offline'),
|
|
38
|
+
] } });
|
|
39
|
+
});
|
|
40
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
41
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
42
|
+
|
|
43
|
+
const page = await ctx.newPage();
|
|
44
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
45
|
+
await page.waitForSelector('[data-legend-panel-count]', { timeout: 15000 });
|
|
46
|
+
await page.waitForTimeout(300);
|
|
47
|
+
|
|
48
|
+
const probe = await page.evaluate(() => {
|
|
49
|
+
const sel = (s) => document.querySelector(s);
|
|
50
|
+
const panelCount = sel('[data-legend-panel-count]');
|
|
51
|
+
const fvn = (el) => el ? getComputedStyle(el).fontVariantNumeric : null;
|
|
52
|
+
return {
|
|
53
|
+
panelCountFw: panelCount?.getAttribute('font-weight') ?? null,
|
|
54
|
+
panelCountText: panelCount?.textContent?.trim() ?? null,
|
|
55
|
+
panelCountFvn: fvn(panelCount),
|
|
56
|
+
rowCountWorkingFw: sel('[data-legend-count="working"]')?.getAttribute('font-weight') ?? null,
|
|
57
|
+
rowCountIdleFw: sel('[data-legend-count="idle"]')?.getAttribute('font-weight') ?? null,
|
|
58
|
+
rowCountOfflineFw: sel('[data-legend-count="offline"]')?.getAttribute('font-weight') ?? null,
|
|
59
|
+
workingLabel: sel('[data-legend-row-label="working"]')?.textContent?.trim() ?? null,
|
|
60
|
+
idleLabel: sel('[data-legend-row-label="idle"]')?.textContent?.trim() ?? null,
|
|
61
|
+
offlineLabel: sel('[data-legend-row-label="offline"]')?.textContent?.trim() ?? null,
|
|
62
|
+
layoutRingCls: sel('[data-topo-chrome-layout="ring"]')?.className ?? '',
|
|
63
|
+
subhintLs: sel('[data-recent-signal-empty-hint]')?.getAttribute('letter-spacing') ?? null,
|
|
64
|
+
pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
await browser.close();
|
|
68
|
+
|
|
69
|
+
const hasTab = (s) => /tabular-nums/.test(s || '');
|
|
70
|
+
|
|
71
|
+
const results = {
|
|
72
|
+
panel_count_fw_600: probe.panelCountFw === '600',
|
|
73
|
+
panel_count_text_pattern: /^\d+ node/.test(probe.panelCountText || ''),
|
|
74
|
+
panel_count_tabular_kept: hasTab(probe.panelCountFvn),
|
|
75
|
+
r309_row_working_fw_600: probe.rowCountWorkingFw === '600',
|
|
76
|
+
r309_row_idle_fw_600: probe.rowCountIdleFw === '600',
|
|
77
|
+
r309_row_offline_fw_600: probe.rowCountOfflineFw === '600',
|
|
78
|
+
r308_working_label: probe.workingLabel === 'working',
|
|
79
|
+
r308_idle_label: probe.idleLabel === 'idle',
|
|
80
|
+
r307_offline_label: probe.offlineLabel === 'offline',
|
|
81
|
+
r306_focus_ring_1_kept: probe.layoutRingCls.includes('focus-visible:ring-1'),
|
|
82
|
+
r304_subhint_ls_0_15: probe.subhintLs === '0.15',
|
|
83
|
+
r294_pulse_absent: probe.pulseCount === 0,
|
|
84
|
+
};
|
|
85
|
+
const ok = Object.values(results).every(Boolean);
|
|
86
|
+
console.log(`${ok ? '✅' : '❌'} legend panel count weight:`, JSON.stringify(results),
|
|
87
|
+
'\n panel count:', probe.panelCountText, 'fw=', probe.panelCountFw,
|
|
88
|
+
'\n row counts fw:', probe.rowCountWorkingFw, '/', probe.rowCountIdleFw, '/', probe.rowCountOfflineFw);
|
|
89
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* Round 106 verification: legend panel gains a header matching the
|
|
2
|
+
* recent-signal panel's "recent signal · N flows" format. Same
|
|
3
|
+
* font/position vocabulary so the two side panels feel paired. The
|
|
4
|
+
* fleet count is right-aligned at x=215 in the accent colour.
|
|
5
|
+
*
|
|
6
|
+
* Sanity-checks:
|
|
7
|
+
* - "legend" title text exists in the legend panel
|
|
8
|
+
* - "N node(s)" count matches sessions.length
|
|
9
|
+
* - Singular vs plural ("1 node" vs "5 nodes") correct
|
|
10
|
+
* - Existing rows still render (hover affordance preserved)
|
|
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() - 60 * 1000).toISOString();
|
|
18
|
+
|
|
19
|
+
const probe = async (count) => {
|
|
20
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
21
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
22
|
+
await ctx.addInitScript(() => {
|
|
23
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
24
|
+
});
|
|
25
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
26
|
+
const r = await route.fetch();
|
|
27
|
+
const b = await r.json();
|
|
28
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
29
|
+
const sessions = [];
|
|
30
|
+
for (let i = 0; i < count; i++) {
|
|
31
|
+
sessions.push({
|
|
32
|
+
alias: `n${i}`, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
33
|
+
network_id: nid, project_dir: null,
|
|
34
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
38
|
+
});
|
|
39
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
40
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
41
|
+
const page = await ctx.newPage();
|
|
42
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
43
|
+
await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n, count, { timeout: 30000 });
|
|
44
|
+
await page.waitForSelector('[data-legend-panel-count]', { timeout: 10000 });
|
|
45
|
+
await page.waitForTimeout(400);
|
|
46
|
+
|
|
47
|
+
const data = await page.evaluate(() => {
|
|
48
|
+
const headerCount = document.querySelector('[data-legend-panel-count]')?.textContent;
|
|
49
|
+
const headerTitle = (() => {
|
|
50
|
+
// Find a <text> with "legend" content in the legend panel <g>
|
|
51
|
+
const panel = document.querySelector('[data-topo-panel-elevation="legend"]')?.parentElement;
|
|
52
|
+
if (!panel) return null;
|
|
53
|
+
for (const t of panel.querySelectorAll('text')) {
|
|
54
|
+
if (t.textContent === 'legend') return t.textContent;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
})();
|
|
58
|
+
const legendRows = document.querySelectorAll('[data-legend-status]').length;
|
|
59
|
+
return { headerCount, headerTitle, legendRows };
|
|
60
|
+
});
|
|
61
|
+
await ctx.close();
|
|
62
|
+
return data;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const single = await probe(1);
|
|
66
|
+
const many = await probe(5);
|
|
67
|
+
await browser.close();
|
|
68
|
+
|
|
69
|
+
const results = {
|
|
70
|
+
single_hasTitle: single.headerTitle === 'legend',
|
|
71
|
+
single_count1Node: single.headerCount === '1 node',
|
|
72
|
+
many_hasTitle: many.headerTitle === 'legend',
|
|
73
|
+
many_count5Nodes: many.headerCount === '5 nodes',
|
|
74
|
+
many_rowsStillRender: many.legendRows >= 1, // depends on which states are present
|
|
75
|
+
// Sanity: singular vs plural
|
|
76
|
+
singular_correct: single.headerCount?.endsWith(' node'),
|
|
77
|
+
plural_correct: many.headerCount?.endsWith(' nodes'),
|
|
78
|
+
};
|
|
79
|
+
const ok = Object.values(results).every(Boolean);
|
|
80
|
+
console.log(`${ok ? '✅' : '❌'} legend panel header:`, JSON.stringify(results),
|
|
81
|
+
`\n single (1 node)=`, single,
|
|
82
|
+
`\n many (5 nodes)=`, many);
|
|
83
|
+
process.exit(ok ? 0 : 1);
|