@sleep2agi/agent-network-dashboard 0.4.6-preview.33 → 0.4.6-preview.331
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/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +41 -41
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- 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/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/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -1
- 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_057q.ne._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.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_0_d45-d._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.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/0-mpa_947ipeq.js +1 -0
- package/.next/static/chunks/01lmbsd37fybu.js +1 -0
- package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
- package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
- package/.next/static/chunks/05uk96gc~9mni.js +1 -0
- package/.next/static/chunks/07lsi~s-c0wvh.css +2 -0
- package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
- package/.next/static/chunks/0ggj5ummw2a84.js +7 -0
- package/.next/static/chunks/0glmrdj9112tc.js +7 -0
- package/.next/static/chunks/0mpa-fgyi2k78.js +1 -0
- package/.next/static/chunks/0nlhul7k4hwil.js +1 -0
- package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
- package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
- package/.next/static/chunks/174re5ar~kxne.js +1 -0
- package/.next/static/chunks/1851sbdemt00t.js +4 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/ChatPopover.tsx +38 -2
- package/app/components/CommandPalette.tsx +107 -0
- package/app/components/HelpOverlay.tsx +111 -1
- package/app/components/TopoGraph.tsx +6197 -262
- package/app/components/utils.ts +8 -7
- package/app/globals.css +110 -5
- package/app/lib/time.ts +36 -0
- package/app/lib/vendorIdentity.ts +14 -3
- 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/v0.10.0-hero3-rfc.md +230 -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-chip-test.mjs +73 -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-auto-fit-test.mjs +73 -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-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-semantic-gap-test.mjs +97 -0
- package/scripts/topo-chip-row-tabular-test.mjs +111 -0
- package/scripts/topo-chiprow-data-weight-test.mjs +94 -0
- package/scripts/topo-chrome-a11y-test.mjs +107 -0
- package/scripts/topo-chrome-focus-ring-unify-test.mjs +100 -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-cwd-tooltip-test.mjs +64 -0
- package/scripts/topo-dblclick-reset-test.mjs +74 -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 +93 -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-edge-tooltip-test.mjs +70 -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-test.mjs +110 -0
- package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
- package/scripts/topo-fit-key-test.mjs +106 -0
- package/scripts/topo-freshness-chip-test.mjs +74 -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 +100 -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-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 +93 -0
- 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 +119 -0
- package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
- package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
- package/scripts/topo-layout-key-test.mjs +91 -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-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-rect-stroke-test.mjs +94 -0
- package/scripts/topo-minimap-test.mjs +68 -0
- package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
- package/scripts/topo-mobile-chips-test.mjs +103 -0
- package/scripts/topo-monogram-font-test.mjs +112 -0
- package/scripts/topo-monogram-stroke-test.mjs +101 -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-overlap-test.mjs +32 -5
- 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-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-popover-meta-test.mjs +78 -0
- package/scripts/topo-prepublish-guard-test.mjs +95 -0
- package/scripts/topo-pressure-bar-test.mjs +69 -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-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-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-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-recent-signal-empty-test.mjs +63 -0
- package/scripts/topo-reduced-motion-test.mjs +77 -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-tz-ghost-test.mjs +69 -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-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-shadow-theme-ease-test.mjs +89 -0
- package/.next/static/chunks/0-7iowzu4.798.js +0 -1
- package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
- package/.next/static/chunks/09nd.t_hd.y6t.css +0 -2
- package/.next/static/chunks/0_8snedeata9k.js +0 -1
- package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
- package/.next/static/chunks/0elalb9n_.~di.js +0 -1
- package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
- package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
- package/.next/static/chunks/0kwf..oq7.27x.js +0 -7
- package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
- package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
- package/.next/static/chunks/0sjqjenh7~k-j.js +0 -1
- package/.next/static/chunks/0y8v4grez.a76.js +0 -1
- package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
- package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
- /package/.next/static/{Q3_3F7glo-M3CFrXA8POE → 3PMYuA9TBRow9F7QeNZc7}/_buildManifest.js +0 -0
- /package/.next/static/{Q3_3F7glo-M3CFrXA8POE → 3PMYuA9TBRow9F7QeNZc7}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{Q3_3F7glo-M3CFrXA8POE → 3PMYuA9TBRow9F7QeNZc7}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Round 285 verification: section kicker letter-spacing widens
|
|
2
|
+
* tracking-wider (0.05em) → tracking-widest (0.1em).
|
|
3
|
+
*
|
|
4
|
+
* Tailwind v4 utility class compiles to:
|
|
5
|
+
* tracking-wider → letter-spacing: 0.05em
|
|
6
|
+
* tracking-widest → letter-spacing: 0.1em
|
|
7
|
+
*
|
|
8
|
+
* At text-xs (12px) the 0.05em→0.1em jump adds ~0.6px between
|
|
9
|
+
* letters, which on an uppercase 'NETWORK TOPOLOGY' label
|
|
10
|
+
* telegraphs "eyebrow label" cleanly vs body-text density.
|
|
11
|
+
*
|
|
12
|
+
* Contract:
|
|
13
|
+
* - [data-topo-section-kicker] computed letter-spacing ~ 0.1em
|
|
14
|
+
* (12 * 0.1 = 1.2px ± rounding).
|
|
15
|
+
* - Pre-R285 was 0.05em → ~0.6px.
|
|
16
|
+
* - Element still text-transform: uppercase, font-size: 12px.
|
|
17
|
+
* - data-topo-section-title sibling unchanged (regression).
|
|
18
|
+
* - R283 monogram stroke + R282 watermark intact (regression).
|
|
19
|
+
*/
|
|
20
|
+
import { chromium } from 'playwright';
|
|
21
|
+
import { readFileSync } from 'node:fs';
|
|
22
|
+
|
|
23
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
24
|
+
const browser = await chromium.launch({ headless: true });
|
|
25
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, 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
|
+
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 mk = (alias, model) => ({
|
|
36
|
+
alias, status: 'working', model, runtime: 'claude-code-cli',
|
|
37
|
+
network_id: nid, project_dir: null,
|
|
38
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
39
|
+
});
|
|
40
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
41
|
+
mk('alpha', 'claude-opus-4'),
|
|
42
|
+
mk('beta', 'gpt-4o'),
|
|
43
|
+
] } });
|
|
44
|
+
});
|
|
45
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
46
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
47
|
+
|
|
48
|
+
const page = await ctx.newPage();
|
|
49
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
50
|
+
await page.waitForSelector('[data-topo-section-kicker]', { timeout: 15000 });
|
|
51
|
+
await page.waitForSelector('[data-topo-brand-watermark]', { timeout: 10000 });
|
|
52
|
+
await page.waitForTimeout(300);
|
|
53
|
+
|
|
54
|
+
const probe = await page.evaluate(() => {
|
|
55
|
+
const kicker = document.querySelector('[data-topo-section-kicker]');
|
|
56
|
+
const title = document.querySelector('[data-topo-section-title]');
|
|
57
|
+
const kCs = kicker ? getComputedStyle(kicker) : null;
|
|
58
|
+
const tCs = title ? getComputedStyle(title) : null;
|
|
59
|
+
// Probe alpha avatar circle stroke for R283 regression.
|
|
60
|
+
const alphaNode = document.querySelector('g[data-node="alpha"]');
|
|
61
|
+
const alphaCircles = alphaNode ? [...alphaNode.querySelectorAll('circle')] : [];
|
|
62
|
+
const alphaAvatarCircle = alphaCircles
|
|
63
|
+
.map(c => ({ r: +c.getAttribute('r') || 0, sw: c.getAttribute('stroke-width'), isBadge: c.hasAttribute('data-runtime-badge') }))
|
|
64
|
+
.filter(c => c.r > 0 && c.r < 18 && !c.isBadge)
|
|
65
|
+
.sort((a, b) => b.r - a.r)[0];
|
|
66
|
+
const watermark = document.querySelector('[data-topo-brand-watermark]');
|
|
67
|
+
return {
|
|
68
|
+
kickerLetterSpacing: kCs?.letterSpacing ?? null,
|
|
69
|
+
kickerFontSize: kCs?.fontSize ?? null,
|
|
70
|
+
kickerTextTransform: kCs?.textTransform ?? null,
|
|
71
|
+
kickerText: kicker?.textContent ?? null,
|
|
72
|
+
titleText: title?.textContent ?? null,
|
|
73
|
+
titleLeading: tCs?.lineHeight ?? null,
|
|
74
|
+
alphaAvatarStroke: alphaAvatarCircle?.sw ?? null,
|
|
75
|
+
watermarkPresent: watermark !== null,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
await browser.close();
|
|
79
|
+
|
|
80
|
+
// 12px * 0.1em = 1.2px (post-R285). Pre-R285 0.05em = 0.6px.
|
|
81
|
+
// Allow tolerance window 1.0–1.4 to be safe across rounding.
|
|
82
|
+
const ls = parseFloat(probe.kickerLetterSpacing) || 0;
|
|
83
|
+
const fs = parseFloat(probe.kickerFontSize) || 12;
|
|
84
|
+
const lsPerEm = fs ? ls / fs : 0;
|
|
85
|
+
|
|
86
|
+
const results = {
|
|
87
|
+
kicker_present: probe.kickerText === 'Network Topology',
|
|
88
|
+
kicker_uppercase: probe.kickerTextTransform === 'uppercase',
|
|
89
|
+
kicker_widest_ls: ls >= 1.0 && ls <= 1.5, // 0.1em at 12px ≈ 1.2px
|
|
90
|
+
kicker_ls_per_em_widest: lsPerEm >= 0.08 && lsPerEm <= 0.12,
|
|
91
|
+
title_still_present: probe.titleText === 'Command mesh',
|
|
92
|
+
r283_monogram_stroke_kept: probe.alphaAvatarStroke === '1.5',
|
|
93
|
+
r282_watermark_present: probe.watermarkPresent,
|
|
94
|
+
};
|
|
95
|
+
const ok = Object.values(results).every(Boolean);
|
|
96
|
+
console.log(`${ok ? '✅' : '❌'} kicker tracking:`, JSON.stringify(results),
|
|
97
|
+
'\n kicker text:', JSON.stringify(probe.kickerText),
|
|
98
|
+
'\n letter-spacing:', probe.kickerLetterSpacing, `(=${lsPerEm.toFixed(3)}em)`,
|
|
99
|
+
'\n font-size:', probe.kickerFontSize,
|
|
100
|
+
'\n title text:', JSON.stringify(probe.titleText));
|
|
101
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* Round 300 (milestone) verification: kicker font-weight bumps from
|
|
2
|
+
* default (400/normal) → font-medium (500). Uppercase eyebrow at
|
|
3
|
+
* text-xs with R285 tracking-widest reads slightly under-authored
|
|
4
|
+
* at 400; 500 lifts the label to the conventional SaaS-eyebrow
|
|
5
|
+
* weight (Stripe / Vercel / Linear).
|
|
6
|
+
*
|
|
7
|
+
* Hierarchy preserved: h2 (600 font-semibold) > kicker (500 font-
|
|
8
|
+
* medium) > body weight families.
|
|
9
|
+
*
|
|
10
|
+
* Contract:
|
|
11
|
+
* - [data-topo-section-kicker] computed font-weight === 500.
|
|
12
|
+
* - R296 kicker color (~gray-500) preserved.
|
|
13
|
+
* - R285 tracking-widest (~1.2px @ 12px) preserved.
|
|
14
|
+
* - R299 title-block mb=16px + R298 left flex gap=10px preserved.
|
|
15
|
+
* - R297 brand-logo transition + R294 pulse absent intact.
|
|
16
|
+
*/
|
|
17
|
+
import { chromium } from 'playwright';
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
19
|
+
|
|
20
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
21
|
+
const browser = await chromium.launch({ headless: true });
|
|
22
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
23
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
24
|
+
await ctx.addInitScript(() => {
|
|
25
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
26
|
+
});
|
|
27
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
28
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
29
|
+
const r = await route.fetch();
|
|
30
|
+
const b = await r.json();
|
|
31
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
32
|
+
const mk = (alias, model) => ({
|
|
33
|
+
alias, status: 'working', model, runtime: 'claude-code-cli',
|
|
34
|
+
network_id: nid, project_dir: null,
|
|
35
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
36
|
+
});
|
|
37
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
38
|
+
mk('alpha', 'claude-opus-4'),
|
|
39
|
+
mk('beta', 'gpt-4o'),
|
|
40
|
+
] } });
|
|
41
|
+
});
|
|
42
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
43
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
44
|
+
|
|
45
|
+
const page = await ctx.newPage();
|
|
46
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
47
|
+
await page.waitForSelector('[data-topo-section-kicker]', { timeout: 15000 });
|
|
48
|
+
await page.waitForTimeout(300);
|
|
49
|
+
|
|
50
|
+
const probe = await page.evaluate(() => {
|
|
51
|
+
const kicker = document.querySelector('[data-topo-section-kicker]');
|
|
52
|
+
const title = document.querySelector('[data-topo-section-title]');
|
|
53
|
+
const logo = document.querySelector('[data-topo-brand-logo]');
|
|
54
|
+
const kCs = kicker ? getComputedStyle(kicker) : null;
|
|
55
|
+
const tCs = title ? getComputedStyle(title) : null;
|
|
56
|
+
const flexParent = logo?.parentElement;
|
|
57
|
+
const flexCs = flexParent ? getComputedStyle(flexParent) : null;
|
|
58
|
+
const titleBlockContainer = flexParent?.parentElement;
|
|
59
|
+
const tbCs = titleBlockContainer ? getComputedStyle(titleBlockContainer) : null;
|
|
60
|
+
const swatch = document.querySelector('[data-legend-swatch="working"]');
|
|
61
|
+
return {
|
|
62
|
+
kickerFontWeight: kCs?.fontWeight ?? null,
|
|
63
|
+
kickerColor: kCs?.color ?? null,
|
|
64
|
+
kickerLetterSpacing: kCs?.letterSpacing ?? null,
|
|
65
|
+
titleFontWeight: tCs?.fontWeight ?? null,
|
|
66
|
+
titleBlockMarginBottom: tbCs?.marginBottom ?? null,
|
|
67
|
+
leftFlexGap: flexCs?.columnGap ?? null,
|
|
68
|
+
brandLogoTransition: logo ? getComputedStyle(logo).transition : null,
|
|
69
|
+
swatchR: swatch?.getAttribute('r') ?? null,
|
|
70
|
+
pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
await browser.close();
|
|
74
|
+
|
|
75
|
+
const kFw = parseInt(probe.kickerFontWeight) || 0;
|
|
76
|
+
const tFw = parseInt(probe.titleFontWeight) || 0;
|
|
77
|
+
const ls = parseFloat(probe.kickerLetterSpacing) || 0;
|
|
78
|
+
const mb = parseFloat(probe.titleBlockMarginBottom) || 0;
|
|
79
|
+
const gap = parseFloat(probe.leftFlexGap) || 0;
|
|
80
|
+
|
|
81
|
+
const results = {
|
|
82
|
+
kicker_weight_500: kFw === 500,
|
|
83
|
+
title_weight_higher: tFw > kFw,
|
|
84
|
+
r285_tracking_widest: ls >= 1.0 && ls <= 1.5,
|
|
85
|
+
r296_kicker_color_not_gray600: !/rgba?\(\s*75\s*,\s*85\s*,\s*99/.test(probe.kickerColor || ''),
|
|
86
|
+
r299_mb_16: mb >= 15.5 && mb <= 16.5,
|
|
87
|
+
r298_gap_10: gap >= 9.5 && gap <= 10.5,
|
|
88
|
+
r297_transition_kept: /color/.test(probe.brandLogoTransition || ''),
|
|
89
|
+
r295_swatch_r_6: probe.swatchR === '6',
|
|
90
|
+
r294_pulse_absent: probe.pulseCount === 0,
|
|
91
|
+
};
|
|
92
|
+
const ok = Object.values(results).every(Boolean);
|
|
93
|
+
console.log(`${ok ? '✅' : '❌'} kicker weight (R300 milestone):`, JSON.stringify(results),
|
|
94
|
+
'\n kicker font-weight:', probe.kickerFontWeight,
|
|
95
|
+
'\n title font-weight:', probe.titleFontWeight,
|
|
96
|
+
'\n letter-spacing:', probe.kickerLetterSpacing,
|
|
97
|
+
'\n title-block mb (R299):', probe.titleBlockMarginBottom);
|
|
98
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* Round 246 verification: node label card chrome rect picks up
|
|
2
|
+
* fill + opacity transitions to coordinate with the existing
|
|
3
|
+
* filter (R142) + stroke (R217) easing on the same element.
|
|
4
|
+
*
|
|
5
|
+
* Pre-R246 the rect transition list was 'filter 220ms ease-out,
|
|
6
|
+
* stroke 220ms ease-out'. fill (pal.labelBox.fill: cyber dark ↔
|
|
7
|
+
* light white) and opacity (cyber 0.94, light 1) snapped on theme
|
|
8
|
+
* toggle. Surrounding elements already ease theme:
|
|
9
|
+
* R211 alias/sub text fill (same card)
|
|
10
|
+
* R242 chat-target ring filter + stroke
|
|
11
|
+
* R244 hub + halo opacity
|
|
12
|
+
* R245 edge stroke
|
|
13
|
+
*
|
|
14
|
+
* R246 closes the label-card chrome — adds fill + opacity to the
|
|
15
|
+
* existing transition list so the whole card (background + text)
|
|
16
|
+
* eases as one unit through theme switches and any future
|
|
17
|
+
* fill/opacity state changes.
|
|
18
|
+
*
|
|
19
|
+
* Test scope per visible label card:
|
|
20
|
+
* - element present at [data-node-label-card]
|
|
21
|
+
* - style.transition contains filter, stroke, fill, AND opacity
|
|
22
|
+
* each at 220ms (or 0.22s browser-normalised)
|
|
23
|
+
*/
|
|
24
|
+
import { chromium } from 'playwright';
|
|
25
|
+
import { readFileSync } from 'node:fs';
|
|
26
|
+
|
|
27
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
28
|
+
const browser = await chromium.launch({ headless: true });
|
|
29
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
30
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
31
|
+
await ctx.addInitScript(() => {
|
|
32
|
+
try {
|
|
33
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
34
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
35
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
36
|
+
} catch {}
|
|
37
|
+
});
|
|
38
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
39
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
40
|
+
const r = await route.fetch();
|
|
41
|
+
const b = await r.json();
|
|
42
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
43
|
+
const mk = (alias) => ({
|
|
44
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
45
|
+
network_id: nid, project_dir: null,
|
|
46
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
47
|
+
});
|
|
48
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
49
|
+
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
50
|
+
] } });
|
|
51
|
+
});
|
|
52
|
+
await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
|
|
53
|
+
await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
|
|
54
|
+
|
|
55
|
+
const page = await ctx.newPage();
|
|
56
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
57
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
58
|
+
await page.waitForSelector('[data-node-label-card]', { timeout: 10000, state: 'attached' });
|
|
59
|
+
await page.waitForTimeout(300);
|
|
60
|
+
|
|
61
|
+
const probe = await page.evaluate(() => {
|
|
62
|
+
const cards = Array.from(document.querySelectorAll('[data-node-label-card]'));
|
|
63
|
+
return cards.map((r) => ({
|
|
64
|
+
alias: r.getAttribute('data-node-label-card'),
|
|
65
|
+
elevation: r.getAttribute('data-node-label-card-elevation'),
|
|
66
|
+
transition: r.style.transition,
|
|
67
|
+
}));
|
|
68
|
+
});
|
|
69
|
+
await browser.close();
|
|
70
|
+
|
|
71
|
+
const hasProp = (s, prop) => new RegExp(`${prop}\\s+(?:220ms|0\\.22s)`).test(s || '');
|
|
72
|
+
|
|
73
|
+
const results = {
|
|
74
|
+
four_cards: probe.length === 4,
|
|
75
|
+
all_have_alias: probe.every(p => typeof p.alias === 'string'),
|
|
76
|
+
all_idle_elevation: probe.every(p => p.elevation === 'idle'),
|
|
77
|
+
all_have_filter_220: probe.every(p => hasProp(p.transition, 'filter')),
|
|
78
|
+
all_have_stroke_220: probe.every(p => hasProp(p.transition, 'stroke')),
|
|
79
|
+
all_have_fill_220: probe.every(p => hasProp(p.transition, 'fill')),
|
|
80
|
+
all_have_opacity_220: probe.every(p => hasProp(p.transition, 'opacity')),
|
|
81
|
+
};
|
|
82
|
+
const ok = Object.values(results).every(Boolean);
|
|
83
|
+
console.log(`${ok ? '✅' : '❌'} label card theme ease:`, JSON.stringify(results),
|
|
84
|
+
'\n cards:', probe.map(p => ({ a: p.alias, e: p.elevation, t: (p.transition || '').slice(0, 100) })));
|
|
85
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* Round 26 verification: hovering a node lifts its label group up by
|
|
2
|
+
* 1.5 px (CSS transform stacked on the SVG positioning translate). Both
|
|
3
|
+
* the full-card path (sparse fleet) and the dense plain-text path
|
|
4
|
+
* (large fleet) should respond. */
|
|
5
|
+
import { chromium } from 'playwright';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
|
|
8
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
9
|
+
const browser = await chromium.launch({ headless: true });
|
|
10
|
+
|
|
11
|
+
async function probe(fleetSize) {
|
|
12
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
|
|
13
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
14
|
+
await ctx.addInitScript(() => {
|
|
15
|
+
try {
|
|
16
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
17
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
18
|
+
localStorage.setItem('anet-topo-nodescale', '1');
|
|
19
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
20
|
+
} catch {}
|
|
21
|
+
});
|
|
22
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
23
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
24
|
+
const r = await route.fetch();
|
|
25
|
+
const b = await r.json();
|
|
26
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
27
|
+
const sessions = Array.from({ length: fleetSize }, (_, i) => ({
|
|
28
|
+
alias: `lab${i + 1}`, status: 'idle', network_id: nid, project_dir: null,
|
|
29
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
30
|
+
}));
|
|
31
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
32
|
+
});
|
|
33
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
34
|
+
const page = await ctx.newPage();
|
|
35
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
36
|
+
await page.waitForFunction((n) => {
|
|
37
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
38
|
+
return !!svg && svg.querySelectorAll('g[data-node]').length === n;
|
|
39
|
+
}, fleetSize, { timeout: 30000 }).catch(() => {});
|
|
40
|
+
await page.waitForTimeout(500);
|
|
41
|
+
|
|
42
|
+
// Re-query the label element fresh each sample — React re-renders on
|
|
43
|
+
// hover (hoveredAlias state change) so any element handle captured
|
|
44
|
+
// before the hover would be detached/stale by the time we read its
|
|
45
|
+
// post-hover position.
|
|
46
|
+
const readLabelTop = () => page.evaluate(() => {
|
|
47
|
+
const node = document.querySelector('g[data-node="lab1"]');
|
|
48
|
+
if (!node) return null;
|
|
49
|
+
const full = [...node.querySelectorAll('g')].find(g => /group-hover:-translate-y/.test(g.getAttribute('class') || ''));
|
|
50
|
+
const el = full || [...node.querySelectorAll('text')].find(t => /group-hover:-translate-y/.test(t.getAttribute('class') || ''));
|
|
51
|
+
return el ? el.getBoundingClientRect().top : null;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const beforeY = await readLabelTop();
|
|
55
|
+
await page.locator('g[data-node="lab1"]').hover();
|
|
56
|
+
await page.waitForTimeout(300);
|
|
57
|
+
const afterY = await readLabelTop();
|
|
58
|
+
|
|
59
|
+
await ctx.close();
|
|
60
|
+
return { beforeY, afterY };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const sparse = await probe(4);
|
|
64
|
+
// Note: in dense mode the same alias's hover triggers `showFullLabel`,
|
|
65
|
+
// which swaps the plain-text element for the full-card <g>. That path
|
|
66
|
+
// switch makes a same-element before/after comparison meaningless.
|
|
67
|
+
// The plain-text element still carries the same hover-lift class, so
|
|
68
|
+
// the lift wiring is verified at the DOM level via the second probe.
|
|
69
|
+
const denseClassPresent = await (async () => {
|
|
70
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
|
|
71
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
72
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
73
|
+
await ctx.addInitScript(() => {
|
|
74
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
75
|
+
localStorage.setItem('anet-topo-nodescale', '1');
|
|
76
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
77
|
+
});
|
|
78
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
79
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
80
|
+
const r = await route.fetch();
|
|
81
|
+
const b = await r.json();
|
|
82
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
83
|
+
const sessions = Array.from({ length: 28 }, (_, i) => ({
|
|
84
|
+
alias: `lab${i + 1}`, status: 'idle', network_id: nid, project_dir: null,
|
|
85
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
86
|
+
}));
|
|
87
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
88
|
+
});
|
|
89
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
90
|
+
const page = await ctx.newPage();
|
|
91
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
92
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 28, { timeout: 30000 }).catch(() => {});
|
|
93
|
+
await page.waitForTimeout(500);
|
|
94
|
+
const has = await page.evaluate(() => {
|
|
95
|
+
const node = document.querySelector('g[data-node="lab2"]'); // not the one we'd hover
|
|
96
|
+
if (!node) return false;
|
|
97
|
+
const t = [...node.querySelectorAll('text')].find(x => /group-hover:-translate-y/.test(x.getAttribute('class') || ''));
|
|
98
|
+
return !!t;
|
|
99
|
+
});
|
|
100
|
+
await ctx.close();
|
|
101
|
+
return has;
|
|
102
|
+
})();
|
|
103
|
+
|
|
104
|
+
await browser.close();
|
|
105
|
+
|
|
106
|
+
// R26 contributes label-only -1.5px lift; R51 (preview.58) adds -2px on the
|
|
107
|
+
// parent <g data-node> on hover, so the label's total measured movement
|
|
108
|
+
// when the node is hovered is ~3.5px. Widen the upper bound to 4 so the
|
|
109
|
+
// two lifts compose without flagging this as a regression.
|
|
110
|
+
const lifted = (m) => m.beforeY != null && m.afterY != null && m.beforeY - m.afterY >= 1 && m.beforeY - m.afterY <= 4;
|
|
111
|
+
|
|
112
|
+
const results = {
|
|
113
|
+
sparseLabelLifts: lifted(sparse),
|
|
114
|
+
densePlainTextHasLiftClass: denseClassPresent,
|
|
115
|
+
};
|
|
116
|
+
const ok = Object.values(results).every(Boolean);
|
|
117
|
+
console.log(`${ok ? '✅' : '❌'} label hover lift:`, JSON.stringify(results),
|
|
118
|
+
`sparse=Δ${(sparse.beforeY - sparse.afterY)?.toFixed(2)}px`);
|
|
119
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* Round 34 verification: offline node tooltip carries "last seen: <relative>";
|
|
2
|
+
* online nodes skip the line. SQL-style timestamp parsing is exercised in
|
|
3
|
+
* production (CommHub format) but the existing isGhost filter has a
|
|
4
|
+
* separate TZ-sensitivity issue that ghosts SQL-without-Z entries on
|
|
5
|
+
* non-UTC browsers — out of Round 34 scope. The helper supports both
|
|
6
|
+
* formats; the SQL path is covered by manual inspection. */
|
|
7
|
+
import { chromium } from 'playwright';
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
|
|
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: 1280, height: 900 } });
|
|
13
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
14
|
+
await ctx.addInitScript(() => {
|
|
15
|
+
try {
|
|
16
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
17
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
18
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
19
|
+
} catch {}
|
|
20
|
+
});
|
|
21
|
+
// One online, two offline at different ages (both ISO).
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const isoMin = (m) => new Date(now - m * 60 * 1000).toISOString();
|
|
24
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
25
|
+
const r = await route.fetch();
|
|
26
|
+
const b = await r.json();
|
|
27
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
28
|
+
const fresh = isoMin(0.1);
|
|
29
|
+
const sessions = [
|
|
30
|
+
{ alias: 'live', status: 'idle', network_id: nid, project_dir: null,
|
|
31
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
32
|
+
{ alias: 'off6m', status: 'offline', network_id: nid, project_dir: null,
|
|
33
|
+
created_at: fresh, updated_at: fresh, last_seen_at: isoMin(6) },
|
|
34
|
+
{ alias: 'off45m', status: 'offline', network_id: nid, project_dir: null,
|
|
35
|
+
created_at: fresh, updated_at: fresh, last_seen_at: isoMin(45) },
|
|
36
|
+
];
|
|
37
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
38
|
+
});
|
|
39
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
40
|
+
const page = await ctx.newPage();
|
|
41
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
42
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
|
|
43
|
+
await page.waitForTimeout(400);
|
|
44
|
+
|
|
45
|
+
const titleOf = alias => page.$eval(`g[data-node="${alias}"] title`, el => el.textContent);
|
|
46
|
+
const live = await titleOf('live');
|
|
47
|
+
const off6m = await titleOf('off6m');
|
|
48
|
+
const off45m = await titleOf('off45m');
|
|
49
|
+
|
|
50
|
+
await browser.close();
|
|
51
|
+
const results = {
|
|
52
|
+
liveSkipsLastSeen: !/last seen:/.test(live),
|
|
53
|
+
off6mShows6mAgo: /last seen: [56]m ago/.test(off6m), // 6m exactly, ±1 minute slack
|
|
54
|
+
off45mShows45mAgo: /last seen: 4[45]m ago/.test(off45m), // 45m exactly, ±1 minute slack
|
|
55
|
+
};
|
|
56
|
+
const ok = Object.values(results).every(Boolean);
|
|
57
|
+
console.log(`${ok ? '✅' : '❌'} last-seen tooltip:`, JSON.stringify(results),
|
|
58
|
+
`\n live=${JSON.stringify(live)}\n off6m=${JSON.stringify(off6m)}\n off45m=${JSON.stringify(off45m)}`);
|
|
59
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Round 273 verification: Layout toggle inactive buttons hover-text
|
|
2
|
+
* now previews active text color (cyan-300), completing R163's
|
|
3
|
+
* "hover previews active state" pattern.
|
|
4
|
+
*
|
|
5
|
+
* Pre-R273 the inactive Ring/Grid buttons used:
|
|
6
|
+
* text-gray-500 hover:text-gray-300 hover:bg-cyan-500/5 active:bg-cyan-500/15
|
|
7
|
+
*
|
|
8
|
+
* The bg part previewed the active cyan-500/15 ghost via cyan-500/5,
|
|
9
|
+
* but the text just brightened gray-500→gray-300. Half-preview:
|
|
10
|
+
* cyan ghost bg + gray text. Active state was cyan-500/15 bg + cyan-300
|
|
11
|
+
* text — so hovering inactive didn't visually preview the active text
|
|
12
|
+
* color.
|
|
13
|
+
*
|
|
14
|
+
* Post-R273:
|
|
15
|
+
* text-gray-500 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15
|
|
16
|
+
*
|
|
17
|
+
* Hover state now previews BOTH the active bg (cyan ghost) AND text
|
|
18
|
+
* (cyan-300). Bg /5 vs /15 still distinguishes hover-preview from
|
|
19
|
+
* active-real.
|
|
20
|
+
*
|
|
21
|
+
* Test scope:
|
|
22
|
+
* 1. Ring inactive button (default state has Ring active; toggle to
|
|
23
|
+
* Grid first so Ring becomes inactive) classlist contains
|
|
24
|
+
* 'hover:text-cyan-300' and does NOT contain 'hover:text-gray-300'.
|
|
25
|
+
* 2. Grid inactive button (default state) classlist contains
|
|
26
|
+
* 'hover:text-cyan-300'.
|
|
27
|
+
* 3. R163 invariant: hover:bg-cyan-500/5 still present on inactive.
|
|
28
|
+
* 4. R272 freshness chip text starts with 'live · ' (regression).
|
|
29
|
+
*/
|
|
30
|
+
import { chromium } from 'playwright';
|
|
31
|
+
import { readFileSync } from 'node:fs';
|
|
32
|
+
|
|
33
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
34
|
+
const browser = await chromium.launch({ headless: true });
|
|
35
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
36
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
37
|
+
await ctx.addInitScript(() => {
|
|
38
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
39
|
+
});
|
|
40
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
41
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
42
|
+
const r = await route.fetch();
|
|
43
|
+
const b = await r.json();
|
|
44
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
45
|
+
const mk = (alias) => ({
|
|
46
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
47
|
+
network_id: nid, project_dir: null,
|
|
48
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
49
|
+
});
|
|
50
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
51
|
+
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
52
|
+
] } });
|
|
53
|
+
});
|
|
54
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
55
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
56
|
+
|
|
57
|
+
const page = await ctx.newPage();
|
|
58
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
59
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
60
|
+
await page.waitForSelector('[data-topo-chrome-layout="ring"]', { timeout: 10000 });
|
|
61
|
+
await page.waitForSelector('[data-topo-chrome-layout="grid"]', { timeout: 10000 });
|
|
62
|
+
await page.waitForSelector('[data-freshness-chip]', { timeout: 10000 });
|
|
63
|
+
await page.waitForTimeout(300);
|
|
64
|
+
|
|
65
|
+
const probe = await page.evaluate(() => {
|
|
66
|
+
// Default localStorage = ring is active, grid is inactive.
|
|
67
|
+
const ring = document.querySelector('[data-topo-chrome-layout="ring"]');
|
|
68
|
+
const grid = document.querySelector('[data-topo-chrome-layout="grid"]');
|
|
69
|
+
const freshness = document.querySelector('[data-freshness-chip]');
|
|
70
|
+
return {
|
|
71
|
+
ringClasses: ring ? ring.className.toString() : null,
|
|
72
|
+
ringActive: ring ? ring.getAttribute('data-topo-chrome-layout-active') : null,
|
|
73
|
+
gridClasses: grid ? grid.className.toString() : null,
|
|
74
|
+
gridActive: grid ? grid.getAttribute('data-topo-chrome-layout-active') : null,
|
|
75
|
+
freshnessText: freshness ? freshness.textContent.trim() : null,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
await browser.close();
|
|
79
|
+
|
|
80
|
+
const has = (s, cls) => (s || '').includes(cls);
|
|
81
|
+
|
|
82
|
+
// Ring is default-active (true). Grid is default-inactive (false).
|
|
83
|
+
// Grid (inactive) should have hover:text-cyan-300.
|
|
84
|
+
// Ring (active) uses 'bg-cyan-500/15 text-cyan-300 hover:bg-cyan-500/20 active:bg-cyan-500/25'
|
|
85
|
+
// — no hover:text-cyan-300 (it's already cyan-300 at rest) — UNCHANGED.
|
|
86
|
+
|
|
87
|
+
const results = {
|
|
88
|
+
ring_is_active: probe.ringActive === 'true',
|
|
89
|
+
grid_is_inactive: probe.gridActive === 'false',
|
|
90
|
+
grid_inactive_has_hover_cyan_text: has(probe.gridClasses, 'hover:text-cyan-300'),
|
|
91
|
+
grid_inactive_no_hover_gray_text: !has(probe.gridClasses, 'hover:text-gray-300'),
|
|
92
|
+
grid_inactive_keeps_cyan_bg_hover: has(probe.gridClasses, 'hover:bg-cyan-500/5'),
|
|
93
|
+
ring_active_has_cyan_text_base: has(probe.ringClasses, 'text-cyan-300'),
|
|
94
|
+
r272_freshness_starts_with_live: probe.freshnessText != null && probe.freshnessText.startsWith('live · '),
|
|
95
|
+
};
|
|
96
|
+
const ok = Object.values(results).every(Boolean);
|
|
97
|
+
console.log(`${ok ? '✅' : '❌'} layout hover text preview:`, JSON.stringify(results),
|
|
98
|
+
'\n ring classes:', probe.ringClasses,
|
|
99
|
+
'\n grid classes:', probe.gridClasses,
|
|
100
|
+
'\n freshness:', probe.freshnessText);
|
|
101
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* Round 32 verification: `l` (and `L`) keyboard shortcut toggles
|
|
2
|
+
* ring ↔ grid; titles + Help overlay surface the hint; input-focus
|
|
3
|
+
* suppression still blocks it. */
|
|
4
|
+
import { chromium } from 'playwright';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
|
|
7
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
8
|
+
const browser = await chromium.launch({ headless: true });
|
|
9
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
|
|
10
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
11
|
+
await ctx.addInitScript(() => {
|
|
12
|
+
try {
|
|
13
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
14
|
+
localStorage.setItem('anet-topo-layout', 'grid'); // start on grid
|
|
15
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
16
|
+
} catch {}
|
|
17
|
+
});
|
|
18
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
19
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
20
|
+
const r = await route.fetch();
|
|
21
|
+
const b = await r.json();
|
|
22
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
23
|
+
const sessions = ['a', 'b', 'c'].map(a => ({
|
|
24
|
+
alias: a, status: 'idle', 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
|
+
});
|
|
29
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
30
|
+
const page = await ctx.newPage();
|
|
31
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
32
|
+
await page.waitForSelector('button[aria-label="Topology layout"], button[title*="Ring layout"]', { timeout: 30000 });
|
|
33
|
+
await page.waitForTimeout(400);
|
|
34
|
+
|
|
35
|
+
const layoutPressed = () => page.evaluate(() => {
|
|
36
|
+
const ring = document.querySelector('button[title^="Ring layout"]');
|
|
37
|
+
const grid = document.querySelector('button[title^="Grid layout"]');
|
|
38
|
+
return { ring: ring?.getAttribute('aria-pressed'), grid: grid?.getAttribute('aria-pressed') };
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const initial = await layoutPressed();
|
|
42
|
+
|
|
43
|
+
// Press `l` → should toggle to ring
|
|
44
|
+
await page.keyboard.press('l');
|
|
45
|
+
await page.waitForTimeout(150);
|
|
46
|
+
const afterL = await layoutPressed();
|
|
47
|
+
|
|
48
|
+
// Press `L` (shift+l) → toggle back to grid
|
|
49
|
+
await page.keyboard.press('Shift+L');
|
|
50
|
+
await page.waitForTimeout(150);
|
|
51
|
+
const afterShiftL = await layoutPressed();
|
|
52
|
+
|
|
53
|
+
// Titles carry the hint
|
|
54
|
+
const titles = await page.evaluate(() => ({
|
|
55
|
+
ring: document.querySelector('button[title^="Ring layout"]')?.getAttribute('title') || '',
|
|
56
|
+
grid: document.querySelector('button[title^="Grid layout"]')?.getAttribute('title') || '',
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
// Help overlay lists the shortcut
|
|
60
|
+
await page.keyboard.press('?');
|
|
61
|
+
await page.waitForTimeout(200);
|
|
62
|
+
const helpHasLayout = await page.evaluate(() => {
|
|
63
|
+
const heading = [...document.querySelectorAll('[role="dialog"] div')].find(el => el.textContent === 'Topology canvas');
|
|
64
|
+
if (!heading) return false;
|
|
65
|
+
return /Toggle layout/i.test(heading.parentElement?.textContent || '');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Input-focus suppression — Cmd+K input, press l, no toggle
|
|
69
|
+
await page.keyboard.press('Escape');
|
|
70
|
+
await page.keyboard.press('Meta+k');
|
|
71
|
+
await page.waitForTimeout(200);
|
|
72
|
+
const palette = page.locator('input[placeholder*="Search"], input[type="search"], [role="dialog"] input').first();
|
|
73
|
+
await palette.focus().catch(() => {});
|
|
74
|
+
const beforeFocusToggle = await layoutPressed();
|
|
75
|
+
await page.keyboard.press('l');
|
|
76
|
+
await page.waitForTimeout(150);
|
|
77
|
+
const afterFocusToggle = await layoutPressed();
|
|
78
|
+
await page.keyboard.press('Escape');
|
|
79
|
+
|
|
80
|
+
await browser.close();
|
|
81
|
+
const results = {
|
|
82
|
+
startsGrid: initial.grid === 'true' && initial.ring === 'false',
|
|
83
|
+
lTogglesToRing: afterL.ring === 'true' && afterL.grid === 'false',
|
|
84
|
+
shiftLTogglesBack: afterShiftL.grid === 'true' && afterShiftL.ring === 'false',
|
|
85
|
+
titlesPresent: /l to toggle/i.test(titles.ring) && /l to toggle/i.test(titles.grid),
|
|
86
|
+
helpListsLayout: helpHasLayout,
|
|
87
|
+
inputFocusSuppressed: beforeFocusToggle.grid === afterFocusToggle.grid && beforeFocusToggle.ring === afterFocusToggle.ring,
|
|
88
|
+
};
|
|
89
|
+
const ok = Object.values(results).every(Boolean);
|
|
90
|
+
console.log(`${ok ? '✅' : '❌'} layout key:`, JSON.stringify(results), `titles=`, titles);
|
|
91
|
+
process.exit(ok ? 0 : 1);
|