@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,82 @@
|
|
|
1
|
+
/* P0 (Vincent 5222) baseline + after screenshot of TopoGraph with a
|
|
2
|
+
* realistic 4-agent mixed-vendor fleet. Usage:
|
|
3
|
+
* node scripts/p0-topo-screenshot.mjs <out-prefix>
|
|
4
|
+
* Writes <out-prefix>-full.png (1600×900) and <out-prefix>-svg.png
|
|
5
|
+
* (just the SVG bbox, cropped). */
|
|
6
|
+
import { chromium } from 'playwright';
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
|
|
9
|
+
const outPrefix = process.argv[2] || 'screenshots/r293-p0/baseline';
|
|
10
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
11
|
+
const browser = await chromium.launch({ headless: true });
|
|
12
|
+
const ctx = await browser.newContext({ viewport: { width: 1600, height: 1400 }, deviceScaleFactor: 2 });
|
|
13
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
14
|
+
await ctx.addInitScript(() => {
|
|
15
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
16
|
+
});
|
|
17
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
18
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
19
|
+
const r = await route.fetch();
|
|
20
|
+
const b = await r.json();
|
|
21
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
22
|
+
const mk = (alias, model, status = 'working') => ({
|
|
23
|
+
alias, status, model, runtime: 'claude-code-cli',
|
|
24
|
+
network_id: nid, project_dir: null,
|
|
25
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
26
|
+
});
|
|
27
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
28
|
+
mk('claude-1', 'claude-opus-4'),
|
|
29
|
+
mk('claude-2', 'claude-sonnet-4', 'idle'),
|
|
30
|
+
mk('codex-1', 'gpt-4o'),
|
|
31
|
+
mk('intern-1', 'internlm/internlm2'),
|
|
32
|
+
mk('minimax-1', 'minimax/abab6', 'idle'),
|
|
33
|
+
] } });
|
|
34
|
+
});
|
|
35
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
|
|
36
|
+
{ from: 'claude-1', to: 'codex-1', text: 'review the diff please', ts: fresh },
|
|
37
|
+
{ from: 'codex-1', to: 'claude-1', text: 'lgtm, ship', ts: fresh },
|
|
38
|
+
{ from: 'intern-1', to: 'claude-1', text: 'logs uploaded', ts: fresh },
|
|
39
|
+
] } }));
|
|
40
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
41
|
+
|
|
42
|
+
const page = await ctx.newPage();
|
|
43
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
44
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length >= 4, { timeout: 30000 });
|
|
45
|
+
await page.waitForTimeout(800);
|
|
46
|
+
|
|
47
|
+
// Scroll the topology card into view + measure.
|
|
48
|
+
const topoBox = await page.evaluate(() => {
|
|
49
|
+
const chromeEl = document.querySelector('[data-topo-chrome]');
|
|
50
|
+
if (!chromeEl) return null;
|
|
51
|
+
// Walk up to find the topology card container — has the SVG inside.
|
|
52
|
+
let card = chromeEl.parentElement;
|
|
53
|
+
while (card && !card.querySelector(':scope > svg')) card = card.parentElement;
|
|
54
|
+
if (!card) return null;
|
|
55
|
+
card.scrollIntoView({ block: 'start' });
|
|
56
|
+
// Allow scroll to settle.
|
|
57
|
+
return new Promise(resolve => {
|
|
58
|
+
requestAnimationFrame(() => {
|
|
59
|
+
const r = card.getBoundingClientRect();
|
|
60
|
+
resolve({ x: Math.max(0, r.x), y: Math.max(0, r.y), width: r.width, height: r.height });
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
await page.waitForTimeout(400);
|
|
65
|
+
|
|
66
|
+
await page.screenshot({ path: `${outPrefix}-full.png`, fullPage: false });
|
|
67
|
+
if (topoBox && topoBox.width > 100 && topoBox.height > 100) {
|
|
68
|
+
// Re-measure after settling.
|
|
69
|
+
const box2 = await page.evaluate(() => {
|
|
70
|
+
const chromeEl = document.querySelector('[data-topo-chrome]');
|
|
71
|
+
let card = chromeEl?.parentElement;
|
|
72
|
+
while (card && !card.querySelector(':scope > svg')) card = card?.parentElement;
|
|
73
|
+
if (!card) return null;
|
|
74
|
+
const r = card.getBoundingClientRect();
|
|
75
|
+
return { x: Math.max(0, r.x), y: Math.max(0, r.y), width: r.width, height: r.height };
|
|
76
|
+
});
|
|
77
|
+
if (box2) {
|
|
78
|
+
await page.screenshot({ path: `${outPrefix}-topo.png`, clip: box2 });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
console.log(`✅ wrote ${outPrefix}-full.png + ${outPrefix}-topo.png`);
|
|
82
|
+
await browser.close();
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/* Round 136 verification: the "N active links" chip in the chip
|
|
2
|
+
* row becomes a clickable nav to /messages when flowLinks.length
|
|
3
|
+
* > 0. Closes the cursor-pointer-lying bug — the chip already had
|
|
4
|
+
* cursor:pointer set when interactive, but no onClick handler was
|
|
5
|
+
* wired (line 1877 of TopoGraph.tsx pre-R136). Mirrors R133 footer-
|
|
6
|
+
* nav idiom and generalizes "click to open the full flow list"
|
|
7
|
+
* across the chip row, not just the recent-signal panel.
|
|
8
|
+
*
|
|
9
|
+
* Two-state behavior:
|
|
10
|
+
* - flowLinks.length === 0 → not interactive: no role=link, no
|
|
11
|
+
* cursor:pointer, no onClick fires
|
|
12
|
+
* - flowLinks.length > 0 → role=link, tabIndex=0, cursor:pointer,
|
|
13
|
+
* onClick → /messages, tooltip mentions "click to open"
|
|
14
|
+
*
|
|
15
|
+
* Hover still boosts edges (R77) regardless — that's a separate
|
|
16
|
+
* gesture.
|
|
17
|
+
*/
|
|
18
|
+
import { chromium } from 'playwright';
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
|
|
21
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
22
|
+
|
|
23
|
+
async function probeWithFlows(pairCount) {
|
|
24
|
+
const browser = await chromium.launch({ headless: true });
|
|
25
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
26
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
27
|
+
await ctx.addInitScript(() => {
|
|
28
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
29
|
+
});
|
|
30
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
31
|
+
const aliases = ['alpha', 'beta', 'gamma', 'delta', 'epsilon'];
|
|
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: 'cli-claude-code',
|
|
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: aliases.map(mk) } });
|
|
42
|
+
});
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
const msgs = [];
|
|
45
|
+
for (let i = 0; i < pairCount; i++) {
|
|
46
|
+
msgs.push({
|
|
47
|
+
id: `m${i}`,
|
|
48
|
+
from_alias: aliases[i % aliases.length],
|
|
49
|
+
to_alias: aliases[(i + 1) % aliases.length],
|
|
50
|
+
content: 'hi',
|
|
51
|
+
network_id: 'default',
|
|
52
|
+
created_at: new Date(now - (20000 + i * 1000)).toISOString(),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
|
|
56
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
57
|
+
|
|
58
|
+
const page = await ctx.newPage();
|
|
59
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
60
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 5, { timeout: 30000 });
|
|
61
|
+
await page.waitForSelector('[data-active-links-chip]', { timeout: 10000 });
|
|
62
|
+
await page.waitForTimeout(400);
|
|
63
|
+
|
|
64
|
+
const probe = await page.evaluate(() => {
|
|
65
|
+
const chip = document.querySelector('[data-active-links-chip]');
|
|
66
|
+
return {
|
|
67
|
+
flowCount: chip?.getAttribute('data-active-links-flow-count'),
|
|
68
|
+
clickable: chip?.getAttribute('data-active-links-clickable'),
|
|
69
|
+
role: chip?.getAttribute('role'),
|
|
70
|
+
tabindex: chip?.getAttribute('tabindex'),
|
|
71
|
+
cursor: (chip?.getAttribute('style') || '').includes('cursor: pointer'),
|
|
72
|
+
title: chip?.getAttribute('title'),
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
let finalUrl = null;
|
|
77
|
+
if (pairCount > 0) {
|
|
78
|
+
await page.locator('[data-active-links-chip]').click();
|
|
79
|
+
await page.waitForURL('**/messages', { timeout: 10000 });
|
|
80
|
+
finalUrl = page.url();
|
|
81
|
+
}
|
|
82
|
+
await browser.close();
|
|
83
|
+
return { probe, finalUrl };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const empty = await probeWithFlows(0);
|
|
87
|
+
const oneFlow = await probeWithFlows(1);
|
|
88
|
+
const many = await probeWithFlows(3);
|
|
89
|
+
|
|
90
|
+
const results = {
|
|
91
|
+
// Empty (0 flows) — not interactive
|
|
92
|
+
empty_flowCount0: empty.probe.flowCount === '0',
|
|
93
|
+
empty_clickableFalse: empty.probe.clickable === 'false',
|
|
94
|
+
empty_noRole: empty.probe.role === null || empty.probe.role === undefined,
|
|
95
|
+
empty_noTabIndex: empty.probe.tabindex === null || empty.probe.tabindex === undefined,
|
|
96
|
+
empty_noPointer: !empty.probe.cursor,
|
|
97
|
+
empty_noTooltip: !empty.probe.title,
|
|
98
|
+
|
|
99
|
+
// 1 flow — interactive
|
|
100
|
+
one_flowCount1: oneFlow.probe.flowCount === '1',
|
|
101
|
+
one_clickableTrue: oneFlow.probe.clickable === 'true',
|
|
102
|
+
one_roleLink: oneFlow.probe.role === 'link',
|
|
103
|
+
one_tabIndex0: oneFlow.probe.tabindex === '0',
|
|
104
|
+
one_cursorPointer: oneFlow.probe.cursor,
|
|
105
|
+
one_titleMentionsOpen: (oneFlow.probe.title || '').includes('click to open'),
|
|
106
|
+
one_navigatedToMessages: /\/messages$/.test(oneFlow.finalUrl || ''),
|
|
107
|
+
|
|
108
|
+
// 3 flows — same interactive shape
|
|
109
|
+
many_clickableTrue: many.probe.clickable === 'true',
|
|
110
|
+
many_navigatedToMessages: /\/messages$/.test(many.finalUrl || ''),
|
|
111
|
+
};
|
|
112
|
+
const ok = Object.values(results).every(Boolean);
|
|
113
|
+
console.log(`${ok ? '✅' : '❌'} active-links chip nav:`, JSON.stringify(results),
|
|
114
|
+
`\n empty=`, empty,
|
|
115
|
+
`\n oneFlow=`, oneFlow,
|
|
116
|
+
`\n many=`, many);
|
|
117
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/* Round 193 verification: the active-links chip itself responds to
|
|
2
|
+
* hover by adopting a cyan tint, mirroring the canvas-edge highlight
|
|
3
|
+
* it already fires (which R77's topo-active-links-chip-hover-test
|
|
4
|
+
* verifies). Two different surfaces:
|
|
5
|
+
* R77 test → canvas edges (effect element)
|
|
6
|
+
* R193 test → chip itself (cause element)
|
|
7
|
+
*
|
|
8
|
+
* Pre-R193 the chip stayed gray when hovered while the canvas edges
|
|
9
|
+
* brightened to cyan — cause-element silent, effect-element loud.
|
|
10
|
+
* R193 attaches a Tailwind :hover variant (cyan-500/10 bg, cyan-200
|
|
11
|
+
* text, cyan-500/30 border) when the chip is interactive, plus a
|
|
12
|
+
* transition-colors duration-200 for smooth blend. Non-interactive
|
|
13
|
+
* chips (0 active links) keep the gray-only variant.
|
|
14
|
+
*
|
|
15
|
+
* Test scenarios:
|
|
16
|
+
* A. Interactive (3 flows):
|
|
17
|
+
* 1. Idle: bg near gray-500/10 (R,G,B close to each other)
|
|
18
|
+
* 2. className includes 'hover:bg-cyan-500/10'
|
|
19
|
+
* 3. Hover: bg, text, border all shift cyan (B > R)
|
|
20
|
+
* 4. transition-colors duration-200 present
|
|
21
|
+
* 5. bg actually changed from idle to hover
|
|
22
|
+
* B. Non-interactive (0 flows):
|
|
23
|
+
* 6. className does NOT include 'hover:bg-cyan-500/10'
|
|
24
|
+
* 7. data-active-links-clickable='false'
|
|
25
|
+
*/
|
|
26
|
+
import { chromium } from 'playwright';
|
|
27
|
+
import { readFileSync } from 'node:fs';
|
|
28
|
+
|
|
29
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
30
|
+
const browser = await chromium.launch({ headless: true });
|
|
31
|
+
|
|
32
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
33
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
34
|
+
await ctx.addInitScript(() => {
|
|
35
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
async function load(flowsScenario) {
|
|
39
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
40
|
+
await ctx.unroute('**/api/hub/status*').catch(() => {});
|
|
41
|
+
await ctx.unroute('**/api/hub/messages*').catch(() => {});
|
|
42
|
+
await ctx.unroute('**/api/hub/tasks*').catch(() => {});
|
|
43
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
44
|
+
const r = await route.fetch();
|
|
45
|
+
const b = await r.json();
|
|
46
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
47
|
+
const mk = (alias) => ({
|
|
48
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
49
|
+
network_id: nid, project_dir: null,
|
|
50
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
51
|
+
});
|
|
52
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
53
|
+
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
54
|
+
] } });
|
|
55
|
+
});
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
const msgs = flowsScenario(now);
|
|
58
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
|
|
59
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
60
|
+
|
|
61
|
+
const page = await ctx.newPage();
|
|
62
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
63
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
64
|
+
await page.waitForSelector('[data-active-links-chip]', { timeout: 10000 });
|
|
65
|
+
await page.waitForTimeout(400);
|
|
66
|
+
return page;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Scenario A: 3 flows (interactive)
|
|
70
|
+
const pageA = await load((now) => [
|
|
71
|
+
{ id: '1', from_alias: 'alpha', to_alias: 'beta', content: 'hi',
|
|
72
|
+
network_id: 'default', created_at: new Date(now - 5000).toISOString() },
|
|
73
|
+
{ id: '2', from_alias: 'beta', to_alias: 'gamma', content: 'hi',
|
|
74
|
+
network_id: 'default', created_at: new Date(now - 8000).toISOString() },
|
|
75
|
+
{ id: '3', from_alias: 'gamma', to_alias: 'delta', content: 'hi',
|
|
76
|
+
network_id: 'default', created_at: new Date(now - 11000).toISOString() },
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const idleA = await pageA.evaluate(() => {
|
|
80
|
+
const el = document.querySelector('[data-active-links-chip]');
|
|
81
|
+
const cs = getComputedStyle(el);
|
|
82
|
+
return {
|
|
83
|
+
className: el.getAttribute('class') || '',
|
|
84
|
+
clickable: el.getAttribute('data-active-links-clickable'),
|
|
85
|
+
flowCount: el.getAttribute('data-active-links-flow-count'),
|
|
86
|
+
bg: cs.backgroundColor,
|
|
87
|
+
color: cs.color,
|
|
88
|
+
borderColor:cs.borderTopColor,
|
|
89
|
+
transition: cs.transitionProperty + ' / ' + cs.transitionDuration,
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await pageA.hover('[data-active-links-chip]');
|
|
94
|
+
await pageA.waitForTimeout(280); // longer than 200ms transition
|
|
95
|
+
|
|
96
|
+
const hoverA = await pageA.evaluate(() => {
|
|
97
|
+
const el = document.querySelector('[data-active-links-chip]');
|
|
98
|
+
const cs = getComputedStyle(el);
|
|
99
|
+
return {
|
|
100
|
+
bg: cs.backgroundColor,
|
|
101
|
+
color: cs.color,
|
|
102
|
+
borderColor:cs.borderTopColor,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await pageA.mouse.move(0, 0);
|
|
107
|
+
await pageA.waitForTimeout(50);
|
|
108
|
+
await pageA.close();
|
|
109
|
+
|
|
110
|
+
// Scenario B: 0 flows (non-interactive)
|
|
111
|
+
const pageB = await load(() => []);
|
|
112
|
+
const idleB = await pageB.evaluate(() => {
|
|
113
|
+
const el = document.querySelector('[data-active-links-chip]');
|
|
114
|
+
return {
|
|
115
|
+
className: el.getAttribute('class') || '',
|
|
116
|
+
clickable: el.getAttribute('data-active-links-clickable'),
|
|
117
|
+
flowCount: el.getAttribute('data-active-links-flow-count'),
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
await pageB.close();
|
|
121
|
+
await browser.close();
|
|
122
|
+
|
|
123
|
+
// Tailwind 4 emits modern color spaces (oklab / lab). Cyan in OKLab
|
|
124
|
+
// is characterised by a<0 (green-leaning) AND b<0 (blue-leaning).
|
|
125
|
+
// Gray has a ≈ 0, b ≈ 0. Lab uses similar axes at different scales.
|
|
126
|
+
function parseLab(s) {
|
|
127
|
+
const m = (s || '').match(/(?:oklab|lab)\(\s*([\-\d.]+)\s+([\-\d.]+)\s+([\-\d.]+)/);
|
|
128
|
+
return m ? { L: +m[1], a: +m[2], b: +m[3] } : null;
|
|
129
|
+
}
|
|
130
|
+
const idleBgLab = parseLab(idleA.bg);
|
|
131
|
+
const hoverBgLab = parseLab(hoverA.bg);
|
|
132
|
+
const hoverFgLab = parseLab(hoverA.color);
|
|
133
|
+
const hoverBdLab = parseLab(hoverA.borderColor);
|
|
134
|
+
const idleFgLab = parseLab(idleA.color);
|
|
135
|
+
|
|
136
|
+
// Cyan-leaning thresholds: a more negative by at least 0.05 (OKLab)
|
|
137
|
+
// or 5 (Lab), b similarly. Robust to either color space.
|
|
138
|
+
const isMoreCyan = (idle, hov) =>
|
|
139
|
+
hov && idle && (hov.a < idle.a - 0.04 || hov.b < idle.b - 0.04 || hov.a < -0.05 || hov.b < -0.05);
|
|
140
|
+
|
|
141
|
+
const results = {
|
|
142
|
+
// A1: idle is roughly gray-ish — small a,b deviation from 0
|
|
143
|
+
idleA_gray_bg: idleBgLab && Math.abs(idleBgLab.a) < 0.05 && Math.abs(idleBgLab.b) < 0.05,
|
|
144
|
+
|
|
145
|
+
// A2: className has hover variant
|
|
146
|
+
idleA_has_hover: idleA.className.includes('hover:bg-cyan-500/10'),
|
|
147
|
+
|
|
148
|
+
// A3: hovered surfaces lean cyan vs idle
|
|
149
|
+
hover_bg_cyan: isMoreCyan(idleBgLab, hoverBgLab),
|
|
150
|
+
hover_text_cyan: isMoreCyan(idleFgLab, hoverFgLab),
|
|
151
|
+
hover_border_cyan: isMoreCyan(idleBgLab, hoverBdLab),
|
|
152
|
+
|
|
153
|
+
// bg actually changed
|
|
154
|
+
bg_changed: idleA.bg !== hoverA.bg,
|
|
155
|
+
|
|
156
|
+
// A4: transition-colors duration-200 present
|
|
157
|
+
idleA_has_transition: idleA.transition.includes('color') && idleA.transition.includes('0.2s'),
|
|
158
|
+
idleA_clickable_true: idleA.clickable === 'true',
|
|
159
|
+
idleA_flow_count_3: idleA.flowCount === '3',
|
|
160
|
+
|
|
161
|
+
// B6: non-interactive chip has NO hover variant
|
|
162
|
+
idleB_no_hover: !idleB.className.includes('hover:bg-cyan-500/10'),
|
|
163
|
+
idleB_clickable_false:idleB.clickable === 'false',
|
|
164
|
+
idleB_flow_count_0: idleB.flowCount === '0',
|
|
165
|
+
};
|
|
166
|
+
const ok = Object.values(results).every(Boolean);
|
|
167
|
+
console.log(`${ok ? '✅' : '❌'} active-links chip self-hover:`, JSON.stringify(results),
|
|
168
|
+
`\n idleA:`, idleA,
|
|
169
|
+
`\n hoverA:`, hoverA,
|
|
170
|
+
`\n idleB:`, idleB);
|
|
171
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* Round 206 verification: active-links chip recedes to opacity 0.5
|
|
2
|
+
* when flowLinks.length === 0, mirroring R204 legend count recede and
|
|
3
|
+
* R205 working/online chip recede patterns at a third scope.
|
|
4
|
+
*
|
|
5
|
+
* Pre-R206 "0 active links" rendered at the same bg-gray-500/10 +
|
|
6
|
+
* opacity 1 as "12 active links" — only the absent cursor + missing
|
|
7
|
+
* tooltip signalled empty. R206 adds the visual recede so all three
|
|
8
|
+
* signals (visual / interactive / affordance) align.
|
|
9
|
+
*
|
|
10
|
+
* Test scenarios:
|
|
11
|
+
* A. 0 messages → flowLinks.length === 0:
|
|
12
|
+
* data-active-links-empty='true', clickable='false',
|
|
13
|
+
* opacity ≈ 0.5
|
|
14
|
+
* B. 3 messages → flowLinks.length === 3:
|
|
15
|
+
* data-active-links-empty='false', clickable='true',
|
|
16
|
+
* opacity = 1.0
|
|
17
|
+
* Both: inline transition includes color/background-color/
|
|
18
|
+
* border-color/opacity 200ms (4-property splice).
|
|
19
|
+
* R193 hover-cyan markup preserved in scenario B className.
|
|
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
|
+
|
|
27
|
+
async function setup(messages) {
|
|
28
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
29
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
30
|
+
await ctx.addInitScript(() => {
|
|
31
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
32
|
+
});
|
|
33
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
34
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
35
|
+
const r = await route.fetch();
|
|
36
|
+
const b = await r.json();
|
|
37
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
38
|
+
const mk = (alias) => ({
|
|
39
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
40
|
+
network_id: nid, project_dir: null,
|
|
41
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
42
|
+
});
|
|
43
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
44
|
+
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
45
|
+
] } });
|
|
46
|
+
});
|
|
47
|
+
await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages } }));
|
|
48
|
+
await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
|
|
49
|
+
const page = await ctx.newPage();
|
|
50
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
51
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
52
|
+
await page.waitForSelector('[data-active-links-chip]', { timeout: 10000 });
|
|
53
|
+
await page.waitForTimeout(300);
|
|
54
|
+
return page;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const probe = async (page) => page.evaluate(() => {
|
|
58
|
+
const el = document.querySelector('[data-active-links-chip]');
|
|
59
|
+
if (!el) return null;
|
|
60
|
+
const cs = getComputedStyle(el);
|
|
61
|
+
return {
|
|
62
|
+
className: el.getAttribute('class') || '',
|
|
63
|
+
empty: el.getAttribute('data-active-links-empty'),
|
|
64
|
+
clickable: el.getAttribute('data-active-links-clickable'),
|
|
65
|
+
flowCount: el.getAttribute('data-active-links-flow-count'),
|
|
66
|
+
opacity: parseFloat(cs.opacity),
|
|
67
|
+
transition: el.style.transition,
|
|
68
|
+
bg: cs.backgroundColor,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Scenario A: empty
|
|
73
|
+
const pageA = await setup([]);
|
|
74
|
+
const probeA = await probe(pageA);
|
|
75
|
+
await pageA.close();
|
|
76
|
+
|
|
77
|
+
// Scenario B: populated
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const pageB = await setup([
|
|
80
|
+
{ id: '1', from_alias: 'alpha', to_alias: 'beta', content: 'hi',
|
|
81
|
+
network_id: 'default', created_at: new Date(now - 1000).toISOString() },
|
|
82
|
+
{ id: '2', from_alias: 'beta', to_alias: 'gamma', content: 'hi',
|
|
83
|
+
network_id: 'default', created_at: new Date(now - 2000).toISOString() },
|
|
84
|
+
{ id: '3', from_alias: 'gamma', to_alias: 'delta', content: 'hi',
|
|
85
|
+
network_id: 'default', created_at: new Date(now - 3000).toISOString() },
|
|
86
|
+
]);
|
|
87
|
+
const probeB = await probe(pageB);
|
|
88
|
+
await pageB.close();
|
|
89
|
+
await browser.close();
|
|
90
|
+
|
|
91
|
+
const near = (got, want, tol = 0.05) => Math.abs(got - want) < tol;
|
|
92
|
+
const has = (s, re) => re.test(s || '');
|
|
93
|
+
|
|
94
|
+
const results = {
|
|
95
|
+
// Scenario A: 0 flows
|
|
96
|
+
A_chip_present: !!probeA,
|
|
97
|
+
A_flow_count_0: probeA?.flowCount === '0',
|
|
98
|
+
A_empty_true: probeA?.empty === 'true',
|
|
99
|
+
A_clickable_false: probeA?.clickable === 'false',
|
|
100
|
+
A_opacity_half: near(probeA?.opacity ?? 0, 0.5),
|
|
101
|
+
// hover variant NOT present on empty (only base bg-gray)
|
|
102
|
+
A_no_hover_variant: !(probeA?.className || '').includes('hover:bg-cyan-500/10'),
|
|
103
|
+
|
|
104
|
+
// Scenario B: 3 flows
|
|
105
|
+
B_chip_present: !!probeB,
|
|
106
|
+
B_flow_count_3: probeB?.flowCount === '3',
|
|
107
|
+
B_empty_false: probeB?.empty === 'false',
|
|
108
|
+
B_clickable_true: probeB?.clickable === 'true',
|
|
109
|
+
B_opacity_full: near(probeB?.opacity ?? 0, 1.0),
|
|
110
|
+
// R193 hover-cyan markup preserved
|
|
111
|
+
B_has_hover_variant: (probeB?.className || '').includes('hover:bg-cyan-500/10'),
|
|
112
|
+
|
|
113
|
+
// Transition integrity — 4-property splice on both scenarios
|
|
114
|
+
A_transition_opacity: has(probeA?.transition, /opacity\s+(?:200ms|0\.2s)/),
|
|
115
|
+
A_transition_bg: has(probeA?.transition, /background-color\s+(?:200ms|0\.2s)/),
|
|
116
|
+
A_transition_color: has(probeA?.transition, /\bcolor\s+(?:200ms|0\.2s)/),
|
|
117
|
+
A_transition_border: has(probeA?.transition, /border-color\s+(?:200ms|0\.2s)/),
|
|
118
|
+
B_transition_opacity: has(probeB?.transition, /opacity\s+(?:200ms|0\.2s)/),
|
|
119
|
+
B_transition_bg: has(probeB?.transition, /background-color\s+(?:200ms|0\.2s)/),
|
|
120
|
+
};
|
|
121
|
+
const ok = Object.values(results).every(Boolean);
|
|
122
|
+
console.log(`${ok ? '✅' : '❌'} active-links empty recede:`, JSON.stringify(results),
|
|
123
|
+
`\n probeA (0 flows):`, probeA,
|
|
124
|
+
`\n probeB (3 flows):`, probeB);
|
|
125
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* Round 161 verification: active-links chip bullet picks up
|
|
2
|
+
* the R160 freshness vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* R10 fades canvas flow edges by freshness. R160 brought that
|
|
5
|
+
* vocabulary to the recent-signal panel rows via a 1.6-px cyan
|
|
6
|
+
* pip at row left edge. R161 extends it one more scope — up to
|
|
7
|
+
* the fleet-aggregate active-links chip in the chip-row.
|
|
8
|
+
*
|
|
9
|
+
* The chip text was already "3 active links · last 5s ago" but
|
|
10
|
+
* the " · " separator was dead gray. R161 colors that bullet
|
|
11
|
+
* by freshness using the same alpha ramp:
|
|
12
|
+
* ageSec ≤ 30 → 1.0 (fully fresh)
|
|
13
|
+
* 30-300s → 1.0 → 0.25 smooth decay
|
|
14
|
+
* > 300s → 0.25 stale floor
|
|
15
|
+
*
|
|
16
|
+
* Three nested scopes now share one freshness ladder:
|
|
17
|
+
* canvas edge fade (R10) — per-edge visual
|
|
18
|
+
* row pip (R160) — per-flow row in recent-signal
|
|
19
|
+
* chip bullet (R161) — fleet aggregate in chip-row
|
|
20
|
+
*
|
|
21
|
+
* Test scenarios (one page load each):
|
|
22
|
+
* A. fresh most-recent flow (5s ago) → alpha ≈ 1.0, fontWeight 700
|
|
23
|
+
* B. mid recency (90s ago) → alpha ≈ 0.83
|
|
24
|
+
* C. stale (360s ago) → alpha ≈ 0.25 (floor)
|
|
25
|
+
*
|
|
26
|
+
* Color parses out of `rgba(34, 211, 238, 1)` cyber theme.
|
|
27
|
+
*/
|
|
28
|
+
import { chromium } from 'playwright';
|
|
29
|
+
import { readFileSync } from 'node:fs';
|
|
30
|
+
|
|
31
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
32
|
+
const browser = await chromium.launch({ headless: true });
|
|
33
|
+
|
|
34
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
35
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
36
|
+
await ctx.addInitScript(() => {
|
|
37
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
async function probe(msgAgeSec) {
|
|
41
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
42
|
+
await ctx.unroute('**/api/hub/status*').catch(() => {});
|
|
43
|
+
await ctx.unroute('**/api/hub/messages*').catch(() => {});
|
|
44
|
+
await ctx.unroute('**/api/hub/tasks*').catch(() => {});
|
|
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: 'cli-claude-code',
|
|
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: [mk('alpha'), mk('beta')] } });
|
|
55
|
+
});
|
|
56
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
|
|
57
|
+
{ id: 'x', from_alias: 'alpha', to_alias: 'beta', content: 'hi',
|
|
58
|
+
network_id: 'default', created_at: new Date(Date.now() - msgAgeSec * 1000).toISOString() },
|
|
59
|
+
] } }));
|
|
60
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
61
|
+
|
|
62
|
+
const page = await ctx.newPage();
|
|
63
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
64
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
|
|
65
|
+
await page.waitForSelector('[data-active-links-freshness-dot]', { timeout: 10000 });
|
|
66
|
+
await page.waitForTimeout(400);
|
|
67
|
+
const out = await page.evaluate(() => {
|
|
68
|
+
const el = document.querySelector('[data-active-links-freshness-dot]');
|
|
69
|
+
if (!el) return null;
|
|
70
|
+
const styleColor = el.style.color || getComputedStyle(el).color;
|
|
71
|
+
return {
|
|
72
|
+
alpha: parseFloat(el.getAttribute('data-active-links-freshness-alpha') || ''),
|
|
73
|
+
color: styleColor,
|
|
74
|
+
fontWeight: el.style.fontWeight || getComputedStyle(el).fontWeight,
|
|
75
|
+
text: el.textContent,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
await page.close();
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const A = await probe(5); // fresh
|
|
83
|
+
const B = await probe(90); // mid
|
|
84
|
+
const C = await probe(360); // stale
|
|
85
|
+
|
|
86
|
+
await browser.close();
|
|
87
|
+
|
|
88
|
+
const cyanRgb = (str) => {
|
|
89
|
+
// Inline `style="color: rgba(34, 211, 238, ...)"` — alpha is part of color.
|
|
90
|
+
const m = /rgba?\((\d+),\s*(\d+),\s*(\d+)/.exec(str);
|
|
91
|
+
if (!m) return null;
|
|
92
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const results = {
|
|
96
|
+
fresh_dot_found: A !== null,
|
|
97
|
+
mid_dot_found: B !== null,
|
|
98
|
+
stale_dot_found: C !== null,
|
|
99
|
+
fresh_alpha_1: A && Math.abs(A.alpha - 1.0) < 0.05,
|
|
100
|
+
mid_alpha_window: B && B.alpha > 0.7 && B.alpha < 0.95,
|
|
101
|
+
stale_alpha_floor: C && Math.abs(C.alpha - 0.25) < 0.05,
|
|
102
|
+
fresh_color_cyan: A && JSON.stringify(cyanRgb(A.color)) === '[34,211,238]',
|
|
103
|
+
fresh_weight_bold: A && (A.fontWeight === '700' || A.fontWeight === 'bold'),
|
|
104
|
+
stale_weight_norm: C && (C.fontWeight === '400' || C.fontWeight === 'normal' || C.fontWeight === ''),
|
|
105
|
+
ladder_descends: A && B && C && A.alpha > B.alpha && B.alpha > C.alpha,
|
|
106
|
+
text_is_separator: A && A.text && A.text.trim() === '·',
|
|
107
|
+
};
|
|
108
|
+
const ok = Object.values(results).every(Boolean);
|
|
109
|
+
console.log(`${ok ? '✅' : '❌'} active-links freshness:`, JSON.stringify(results),
|
|
110
|
+
`\n fresh =`, A,
|
|
111
|
+
`\n mid =`, B,
|
|
112
|
+
`\n stale =`, C);
|
|
113
|
+
process.exit(ok ? 0 : 1);
|