@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,121 @@
|
|
|
1
|
+
/* Round 97 verification: each active-filter pill carries the matched
|
|
2
|
+
* alias list in its title attribute + data-filter-match-aliases for
|
|
3
|
+
* tests. Pre-R97 the pill said "filter: working · 3" but you had to
|
|
4
|
+
* eyeball the dimmed canvas to find which 3 nodes were the matches.
|
|
5
|
+
*
|
|
6
|
+
* Drive each pin type and read the alias data attr. Fleet has known
|
|
7
|
+
* mix so the expected alias sets are deterministic.
|
|
8
|
+
*
|
|
9
|
+
* Truncation at 8 aliases is tested via a fleet with > 8 working
|
|
10
|
+
* nodes — confirms the data attr stores the full list (for test
|
|
11
|
+
* stability) while the visible title shows a "+N more" suffix.
|
|
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
|
+
|
|
19
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, 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 {
|
|
23
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
24
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
25
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
26
|
+
sessionStorage.removeItem('anet-topo-pinned-status');
|
|
27
|
+
sessionStorage.removeItem('anet-topo-pinned-group');
|
|
28
|
+
sessionStorage.removeItem('anet-topo-pinned-vendor');
|
|
29
|
+
} catch {}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
33
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
34
|
+
const r = await route.fetch();
|
|
35
|
+
const b = await r.json();
|
|
36
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
37
|
+
const mk = (alias, status, model) => ({
|
|
38
|
+
alias, status, model, runtime: 'cli-claude-code',
|
|
39
|
+
network_id: nid, project_dir: null,
|
|
40
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
41
|
+
});
|
|
42
|
+
// Fleet: 3 working (alpha1/2 + beta1), 3 idle (alpha3 + beta2 + gamma1),
|
|
43
|
+
// 2 vendors A/O. alpha-prefix = 3 members, beta-prefix = 2, gamma = 1.
|
|
44
|
+
const sessions = [
|
|
45
|
+
mk('alpha1', 'working', 'claude-opus-4'),
|
|
46
|
+
mk('alpha2', 'working', 'claude-opus-4'),
|
|
47
|
+
mk('alpha3', 'idle', 'claude-opus-4'),
|
|
48
|
+
mk('beta1', 'working', 'gpt-5'),
|
|
49
|
+
mk('beta2', 'idle', 'gpt-5'),
|
|
50
|
+
mk('gamma1', 'idle', 'claude-opus-4'),
|
|
51
|
+
];
|
|
52
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
53
|
+
});
|
|
54
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
55
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
56
|
+
const page = await ctx.newPage();
|
|
57
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
58
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 6, { timeout: 30000 });
|
|
59
|
+
await page.waitForTimeout(400);
|
|
60
|
+
|
|
61
|
+
const readPill = (kind) => page.evaluate((k) => {
|
|
62
|
+
const p = document.querySelector(`[data-active-filter="${k}"]`);
|
|
63
|
+
if (!p) return null;
|
|
64
|
+
return {
|
|
65
|
+
title: p.getAttribute('title') || '',
|
|
66
|
+
aliases: p.getAttribute('data-filter-match-aliases') || '',
|
|
67
|
+
count: p.getAttribute('data-filter-match-count') || '',
|
|
68
|
+
};
|
|
69
|
+
}, kind);
|
|
70
|
+
|
|
71
|
+
const pin = async (kind, value) => {
|
|
72
|
+
await page.evaluate(({ kind, value }) => {
|
|
73
|
+
if (kind === 'status') sessionStorage.setItem('anet-topo-pinned-status', value);
|
|
74
|
+
if (kind === 'group') sessionStorage.setItem('anet-topo-pinned-group', value);
|
|
75
|
+
if (kind === 'vendor') sessionStorage.setItem('anet-topo-pinned-vendor', value);
|
|
76
|
+
window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind, value } }));
|
|
77
|
+
}, { kind, value });
|
|
78
|
+
await page.waitForTimeout(200);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Status pin → working should match alpha1, alpha2, beta1.
|
|
82
|
+
await pin('status', 'working');
|
|
83
|
+
const statusPill = await readPill('status');
|
|
84
|
+
|
|
85
|
+
// Vendor pin → A should match all claude-opus-4 sessions (alphas + gamma).
|
|
86
|
+
await page.locator('[data-vendor-letter="A"]').click();
|
|
87
|
+
await page.mouse.move(10, 10);
|
|
88
|
+
await page.waitForTimeout(250);
|
|
89
|
+
const vendorPill = await readPill('vendor');
|
|
90
|
+
|
|
91
|
+
// Group pin → alpha should match alpha1/2/3.
|
|
92
|
+
await page.evaluate(() => {
|
|
93
|
+
sessionStorage.setItem('anet-topo-pinned-group', 'alpha');
|
|
94
|
+
window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'group', value: 'alpha' } }));
|
|
95
|
+
});
|
|
96
|
+
await page.waitForTimeout(250);
|
|
97
|
+
const groupPill = await readPill('group');
|
|
98
|
+
|
|
99
|
+
await browser.close();
|
|
100
|
+
|
|
101
|
+
const sameSet = (csv, expected) => {
|
|
102
|
+
const got = new Set(csv.split(',').filter(Boolean));
|
|
103
|
+
const exp = new Set(expected);
|
|
104
|
+
return got.size === exp.size && [...exp].every(x => got.has(x));
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const results = {
|
|
108
|
+
status_aliasesMatch: sameSet(statusPill?.aliases || '', ['alpha1', 'alpha2', 'beta1']),
|
|
109
|
+
status_titleListsThem: /alpha1.*alpha2.*beta1|alpha2.*alpha1.*beta1/.test(statusPill?.title || ''),
|
|
110
|
+
status_titleHasClear: /click to clear/i.test(statusPill?.title || ''),
|
|
111
|
+
group_aliasesMatch: sameSet(groupPill?.aliases || '', ['alpha1', 'alpha2', 'alpha3']),
|
|
112
|
+
group_titleListsThem: /alpha1.*alpha2.*alpha3/.test(groupPill?.title || ''),
|
|
113
|
+
vendor_aliasesMatch: sameSet(vendorPill?.aliases || '', ['alpha1', 'alpha2', 'alpha3', 'gamma1']),
|
|
114
|
+
vendor_titleListsThem: /alpha1.*alpha2.*alpha3.*gamma1/.test(vendorPill?.title || ''),
|
|
115
|
+
};
|
|
116
|
+
const ok = Object.values(results).every(Boolean);
|
|
117
|
+
console.log(`${ok ? '✅' : '❌'} filter pill aliases:`, JSON.stringify(results),
|
|
118
|
+
`\n status=`, statusPill,
|
|
119
|
+
`\n group=`, groupPill,
|
|
120
|
+
`\n vendor=`, vendorPill);
|
|
121
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* Round 73 verification: clicking the pill body (not just the ×) clears
|
|
2
|
+
* the pin. Matches the Notion / Linear tag UX where the whole chip is
|
|
3
|
+
* the clear target. The × button keeps its dedicated <button> +
|
|
4
|
+
* aria-label so screen readers still get a proper "Clear filter" action.
|
|
5
|
+
*
|
|
6
|
+
* - Pre-seed pin via R66 sessionStorage so pill renders on first paint.
|
|
7
|
+
* - Click the pill body (text portion, NOT the × button).
|
|
8
|
+
* - Assert: pin clears + pill removed from DOM + node opacities
|
|
9
|
+
* restored.
|
|
10
|
+
* - Re-pin; click the × button → pin also clears.
|
|
11
|
+
* - title attr on the pill body reads "Click to clear filter".
|
|
12
|
+
* - cursor:pointer set on the pill body for affordance.
|
|
13
|
+
*/
|
|
14
|
+
import { chromium } from 'playwright';
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
|
|
17
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
18
|
+
const browser = await chromium.launch({ headless: true });
|
|
19
|
+
|
|
20
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
21
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
22
|
+
await ctx.addInitScript(() => {
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
25
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
26
|
+
sessionStorage.setItem('anet-topo-pinned-status', 'working');
|
|
27
|
+
} catch {}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
31
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
32
|
+
const r = await route.fetch();
|
|
33
|
+
const b = await r.json();
|
|
34
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
35
|
+
const sessions = [
|
|
36
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
37
|
+
{ alias: 'idl', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
38
|
+
];
|
|
39
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
40
|
+
});
|
|
41
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
42
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
43
|
+
const page = await ctx.newPage();
|
|
44
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
45
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
|
|
46
|
+
await page.waitForTimeout(500);
|
|
47
|
+
|
|
48
|
+
const readState = () => page.evaluate(() => {
|
|
49
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
50
|
+
return {
|
|
51
|
+
pillPresent: !!pill,
|
|
52
|
+
pillTitle: pill?.getAttribute('title') || null,
|
|
53
|
+
pillCursor: pill ? getComputedStyle(pill).cursor : null,
|
|
54
|
+
idlOpacity: +(document.querySelector('g[data-node="idl"]')?.style.opacity || '1'),
|
|
55
|
+
storageStatus: sessionStorage.getItem('anet-topo-pinned-status'),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const before = await readState();
|
|
60
|
+
|
|
61
|
+
// Click on the pill body — pick a point in the text region, NOT the ×.
|
|
62
|
+
// The pill is a flex row; the first inner <span> holds the text. Click
|
|
63
|
+
// at its centre.
|
|
64
|
+
await page.evaluate(() => {
|
|
65
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
66
|
+
if (!pill) throw new Error('pill not found');
|
|
67
|
+
const text = pill.querySelector('span');
|
|
68
|
+
if (!text) throw new Error('pill text not found');
|
|
69
|
+
const r = text.getBoundingClientRect();
|
|
70
|
+
window.__bodyClickXY = { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
|
71
|
+
});
|
|
72
|
+
const xy = await page.evaluate(() => window.__bodyClickXY);
|
|
73
|
+
await page.mouse.click(xy.x, xy.y);
|
|
74
|
+
await page.waitForTimeout(250);
|
|
75
|
+
const afterBodyClick = await readState();
|
|
76
|
+
|
|
77
|
+
// Re-pin via sessionStorage + event.
|
|
78
|
+
await page.evaluate(() => {
|
|
79
|
+
sessionStorage.setItem('anet-topo-pinned-status', 'working');
|
|
80
|
+
window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'status', value: 'working' } }));
|
|
81
|
+
});
|
|
82
|
+
await page.waitForTimeout(250);
|
|
83
|
+
const afterRepin = await readState();
|
|
84
|
+
|
|
85
|
+
// Click the × button.
|
|
86
|
+
await page.locator('[data-active-filter="status"] button').first().click();
|
|
87
|
+
await page.waitForTimeout(250);
|
|
88
|
+
const afterXClick = await readState();
|
|
89
|
+
|
|
90
|
+
await browser.close();
|
|
91
|
+
|
|
92
|
+
const bright = (v) => v != null && v >= 0.55;
|
|
93
|
+
const dim = (v) => v != null && v < 0.4;
|
|
94
|
+
const results = {
|
|
95
|
+
before_pillPresent: before.pillPresent === true,
|
|
96
|
+
before_titleHint: before.pillTitle === 'Click to clear filter',
|
|
97
|
+
before_cursorPointer: before.pillCursor === 'pointer',
|
|
98
|
+
before_idleDim: dim(before.idlOpacity),
|
|
99
|
+
bodyClick_pillGone: afterBodyClick.pillPresent === false,
|
|
100
|
+
bodyClick_storageGone: afterBodyClick.storageStatus === null,
|
|
101
|
+
bodyClick_idleRestored: bright(afterBodyClick.idlOpacity),
|
|
102
|
+
repin_pillBack: afterRepin.pillPresent === true,
|
|
103
|
+
xClick_pillGone: afterXClick.pillPresent === false,
|
|
104
|
+
xClick_storageGone: afterXClick.storageStatus === null,
|
|
105
|
+
};
|
|
106
|
+
const ok = Object.values(results).every(Boolean);
|
|
107
|
+
console.log(`${ok ? '✅' : '❌'} filter pill body click:`, JSON.stringify(results),
|
|
108
|
+
`\n before=`, before,
|
|
109
|
+
`\n afterBodyClick=`, afterBodyClick,
|
|
110
|
+
`\n afterRepin=`, afterRepin,
|
|
111
|
+
`\n afterXClick=`, afterXClick);
|
|
112
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/* Round 323 verification: filter pin pill count spans (status /
|
|
2
|
+
* group / vendor) AND edge filter pill count (cold + hot) all pick
|
|
3
|
+
* up Tailwind `tabular-nums` class. Pre-R323 the count digit
|
|
4
|
+
* (matchCount or link.count) widened 9→10 / 99→100 even in font-mono
|
|
5
|
+
* because mono fonts still have natural-vs-tabular variance; the
|
|
6
|
+
* trailing × clear-button shifted right a couple px.
|
|
7
|
+
*
|
|
8
|
+
* 9th surface in the info-density tabular-nums sweep:
|
|
9
|
+
* R224 edge badge
|
|
10
|
+
* R225 hub digit / panel flows-count / recent-row count
|
|
11
|
+
* R229 group-label count
|
|
12
|
+
* R230 group-label status pips
|
|
13
|
+
* R320 recent-row count fw=600 (left neighbour)
|
|
14
|
+
* R321 recent-row timestamp
|
|
15
|
+
* R322 panel hot count
|
|
16
|
+
* R323 filter pill counts (status + group + vendor + edge×2)
|
|
17
|
+
*
|
|
18
|
+
* Contract:
|
|
19
|
+
* - All 5 patterns gain `tabular-nums`. We can probe via a
|
|
20
|
+
* scenario that activates the status pin (simplest), then
|
|
21
|
+
* check the resulting [data-filter-pill-count] element.
|
|
22
|
+
* - Each `[data-filter-pill-count]` has computed
|
|
23
|
+
* fontVariantNumeric containing 'tabular-nums'.
|
|
24
|
+
* - The edge pill count test is best-effort — only active when
|
|
25
|
+
* a flow is pinned via R116; we don't simulate pin in this
|
|
26
|
+
* test, so we accept its absence.
|
|
27
|
+
* - R322 hot tabular + R321 ts tabular regressions intact.
|
|
28
|
+
* - R317/R318/R294 chrome + pulse regressions intact.
|
|
29
|
+
*
|
|
30
|
+
* Fixture: 2 working sessions, 2 idle sessions — pin status='idle'
|
|
31
|
+
* via initial localStorage so the status filter pill is visible.
|
|
32
|
+
*/
|
|
33
|
+
import { chromium } from 'playwright';
|
|
34
|
+
import { readFileSync } from 'node:fs';
|
|
35
|
+
|
|
36
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
37
|
+
const browser = await chromium.launch({ headless: true });
|
|
38
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
39
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
40
|
+
await ctx.addInitScript(() => {
|
|
41
|
+
try {
|
|
42
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
43
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
44
|
+
} catch {}
|
|
45
|
+
});
|
|
46
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
47
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
48
|
+
const r = await route.fetch();
|
|
49
|
+
const b = await r.json();
|
|
50
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
51
|
+
const mk = (alias, status) => ({
|
|
52
|
+
alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
53
|
+
network_id: nid, project_dir: null,
|
|
54
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
55
|
+
});
|
|
56
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
57
|
+
mk('alpha-1', 'working'),
|
|
58
|
+
mk('alpha-2', 'idle'),
|
|
59
|
+
mk('beta-1', 'working'),
|
|
60
|
+
mk('beta-2', 'idle'),
|
|
61
|
+
] } });
|
|
62
|
+
});
|
|
63
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
64
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
65
|
+
|
|
66
|
+
const page = await ctx.newPage();
|
|
67
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
68
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 20000 });
|
|
69
|
+
await page.waitForTimeout(300);
|
|
70
|
+
|
|
71
|
+
// Programmatically trigger a status pin by clicking on the working
|
|
72
|
+
// status legend row. R64 pin handler responds to legend clicks.
|
|
73
|
+
// Best-effort: if the click target is not exposed, fall back to
|
|
74
|
+
// the chip-row "working chip" click which also triggers pinning.
|
|
75
|
+
await page.click('[data-working-chip]', { delay: 50 }).catch(() => {});
|
|
76
|
+
await page.waitForTimeout(400);
|
|
77
|
+
|
|
78
|
+
const probe = await page.evaluate(() => {
|
|
79
|
+
const pillCounts = Array.from(document.querySelectorAll('[data-filter-pill-count]'));
|
|
80
|
+
const edgeColdCount = document.querySelector('[data-active-filter-edge-count]');
|
|
81
|
+
const edgeHotCount = document.querySelector('[data-active-filter-edge-count-hot]');
|
|
82
|
+
const recentRowTs = document.querySelector('[data-recent-row-ts]');
|
|
83
|
+
const recentRowCount = document.querySelector('[data-recent-row-count]');
|
|
84
|
+
const recentPanelHot = document.querySelector('[data-recent-panel-hot-count]');
|
|
85
|
+
|
|
86
|
+
const cs = (el) => el ? getComputedStyle(el).fontVariantNumeric : null;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
pillCount: pillCounts.length,
|
|
90
|
+
pillCountAllTabular: pillCounts.every(p => /tabular-nums/.test(getComputedStyle(p).fontVariantNumeric || '')),
|
|
91
|
+
pillTexts: pillCounts.map(p => p.textContent),
|
|
92
|
+
edgeColdTabular: edgeColdCount ? cs(edgeColdCount) : null,
|
|
93
|
+
edgeHotTabular: edgeHotCount ? cs(edgeHotCount) : null,
|
|
94
|
+
edgeColdClass: edgeColdCount?.className ?? null,
|
|
95
|
+
edgeHotClass: edgeHotCount?.className ?? null,
|
|
96
|
+
recentRowTsTabular: cs(recentRowTs),
|
|
97
|
+
recentRowCountFw: recentRowCount ? getComputedStyle(recentRowCount).fontWeight : null,
|
|
98
|
+
recentPanelHotTab: cs(recentPanelHot),
|
|
99
|
+
layoutInactiveCls: document.querySelector('[data-topo-chrome-layout="grid"]')?.className ?? '',
|
|
100
|
+
layoutActiveCls: document.querySelector('[data-topo-chrome-layout="ring"]')?.className ?? '',
|
|
101
|
+
pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
await browser.close();
|
|
105
|
+
|
|
106
|
+
const hasTab = (s) => /tabular-nums/.test(s || '');
|
|
107
|
+
const classHasTab = (s) => /tabular-nums/.test(s || '');
|
|
108
|
+
|
|
109
|
+
const results = {
|
|
110
|
+
// At least one filter pill count visible after click (the status pin).
|
|
111
|
+
pill_count_present: probe.pillCount >= 1,
|
|
112
|
+
pill_count_all_tabular: probe.pillCountAllTabular,
|
|
113
|
+
// The edge pills are not triggered in this test, but their CLASS
|
|
114
|
+
// attribute should already include `tabular-nums` from R323 even
|
|
115
|
+
// if not rendered (best-effort source-level probe via a snapshot
|
|
116
|
+
// of an active pill is not possible without simulating R116 pin).
|
|
117
|
+
// We accept presence-or-absence here; the main contract is the
|
|
118
|
+
// status/group/vendor pills.
|
|
119
|
+
// R322 / R321 regressions — both surfaces depend on recent-signal
|
|
120
|
+
// panel rendering, which requires messages. Fixture here has zero
|
|
121
|
+
// messages (only sessions for the pin click), so accept null OR
|
|
122
|
+
// tabular for both. The R322 regression is primarily covered by
|
|
123
|
+
// topo-recent-panel-hot-tabular-test which forces messages.
|
|
124
|
+
r322_panel_hot_tabular_or_absent: probe.recentPanelHotTab === null || hasTab(probe.recentPanelHotTab),
|
|
125
|
+
r321_ts_tabular_or_absent: probe.recentRowTsTabular === null || hasTab(probe.recentRowTsTabular),
|
|
126
|
+
// R317/R318 chrome regression.
|
|
127
|
+
r317_inactive_gray_400: probe.layoutInactiveCls.includes('text-gray-400'),
|
|
128
|
+
r318_active_font_medium: probe.layoutActiveCls.includes('font-medium'),
|
|
129
|
+
r294_pulse_absent: probe.pulseCount === 0,
|
|
130
|
+
};
|
|
131
|
+
const ok = Object.values(results).every(Boolean);
|
|
132
|
+
console.log(`${ok ? '✅' : '❌'} filter pill count tabular:`, JSON.stringify(results),
|
|
133
|
+
'\n pill count visible:', probe.pillCount,
|
|
134
|
+
'\n pill texts:', probe.pillTexts);
|
|
135
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* Round 71 verification: each active-filter pill shows a "· N" match
|
|
2
|
+
* count tail and surfaces `data-filter-match-count` for tooling.
|
|
3
|
+
*
|
|
4
|
+
* Sessions: 1 working + 2 idle + 1 offline + 3-alpha cluster.
|
|
5
|
+
* Pin status=working → pill "filter: working · 1", count=1
|
|
6
|
+
* Pin status=idle → pill "filter: idle · 5", count=5 (idl + alpha1/2/3 all idle online)
|
|
7
|
+
* Pin status=offline → pill "filter: offline · 1", count=1
|
|
8
|
+
* Pin group=alpha → pill "filter: alpha · 3", count=3 (3 alphas)
|
|
9
|
+
*/
|
|
10
|
+
import { chromium } from 'playwright';
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
|
|
13
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
14
|
+
const browser = await chromium.launch({ headless: true });
|
|
15
|
+
|
|
16
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
17
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
18
|
+
await ctx.addInitScript(() => {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
21
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
22
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
23
|
+
sessionStorage.removeItem('anet-topo-pinned-status');
|
|
24
|
+
sessionStorage.removeItem('anet-topo-pinned-group');
|
|
25
|
+
} catch {}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
29
|
+
const stale = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
30
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
31
|
+
const r = await route.fetch();
|
|
32
|
+
const b = await r.json();
|
|
33
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
34
|
+
const sessions = [
|
|
35
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
36
|
+
{ alias: 'idl', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
37
|
+
{ alias: 'idl2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
38
|
+
{ alias: 'alpha1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
39
|
+
{ alias: 'alpha2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
40
|
+
{ alias: 'alpha3', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
41
|
+
{ alias: 'off', status: 'offline', network_id: nid, project_dir: null, created_at: stale, updated_at: stale, last_seen_at: stale },
|
|
42
|
+
];
|
|
43
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
44
|
+
});
|
|
45
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
46
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
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 === 7, { timeout: 30000 });
|
|
50
|
+
await page.waitForTimeout(700);
|
|
51
|
+
|
|
52
|
+
async function pin({ status, group }) {
|
|
53
|
+
await page.evaluate(({ status, group }) => {
|
|
54
|
+
if (status === null) { sessionStorage.removeItem('anet-topo-pinned-status'); }
|
|
55
|
+
else { sessionStorage.setItem('anet-topo-pinned-status', status); }
|
|
56
|
+
if (group === null) { sessionStorage.removeItem('anet-topo-pinned-group'); }
|
|
57
|
+
else if (group) { sessionStorage.setItem('anet-topo-pinned-group', group); }
|
|
58
|
+
window.dispatchEvent(new CustomEvent('anet:topo-pin',
|
|
59
|
+
{ detail: status !== undefined ? { kind: 'status', value: status }
|
|
60
|
+
: group !== undefined ? { kind: 'group', value: group }
|
|
61
|
+
: { kind: 'clear' } }));
|
|
62
|
+
}, { status, group });
|
|
63
|
+
await page.waitForTimeout(250);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const readPill = (kind) => page.evaluate(k => {
|
|
67
|
+
const el = document.querySelector(`[data-active-filter="${k}"]`);
|
|
68
|
+
if (!el) return null;
|
|
69
|
+
return {
|
|
70
|
+
text: (el.innerText || el.textContent || '').trim(),
|
|
71
|
+
count: el.getAttribute('data-filter-match-count'),
|
|
72
|
+
};
|
|
73
|
+
}, kind);
|
|
74
|
+
|
|
75
|
+
// Pin working → 1 match.
|
|
76
|
+
await pin({ status: 'working' });
|
|
77
|
+
const w = await readPill('status');
|
|
78
|
+
|
|
79
|
+
// Pin idle → 5 (idl + idl2 + alpha1 + alpha2 + alpha3 — all idle online).
|
|
80
|
+
await pin({ status: 'idle' });
|
|
81
|
+
const i = await readPill('status');
|
|
82
|
+
|
|
83
|
+
// Pin offline → 1 match.
|
|
84
|
+
await pin({ status: 'offline' });
|
|
85
|
+
const o = await readPill('status');
|
|
86
|
+
|
|
87
|
+
// Pin group alpha → 3 match.
|
|
88
|
+
await pin({ status: null });
|
|
89
|
+
await pin({ group: 'alpha' });
|
|
90
|
+
const g = await readPill('group');
|
|
91
|
+
|
|
92
|
+
await browser.close();
|
|
93
|
+
|
|
94
|
+
const results = {
|
|
95
|
+
working_attrCount1: w && w.count === '1',
|
|
96
|
+
working_textHas1: w && /·\s*1\b/.test(w.text),
|
|
97
|
+
idle_attrCount5: i && i.count === '5',
|
|
98
|
+
idle_textHas5: i && /·\s*5\b/.test(i.text),
|
|
99
|
+
offline_attrCount1: o && o.count === '1',
|
|
100
|
+
offline_textHas1: o && /·\s*1\b/.test(o.text),
|
|
101
|
+
group_attrCount3: g && g.count === '3',
|
|
102
|
+
group_textHas3: g && /·\s*3\b/.test(g.text),
|
|
103
|
+
};
|
|
104
|
+
const ok = Object.values(results).every(Boolean);
|
|
105
|
+
console.log(`${ok ? '✅' : '❌'} filter pill count:`, JSON.stringify(results),
|
|
106
|
+
`\n working=`, w,
|
|
107
|
+
`\n idle=`, i,
|
|
108
|
+
`\n offline=`, o,
|
|
109
|
+
`\n group=`, g);
|
|
110
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* Round 67 verification: filter pills shed the "filter:" prefix on
|
|
2
|
+
* narrow viewports and gain an anet-fade-in entrance.
|
|
3
|
+
* - At <sm (375 px) the prefix span has `display:none` via the
|
|
4
|
+
* `hidden sm:inline` class — visible pill text = "working×".
|
|
5
|
+
* - At sm+ (900 px) the prefix is visible — "filter: working ×".
|
|
6
|
+
* - The pill `<span>` carries `anet-fade-in` class so it eases in
|
|
7
|
+
* on mount (entrance polish).
|
|
8
|
+
*/
|
|
9
|
+
import { chromium } from 'playwright';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
|
|
12
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
13
|
+
const browser = await chromium.launch({ headless: true });
|
|
14
|
+
|
|
15
|
+
async function probe(width) {
|
|
16
|
+
const ctx = await browser.newContext({ viewport: { width, height: 1500 } });
|
|
17
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
18
|
+
await ctx.addInitScript(() => {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
21
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
22
|
+
// Pre-seed the working pin via R66 persistence so the pill exists
|
|
23
|
+
// on first paint — keeps the test independent of click wiring.
|
|
24
|
+
sessionStorage.setItem('anet-topo-pinned-status', 'working');
|
|
25
|
+
} 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 sessions = [
|
|
33
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
34
|
+
{ alias: 'idl', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
35
|
+
];
|
|
36
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
37
|
+
});
|
|
38
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
39
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
40
|
+
const page = await ctx.newPage();
|
|
41
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
42
|
+
// Mobile viewport auto-hides the topology section; click "Show Topology"
|
|
43
|
+
// if the button surfaces. Otherwise the pill still renders in the chip
|
|
44
|
+
// row (chip row is independent of canvas visibility).
|
|
45
|
+
await page.waitForTimeout(1200);
|
|
46
|
+
const showBtn = page.locator('button', { hasText: /Show Topology/i });
|
|
47
|
+
if (await showBtn.count() > 0) await showBtn.first().click();
|
|
48
|
+
await page.waitForSelector('[data-active-filter="status"]', { timeout: 15000 });
|
|
49
|
+
|
|
50
|
+
const info = await page.evaluate(() => {
|
|
51
|
+
const pill = document.querySelector('[data-active-filter="status"]');
|
|
52
|
+
if (!pill) return null;
|
|
53
|
+
const prefix = pill.querySelector('[data-filter-prefix]');
|
|
54
|
+
// innerText respects display:none (vs textContent which includes
|
|
55
|
+
// all DOM text); that's the visible-to-the-user version.
|
|
56
|
+
return {
|
|
57
|
+
pillText: (pill.innerText || '').trim(),
|
|
58
|
+
pillFull: (pill.textContent || '').trim(),
|
|
59
|
+
pillClasses: pill.getAttribute('class') || '',
|
|
60
|
+
hasFadeIn: /\banet-fade-in\b/.test(pill.getAttribute('class') || ''),
|
|
61
|
+
prefixVisible: prefix
|
|
62
|
+
? (() => {
|
|
63
|
+
const r = prefix.getBoundingClientRect();
|
|
64
|
+
return r.width > 0 && r.height > 0;
|
|
65
|
+
})()
|
|
66
|
+
: false,
|
|
67
|
+
prefixDisplay: prefix ? getComputedStyle(prefix).display : null,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
await ctx.close();
|
|
71
|
+
return info;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const mobile = await probe(375);
|
|
75
|
+
const desktop = await probe(900);
|
|
76
|
+
await browser.close();
|
|
77
|
+
|
|
78
|
+
const results = {
|
|
79
|
+
mobile_prefixHidden: mobile && mobile.prefixVisible === false && mobile.prefixDisplay === 'none',
|
|
80
|
+
mobile_textNoPrefix: mobile && /^working/.test(mobile.pillText.trim()),
|
|
81
|
+
mobile_hasFadeIn: mobile && mobile.hasFadeIn === true,
|
|
82
|
+
desktop_prefixVisible: desktop && desktop.prefixVisible === true,
|
|
83
|
+
desktop_textWithPrefix: desktop && /^filter:\s*working/.test(desktop.pillText.trim()),
|
|
84
|
+
desktop_hasFadeIn: desktop && desktop.hasFadeIn === true,
|
|
85
|
+
};
|
|
86
|
+
const ok = Object.values(results).every(Boolean);
|
|
87
|
+
console.log(`${ok ? '✅' : '❌'} filter pill mobile:`, JSON.stringify(results),
|
|
88
|
+
`\n mobile=`, mobile,
|
|
89
|
+
`\n desktop=`, desktop);
|
|
90
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/* Round 187 verification: FreshnessChip color transition.
|
|
2
|
+
*
|
|
3
|
+
* Pre-R187 the chip swapped between fresh (gray) and stale
|
|
4
|
+
* (amber) Tailwind classes — bg/text/border snapped every
|
|
5
|
+
* time the >10s boundary was crossed. R187 adds
|
|
6
|
+
* transition-colors duration-300 so the palette eases
|
|
7
|
+
* smoothly through the stale-onset and recovery.
|
|
8
|
+
*
|
|
9
|
+
* Test:
|
|
10
|
+
* 1. Mount: data-freshness-chip-stale='false' (just synced)
|
|
11
|
+
* 2. className includes transition-colors + duration-300
|
|
12
|
+
* 3. className includes gray fresh palette
|
|
13
|
+
* 4. computed transition string includes background-color,
|
|
14
|
+
* border-*, color properties
|
|
15
|
+
*/
|
|
16
|
+
import { chromium } from 'playwright';
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
|
|
19
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
20
|
+
const browser = await chromium.launch({ headless: true });
|
|
21
|
+
|
|
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
|
+
|
|
28
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
29
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
30
|
+
const r = await route.fetch();
|
|
31
|
+
const b = await r.json();
|
|
32
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
33
|
+
const mk = (alias) => ({
|
|
34
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
35
|
+
network_id: nid, project_dir: null,
|
|
36
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
37
|
+
});
|
|
38
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta')] } });
|
|
39
|
+
});
|
|
40
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
41
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.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.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
|
|
46
|
+
await page.waitForSelector('[data-freshness-chip]', { timeout: 10000 });
|
|
47
|
+
await page.waitForTimeout(400);
|
|
48
|
+
|
|
49
|
+
const probe = await page.evaluate(() => {
|
|
50
|
+
const chip = document.querySelector('[data-freshness-chip]');
|
|
51
|
+
if (!chip) return null;
|
|
52
|
+
const computed = getComputedStyle(chip);
|
|
53
|
+
return {
|
|
54
|
+
staleAttr: chip.getAttribute('data-freshness-chip-stale'),
|
|
55
|
+
className: chip.getAttribute('class') || '',
|
|
56
|
+
transition: computed.transitionProperty + ' / ' + computed.transitionDuration,
|
|
57
|
+
text: chip.textContent,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await browser.close();
|
|
62
|
+
|
|
63
|
+
const results = {
|
|
64
|
+
chip_found: probe !== null,
|
|
65
|
+
initial_stale_false: probe?.staleAttr === 'false',
|
|
66
|
+
has_transition_colors: probe?.className.includes('transition-colors'),
|
|
67
|
+
has_duration_300: probe?.className.includes('duration-300'),
|
|
68
|
+
has_fresh_palette_bg: probe?.className.includes('bg-gray-500/10'),
|
|
69
|
+
has_fresh_palette_text: probe?.className.includes('text-gray-400'),
|
|
70
|
+
has_fresh_palette_border: probe?.className.includes('border-gray-500/20'),
|
|
71
|
+
no_stale_palette: !probe?.className.includes('bg-amber-500/10'),
|
|
72
|
+
transition_covers_bg: (probe?.transition || '').includes('background-color'),
|
|
73
|
+
transition_covers_border: (probe?.transition || '').includes('border'),
|
|
74
|
+
transition_covers_color: (probe?.transition || '').match(/(^|[\s,/])color\b/),
|
|
75
|
+
text_shows_live: /^live ·/.test(probe?.text || ''),
|
|
76
|
+
};
|
|
77
|
+
const ok = Object.values(results).every(Boolean);
|
|
78
|
+
console.log(`${ok ? '✅' : '❌'} freshness chip transition:`, JSON.stringify(Object.fromEntries(Object.entries(results).map(([k,v]) => [k, !!v]))),
|
|
79
|
+
`\n probe:`, probe);
|
|
80
|
+
process.exit(ok ? 0 : 1);
|