@sleep2agi/agent-network-dashboard 0.4.6-preview.13 → 0.4.6-preview.131
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 +1 -0
- 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/routes-manifest.json +6 -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/servers/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/hub/servers/route/build-manifest.json +9 -0
- package/.next/server/app/api/hub/servers/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/hub/servers/route.js +7 -0
- package/.next/server/app/api/hub/servers/route.js.map +5 -0
- package/.next/server/app/api/hub/servers/route.js.nft.json +1 -0
- package/.next/server/app/api/hub/servers/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 +1 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -0
- 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/08kf-.bgs10ze.js +1 -0
- package/.next/static/chunks/09pxnkf-1.1fj.js +1 -0
- package/.next/static/chunks/0eo167c-wlmfz.js +4 -0
- package/.next/static/chunks/0glmrdj9112tc.js +7 -0
- package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
- package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
- package/.next/static/chunks/0yv1jpx~5v038.js +7 -0
- package/.next/static/chunks/10tq~cwj-obp4.js +1 -0
- package/.next/static/chunks/1450fa_~v9qpc.css +2 -0
- package/.next/static/chunks/174re5ar~kxne.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +9 -0
- package/app/api/hub/servers/route.ts +39 -0
- package/app/components/ChatPopover.tsx +38 -2
- package/app/components/CommandPalette.tsx +107 -0
- package/app/components/HelpOverlay.tsx +87 -0
- package/app/components/ServersDrawer.tsx +64 -27
- package/app/components/TopoGraph.tsx +2419 -163
- package/app/components/utils.ts +8 -7
- package/app/globals.css +19 -1
- package/app/lib/time.ts +36 -0
- package/package.json +1 -1
- package/scripts/help-overlay-topo-test.mjs +44 -0
- package/scripts/servers-drawer-live-test.mjs +96 -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-test.mjs +73 -0
- package/scripts/topo-active-links-tooltip-test.mjs +79 -0
- package/scripts/topo-arrival-ping-test.mjs +87 -0
- package/scripts/topo-arrow-tier-test.mjs +61 -0
- package/scripts/topo-auto-fit-test.mjs +73 -0
- package/scripts/topo-chat-focus-ring-test.mjs +77 -0
- package/scripts/topo-chat-target-breath-test.mjs +84 -0
- package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
- package/scripts/topo-click-ripple-test.mjs +66 -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-cwd-tooltip-test.mjs +64 -0
- package/scripts/topo-dblclick-reset-test.mjs +74 -0
- package/scripts/topo-dispatch-pulse-test.mjs +83 -0
- package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
- package/scripts/topo-edge-badge-hover-test.mjs +85 -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-focus-test.mjs +113 -0
- package/scripts/topo-edge-hover-test.mjs +93 -0
- package/scripts/topo-edge-pill-test.mjs +105 -0
- package/scripts/topo-edge-tooltip-test.mjs +70 -0
- package/scripts/topo-endpoint-ring-test.mjs +93 -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-group-box-pinned-state-test.mjs +95 -0
- package/scripts/topo-group-count-test.mjs +68 -0
- package/scripts/topo-group-hover-test.mjs +82 -0
- package/scripts/topo-group-label-hover-test.mjs +119 -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-status-pips-test.mjs +103 -0
- package/scripts/topo-groupbox-live-test.mjs +100 -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-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-hub-busyness-test.mjs +100 -0
- package/scripts/topo-hub-click-fit-test.mjs +137 -0
- package/scripts/topo-hub-hover-ring-test.mjs +82 -0
- package/scripts/topo-hub-title-test.mjs +93 -0
- package/scripts/topo-ingroup-edge-test.mjs +103 -0
- package/scripts/topo-label-lift-test.mjs +119 -0
- package/scripts/topo-label-scale-test.mjs +78 -0
- package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
- package/scripts/topo-layout-key-test.mjs +91 -0
- package/scripts/topo-legend-count-test.mjs +88 -0
- package/scripts/topo-legend-hover-test.mjs +113 -0
- package/scripts/topo-legend-panel-header-test.mjs +83 -0
- package/scripts/topo-legend-pin-test.mjs +128 -0
- package/scripts/topo-legend-row-tint-test.mjs +95 -0
- package/scripts/topo-minimap-test.mjs +68 -0
- package/scripts/topo-mobile-chips-test.mjs +103 -0
- package/scripts/topo-node-hover-lift-test.mjs +85 -0
- package/scripts/topo-node-title-test.mjs +101 -0
- package/scripts/topo-offline-dim-test.mjs +70 -0
- package/scripts/topo-overlap-test.mjs +32 -5
- package/scripts/topo-pan-cursor-test.mjs +62 -0
- package/scripts/topo-panel-elevation-test.mjs +76 -0
- package/scripts/topo-particle-stagger-test.mjs +82 -0
- package/scripts/topo-pin-intersection-test.mjs +121 -0
- package/scripts/topo-pin-persistence-test.mjs +105 -0
- package/scripts/topo-popover-meta-test.mjs +78 -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-tooltip-test.mjs +98 -0
- package/scripts/topo-pulse-rate-test.mjs +71 -0
- package/scripts/topo-recent-panel-count-test.mjs +72 -0
- package/scripts/topo-recent-row-hover-test.mjs +122 -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-ts-test.mjs +92 -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-status-chip-hover-test.mjs +101 -0
- package/scripts/topo-status-chip-tooltip-test.mjs +95 -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-tz-ghost-test.mjs +69 -0
- package/scripts/topo-vendor-chip-hover-test.mjs +111 -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-wheel-zoom-test.mjs +79 -0
- package/scripts/topo-working-halo-breath-test.mjs +86 -0
- package/scripts/topo-zoom-keys-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/03u_oa2o2jrr~.js +0 -1
- package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
- package/.next/static/chunks/0dttftqqehpdj.js +0 -1
- package/.next/static/chunks/0elalb9n_.~di.js +0 -1
- package/.next/static/chunks/0fqy_0wj_1xa7.js +0 -7
- package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
- package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
- package/.next/static/chunks/0q_z8ad2uojv5.js +0 -1
- package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
- package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
- package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
- package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
- package/.next/static/chunks/17h~0~zsxjx6x.css +0 -2
- /package/.next/static/{nhazRrlYzcsAAYMwx8NFz → A3ssjbOXf7a3BaCWBznPU}/_buildManifest.js +0 -0
- /package/.next/static/{nhazRrlYzcsAAYMwx8NFz → A3ssjbOXf7a3BaCWBznPU}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{nhazRrlYzcsAAYMwx8NFz → A3ssjbOXf7a3BaCWBznPU}/_ssgManifest.js +0 -0
package/app/components/utils.ts
CHANGED
|
@@ -11,14 +11,15 @@ export function previewContent(raw: string | null | undefined): string {
|
|
|
11
11
|
.trim() || '--';
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// Round 44 / Loop: timeAgo now delegates to the shared lib/time helper.
|
|
15
|
+
// The old in-line `replace + 'Z'` parse appended a Z to already-ISO
|
|
16
|
+
// inputs (producing "…ZZ", browser-fragile) — strict-superset semantics
|
|
17
|
+
// preserved for SQL-style inputs, fixed for ISO. Plus a 'just now' fallback
|
|
18
|
+
// when clock skew puts the timestamp in the future. Five callers benefit:
|
|
19
|
+
// /admin · /logs · /settings/networks · InboxPanel · /messages.
|
|
20
|
+
import { relativeAgo } from '../lib/time';
|
|
14
21
|
export function timeAgo(dateStr: string): string {
|
|
15
|
-
|
|
16
|
-
const diff = Date.now() - new Date(dateStr.replace(' ', 'T') + 'Z').getTime();
|
|
17
|
-
const s = Math.floor(diff / 1000);
|
|
18
|
-
if (s < 60) return `${s}s ago`;
|
|
19
|
-
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
20
|
-
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
|
21
|
-
return `${Math.floor(s / 86400)}d ago`;
|
|
22
|
+
return relativeAgo(dateStr) ?? '--';
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export function statusColor(status: string, hasSse: boolean): string {
|
package/app/globals.css
CHANGED
|
@@ -860,6 +860,23 @@ body {
|
|
|
860
860
|
animation: anet-topo-spoke-flow-kf 2.4s linear infinite;
|
|
861
861
|
}
|
|
862
862
|
|
|
863
|
+
/* Round 85 — group-box "marching ants" perimeter drift.
|
|
864
|
+
Applied to a grid-layout group box whose `statuses.working > 0`
|
|
865
|
+
AND that is neither hovered nor pinned (those treatments already
|
|
866
|
+
carry their own visual emphasis via solid stroke). Slow 12s
|
|
867
|
+
linear cycle reads as ambient motion, not animation — the eye
|
|
868
|
+
parses "this group has live work" without registering the box
|
|
869
|
+
as moving. R86 stride matches the box's `strokeDasharray="6 6"`
|
|
870
|
+
so a single full cycle returns to a frame-identical state.
|
|
871
|
+
prefers-reduced-motion handled by the blanket override below. */
|
|
872
|
+
@keyframes anet-topo-groupbox-march-kf {
|
|
873
|
+
from { stroke-dashoffset: 0; }
|
|
874
|
+
to { stroke-dashoffset: -12; }
|
|
875
|
+
}
|
|
876
|
+
.anet-topo-groupbox-live {
|
|
877
|
+
animation: anet-topo-groupbox-march-kf 12s linear infinite;
|
|
878
|
+
}
|
|
879
|
+
|
|
863
880
|
/* ─────────────────────────────────────────────────────────────────────
|
|
864
881
|
Round 29 — prefers-reduced-motion a11y sweep
|
|
865
882
|
|
|
@@ -894,7 +911,8 @@ body {
|
|
|
894
911
|
.anet-fade-in,
|
|
895
912
|
.anet-current-step-pulse,
|
|
896
913
|
.anet-topo-sweep,
|
|
897
|
-
.anet-topo-spoke-flow
|
|
914
|
+
.anet-topo-spoke-flow,
|
|
915
|
+
.anet-topo-groupbox-live {
|
|
898
916
|
animation: none !important;
|
|
899
917
|
}
|
|
900
918
|
/* Tasks row grid 0fr↔1fr keep working (it's the toggle mechanism),
|
package/app/lib/time.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Shared timestamp helpers for hub-sourced data.
|
|
2
|
+
*
|
|
3
|
+
* Background — the CommHub serialises timestamps SQL-style without a
|
|
4
|
+
* zone ("2026-05-15 06:00:28"), but some test data uses full ISO
|
|
5
|
+
* ("…T06:00:28Z"). `Date.parse` on bare SQL is **local-time** in every
|
|
6
|
+
* browser the dashboard supports, so a UTC+8 operator silently sees
|
|
7
|
+
* every timestamp 8 h older than reality (Round 35 dug this out via
|
|
8
|
+
* the `isGhost` filter mis-fire).
|
|
9
|
+
*
|
|
10
|
+
* Round 38 / Loop consolidates parsing — TopoGraph (R35), ChatPopover
|
|
11
|
+
* (R37) and utils.ts::timeAgo all mirrored the same SQL→UTC rewrite;
|
|
12
|
+
* one source of truth here. */
|
|
13
|
+
|
|
14
|
+
/** Parse a hub timestamp to ms-since-epoch, treating SQL-style strings
|
|
15
|
+
* as UTC. ISO strings (with `T`) bypass the rewrite and are parsed as-is.
|
|
16
|
+
* Returns null for empty / unparseable input. */
|
|
17
|
+
export function parseHubTime(dateStr: string | null | undefined): number | null {
|
|
18
|
+
if (!dateStr) return null;
|
|
19
|
+
const iso = dateStr.includes('T') ? dateStr : dateStr.replace(' ', 'T') + 'Z';
|
|
20
|
+
const t = Date.parse(iso);
|
|
21
|
+
return Number.isFinite(t) ? t : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Format `dateStr` as a short relative-time string ("6m ago" / "2h ago").
|
|
25
|
+
* Future timestamps (clock skew) collapse to "just now". Returns null when
|
|
26
|
+
* the input doesn't parse — callers can decide on a fallback. */
|
|
27
|
+
export function relativeAgo(dateStr: string | null | undefined): string | null {
|
|
28
|
+
const t = parseHubTime(dateStr);
|
|
29
|
+
if (t === null) return null;
|
|
30
|
+
const s = Math.floor((Date.now() - t) / 1000);
|
|
31
|
+
if (s < 0) return 'just now';
|
|
32
|
+
if (s < 60) return `${s}s ago`;
|
|
33
|
+
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
34
|
+
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
|
35
|
+
return `${Math.floor(s / 86400)}d ago`;
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* Round 25 verification: pressing `?` opens the help overlay, and the
|
|
2
|
+
* new "Topology canvas" section is present with the documented keys. */
|
|
3
|
+
import { chromium } from 'playwright';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
7
|
+
const browser = await chromium.launch({ headless: true });
|
|
8
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
|
9
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
10
|
+
await ctx.addInitScript(() => {
|
|
11
|
+
try { sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
12
|
+
});
|
|
13
|
+
await ctx.route('**/api/hub/status*', (route) => route.fulfill({ json: { sessions: [] } }));
|
|
14
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
15
|
+
const page = await ctx.newPage();
|
|
16
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
17
|
+
await page.waitForTimeout(400);
|
|
18
|
+
|
|
19
|
+
await page.keyboard.press('?');
|
|
20
|
+
await page.waitForTimeout(200);
|
|
21
|
+
|
|
22
|
+
// The overlay contains group headings; find the Topology canvas group + its rows.
|
|
23
|
+
const data = await page.evaluate(() => {
|
|
24
|
+
// The group heading is a <div>; its parent wraps heading + the <ul> of items.
|
|
25
|
+
const heading = [...document.querySelectorAll('[role="dialog"] div')].find(el => el.textContent === 'Topology canvas');
|
|
26
|
+
if (!heading) return null;
|
|
27
|
+
return {
|
|
28
|
+
headingPresent: true,
|
|
29
|
+
sectionText: (heading.parentElement?.textContent || '').replace(/\s+/g, ' ').trim(),
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await browser.close();
|
|
34
|
+
const results = {
|
|
35
|
+
overlayOpened: !!data,
|
|
36
|
+
topologySectionPresent: !!data?.headingPresent,
|
|
37
|
+
hasZoomKeys: !!data && /Zoom in/.test(data.sectionText) && /Zoom out/.test(data.sectionText) && /Reset zoom/.test(data.sectionText),
|
|
38
|
+
hasMouseGestures: !!data && /wheel/.test(data.sectionText) && /Pan the canvas/.test(data.sectionText),
|
|
39
|
+
hasClickHint: !!data && /chat popover/.test(data.sectionText) && /dbl-click/.test(data.sectionText),
|
|
40
|
+
};
|
|
41
|
+
const ok = Object.values(results).every(Boolean);
|
|
42
|
+
console.log(`${ok ? '✅' : '❌'} help overlay topology section:`, JSON.stringify(results));
|
|
43
|
+
if (!ok && data) console.log('section text:', data.sectionText.slice(0, 300));
|
|
44
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* Round 20 / #119 step 3: ServersDrawer now consumes /api/hub/servers via
|
|
2
|
+
* useSWR. Verifies:
|
|
3
|
+
* 1. drawer renders live cards from the proxied endpoint (3 hosts)
|
|
4
|
+
* 2. polling fires (refreshInterval 5s) — second snapshot reflects an
|
|
5
|
+
* updated payload after >5.2s
|
|
6
|
+
* 3. when the hub predates 0.8.1-preview.2 the proxy returns
|
|
7
|
+
* `{ servers: [], unavailable: true }` and the drawer shows the
|
|
8
|
+
* friendly upgrade hint instead of an angry empty state
|
|
9
|
+
* 4. hub-down (502) shows the retry chip */
|
|
10
|
+
import { chromium } from 'playwright';
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
|
|
13
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
14
|
+
const browser = await chromium.launch({ headless: true });
|
|
15
|
+
|
|
16
|
+
async function load({ payload, status = 200 }) {
|
|
17
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 900 } });
|
|
18
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
19
|
+
await ctx.addInitScript(() => {
|
|
20
|
+
try {
|
|
21
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
22
|
+
localStorage.setItem('anet-servers-drawer', '1'); // start expanded so SWR fires
|
|
23
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
24
|
+
} catch {}
|
|
25
|
+
});
|
|
26
|
+
let calls = 0;
|
|
27
|
+
await ctx.route('**/api/hub/servers', async (route) => {
|
|
28
|
+
calls++;
|
|
29
|
+
const body = typeof payload === 'function' ? payload(calls) : payload;
|
|
30
|
+
await route.fulfill({ status, contentType: 'application/json', body: JSON.stringify(body) });
|
|
31
|
+
});
|
|
32
|
+
// keep the rest of the dashboard quiet
|
|
33
|
+
await ctx.route('**/api/hub/status*', (route) => route.fulfill({ json: { sessions: [] } }));
|
|
34
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
35
|
+
const page = await ctx.newPage();
|
|
36
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
37
|
+
await page.waitForSelector('aside[aria-label="Servers panel"]', { timeout: 30000 });
|
|
38
|
+
return { ctx, page, getCalls: () => calls };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const results = {};
|
|
42
|
+
|
|
43
|
+
// 1+2: live cards + polling
|
|
44
|
+
{
|
|
45
|
+
const seed = (n) => ({
|
|
46
|
+
servers: [
|
|
47
|
+
{ hostname: 'srv-01', ip: '10.0.0.1', cpu_load_1min: 1.2, cpu_cores: 4, mem_used_gb: 3, mem_total_gb: 16, agent_count: n, status: 'online' },
|
|
48
|
+
{ hostname: 'srv-02', cpu_load_1min: 0.4, cpu_cores: 8, mem_used_gb: 6, mem_total_gb: 32, agent_count: 2, status: 'online' },
|
|
49
|
+
{ hostname: 'srv-03', cpu_load_1min: null, cpu_cores: 0, mem_used_gb: null, mem_total_gb: null, agent_count: 1, status: 'offline' },
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
const { ctx, page, getCalls } = await load({ payload: seed });
|
|
53
|
+
await page.waitForFunction(() => {
|
|
54
|
+
const cards = document.querySelectorAll('aside[aria-label="Servers panel"] [data-servers-body] > div[title]');
|
|
55
|
+
return cards.length >= 3;
|
|
56
|
+
}, { timeout: 10000 });
|
|
57
|
+
const hostnames = await page.$$eval(
|
|
58
|
+
'aside[aria-label="Servers panel"] [data-servers-body] span.font-semibold',
|
|
59
|
+
els => els.map(e => e.textContent),
|
|
60
|
+
);
|
|
61
|
+
results.rendersLiveCards = hostnames.includes('srv-01') && hostnames.includes('srv-02') && hostnames.includes('srv-03');
|
|
62
|
+
|
|
63
|
+
// wait past one poll cycle (5s + slack) and confirm the proxy was hit again
|
|
64
|
+
await page.waitForTimeout(5400);
|
|
65
|
+
results.polls = getCalls() >= 2;
|
|
66
|
+
await ctx.close();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 3: hub predates 0.8.1-preview.2 → unavailable hint
|
|
70
|
+
{
|
|
71
|
+
const { ctx, page } = await load({ payload: { servers: [], unavailable: true } });
|
|
72
|
+
await page.waitForFunction(() => {
|
|
73
|
+
const el = document.querySelector('aside[aria-label="Servers panel"] [data-servers-body]');
|
|
74
|
+
return el && el.textContent.includes('host telemetry not available');
|
|
75
|
+
}, { timeout: 10000 }).catch(() => {});
|
|
76
|
+
const body = await page.$eval('aside[aria-label="Servers panel"] [data-servers-body]', el => el.textContent);
|
|
77
|
+
results.showsUpgradeHint = /host telemetry not available/.test(body) && /commhub-server/.test(body);
|
|
78
|
+
await ctx.close();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 4: hub-down 502 → retry chip
|
|
82
|
+
{
|
|
83
|
+
const { ctx, page } = await load({ payload: { error: 'hub unreachable' }, status: 502 });
|
|
84
|
+
await page.waitForFunction(() => {
|
|
85
|
+
const el = document.querySelector('aside[aria-label="Servers panel"] [data-servers-body]');
|
|
86
|
+
return el && /hub unreachable/.test(el.textContent || '');
|
|
87
|
+
}, { timeout: 10000 }).catch(() => {});
|
|
88
|
+
const body = await page.$eval('aside[aria-label="Servers panel"] [data-servers-body]', el => el.textContent);
|
|
89
|
+
results.showsRetryChip = /hub unreachable/.test(body) && /every 5s/.test(body);
|
|
90
|
+
await ctx.close();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
await browser.close();
|
|
94
|
+
const ok = Object.values(results).every(Boolean);
|
|
95
|
+
console.log(`${ok ? '✅' : '❌'} servers drawer live:`, JSON.stringify(results));
|
|
96
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Round 64 verification: active-filter pills appear in chip row when
|
|
2
|
+
* pins are set and clear their pin on × click.
|
|
3
|
+
*
|
|
4
|
+
* - No pin → no pill rendered.
|
|
5
|
+
* - Pin status (via legend) → pill labelled "filter: working" with
|
|
6
|
+
* aria-label that includes "Clear working filter".
|
|
7
|
+
* - × button click → pin clears + node opacities restore.
|
|
8
|
+
* - Both pins independent: pin a status + pin a group → two pills.
|
|
9
|
+
*/
|
|
10
|
+
import { chromium } from 'playwright';
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
|
|
13
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
14
|
+
const browser = await chromium.launch({ headless: true });
|
|
15
|
+
|
|
16
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
17
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
18
|
+
await ctx.addInitScript(() => {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
21
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
22
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
23
|
+
} catch {}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
27
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
28
|
+
const r = await route.fetch();
|
|
29
|
+
const b = await r.json();
|
|
30
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
31
|
+
const sessions = [
|
|
32
|
+
{ alias: 'alpha1', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
33
|
+
{ alias: 'alpha2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
34
|
+
{ alias: 'alpha3', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
35
|
+
{ alias: 'beta', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
36
|
+
];
|
|
37
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
38
|
+
});
|
|
39
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
40
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
41
|
+
const page = await ctx.newPage();
|
|
42
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
43
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
44
|
+
await page.waitForTimeout(500);
|
|
45
|
+
|
|
46
|
+
const readPills = () => page.evaluate(() => {
|
|
47
|
+
return [...document.querySelectorAll('[data-active-filter]')].map(el => ({
|
|
48
|
+
kind: el.getAttribute('data-active-filter'),
|
|
49
|
+
text: (el.textContent || '').trim(),
|
|
50
|
+
}));
|
|
51
|
+
});
|
|
52
|
+
const readWkrOpacity = () => page.evaluate(() => +(document.querySelector('g[data-node="alpha1"]')?.style.opacity || '1'));
|
|
53
|
+
|
|
54
|
+
const pillsBefore = await readPills();
|
|
55
|
+
|
|
56
|
+
// Pin working via legend.
|
|
57
|
+
await page.locator('g[data-legend-status="working"]').first().click({ force: true });
|
|
58
|
+
await page.mouse.move(10, 10);
|
|
59
|
+
await page.waitForTimeout(250);
|
|
60
|
+
const pillsAfterStatus = await readPills();
|
|
61
|
+
const wkrAfterStatus = await readWkrOpacity();
|
|
62
|
+
|
|
63
|
+
// Pin alpha group via label click.
|
|
64
|
+
await page.locator('g[data-group-label-hit="alpha"]').first().click({ force: true });
|
|
65
|
+
await page.mouse.move(10, 10);
|
|
66
|
+
await page.waitForTimeout(250);
|
|
67
|
+
const pillsBothPinned = await readPills();
|
|
68
|
+
|
|
69
|
+
// Click × on the status pill.
|
|
70
|
+
await page.locator('[data-active-filter="status"] button[aria-label*="working"]').first().click({ force: true });
|
|
71
|
+
await page.waitForTimeout(250);
|
|
72
|
+
const pillsAfterStatusClear = await readPills();
|
|
73
|
+
const wkrAfterStatusClear = await readWkrOpacity();
|
|
74
|
+
|
|
75
|
+
// Click × on the group pill.
|
|
76
|
+
await page.locator('[data-active-filter="group"] button[aria-label*="alpha"]').first().click({ force: true });
|
|
77
|
+
await page.waitForTimeout(250);
|
|
78
|
+
const pillsFinal = await readPills();
|
|
79
|
+
|
|
80
|
+
await browser.close();
|
|
81
|
+
|
|
82
|
+
const results = {
|
|
83
|
+
before_noPills: pillsBefore.length === 0,
|
|
84
|
+
statusPin_oneStatusPill: pillsAfterStatus.length === 1
|
|
85
|
+
&& pillsAfterStatus[0].kind === 'status'
|
|
86
|
+
&& /filter:\s*working/.test(pillsAfterStatus[0].text),
|
|
87
|
+
statusPin_dimsBeta: wkrAfterStatus >= 0.55, // alpha1 IS working — should stay
|
|
88
|
+
bothPinned_twoPills: pillsBothPinned.length === 2
|
|
89
|
+
&& pillsBothPinned.some(p => p.kind === 'status')
|
|
90
|
+
&& pillsBothPinned.some(p => p.kind === 'group'),
|
|
91
|
+
statusClear_only_group_remains: pillsAfterStatusClear.length === 1
|
|
92
|
+
&& pillsAfterStatusClear[0].kind === 'group',
|
|
93
|
+
statusClear_restoredOpacity: wkrAfterStatusClear >= 0.55,
|
|
94
|
+
groupClear_noPills: pillsFinal.length === 0,
|
|
95
|
+
};
|
|
96
|
+
const ok = Object.values(results).every(Boolean);
|
|
97
|
+
console.log(`${ok ? '✅' : '❌'} active-filter pill:`, JSON.stringify(results),
|
|
98
|
+
`\n pillsAfterStatus=`, pillsAfterStatus,
|
|
99
|
+
`\n pillsBothPinned=`, pillsBothPinned,
|
|
100
|
+
`\n pillsAfterStatusClear=`, pillsAfterStatusClear);
|
|
101
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/* Round 77 verification: hovering the "N active links" header chip
|
|
2
|
+
* boosts every flow edge to 1.5× the baseline opacity simultaneously.
|
|
3
|
+
* Mouseleave restores. Composes with R49 / R50 — single-edge hover
|
|
4
|
+
* still wins over chip hover (the priority is edge > node > chip).
|
|
5
|
+
*
|
|
6
|
+
* - 3 flow edges; baseline opacity ~0.28 each.
|
|
7
|
+
* - Hover the active-links chip → all 3 jump to ~0.42 (1.5×).
|
|
8
|
+
* - Mouse-leave → all 3 return to ~0.28.
|
|
9
|
+
* - Hover an individual edge while chip is also hovered → R50 wins,
|
|
10
|
+
* edge gets 2.0×, others drop to 0.35×. (We just check chip-hover
|
|
11
|
+
* here; the interaction with edge hover is documented in R50 tests.)
|
|
12
|
+
*/
|
|
13
|
+
import { chromium } from 'playwright';
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
17
|
+
const browser = await chromium.launch({ headless: true });
|
|
18
|
+
|
|
19
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
20
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
21
|
+
await ctx.addInitScript(() => {
|
|
22
|
+
try {
|
|
23
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
24
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
25
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
26
|
+
sessionStorage.removeItem('anet-topo-pinned-status');
|
|
27
|
+
sessionStorage.removeItem('anet-topo-pinned-group');
|
|
28
|
+
} catch {}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
32
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
33
|
+
const r = await route.fetch();
|
|
34
|
+
const b = await r.json();
|
|
35
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
36
|
+
const sessions = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta'].map(a => ({
|
|
37
|
+
alias: a, status: 'idle', 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
|
+
});
|
|
42
|
+
const now = new Date().toISOString();
|
|
43
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
|
|
44
|
+
{ from_alias: 'alpha', to_alias: 'beta', content: 'm', created_at: now },
|
|
45
|
+
{ from_alias: 'gamma', to_alias: 'delta', content: 'm', created_at: now },
|
|
46
|
+
{ from_alias: 'epsilon', to_alias: 'zeta', content: 'm', created_at: now },
|
|
47
|
+
] } }));
|
|
48
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.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 === 6, { timeout: 30000 });
|
|
52
|
+
await page.waitForTimeout(600);
|
|
53
|
+
|
|
54
|
+
const readEdgeOpacities = () => page.evaluate(() => {
|
|
55
|
+
const out = {};
|
|
56
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
57
|
+
for (const g of svg.querySelectorAll(':scope > g > g')) {
|
|
58
|
+
const t = g.querySelector('path[data-edge-hitbox] title');
|
|
59
|
+
if (!t) continue;
|
|
60
|
+
const route = (t.textContent || '').split('\n')[0];
|
|
61
|
+
const base = [...g.querySelectorAll(':scope > path')].find(
|
|
62
|
+
p => !p.hasAttribute('data-edge-hitbox') && p.hasAttribute('marker-end')
|
|
63
|
+
);
|
|
64
|
+
if (base) out[route] = +base.getAttribute('opacity');
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const before = await readEdgeOpacities();
|
|
70
|
+
|
|
71
|
+
// Hover the active-links chip.
|
|
72
|
+
const chipExists = await page.locator('[data-active-links-chip]').count();
|
|
73
|
+
if (chipExists === 0) { console.log('❌ active-links chip not rendered'); process.exit(1); }
|
|
74
|
+
await page.locator('[data-active-links-chip]').hover();
|
|
75
|
+
await page.waitForTimeout(300);
|
|
76
|
+
const onHover = await readEdgeOpacities();
|
|
77
|
+
|
|
78
|
+
// Mouse-leave.
|
|
79
|
+
await page.mouse.move(10, 10);
|
|
80
|
+
await page.waitForTimeout(300);
|
|
81
|
+
const afterLeave = await readEdgeOpacities();
|
|
82
|
+
|
|
83
|
+
await browser.close();
|
|
84
|
+
|
|
85
|
+
const ratio = (after, before) => after / before;
|
|
86
|
+
const allAround = (rs, lo, hi) => rs.every(r => r > lo && r < hi);
|
|
87
|
+
|
|
88
|
+
const baseValues = Object.values(before);
|
|
89
|
+
const hoverValues = Object.values(onHover);
|
|
90
|
+
const leaveValues = Object.values(afterLeave);
|
|
91
|
+
|
|
92
|
+
const results = {
|
|
93
|
+
baseline_equalAtBase: allAround(baseValues, 0.25, 0.31),
|
|
94
|
+
hover_allBoosted_15x: allAround(hoverValues.map((v, i) => ratio(v, baseValues[i])), 1.4, 1.6),
|
|
95
|
+
hover_absoluteRange: allAround(hoverValues, 0.39, 0.46), // 1.5 * (0.22..0.28) * fresh
|
|
96
|
+
leave_restoreBaseline: allAround(leaveValues.map((v, i) => Math.abs(v - baseValues[i])), -0.01, 0.02),
|
|
97
|
+
};
|
|
98
|
+
const ok = Object.values(results).every(Boolean);
|
|
99
|
+
console.log(`${ok ? '✅' : '❌'} active-links chip hover:`, JSON.stringify(results),
|
|
100
|
+
`\n before=`, before,
|
|
101
|
+
`\n onHover=`, onHover,
|
|
102
|
+
`\n afterLeave=`, afterLeave);
|
|
103
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* Round 42 verification: "X active links" chip in the header includes
|
|
2
|
+
* "· last <relative>" when flow links exist, drops the `· last …` tail
|
|
3
|
+
* when no flow links are present, and renders singular/plural correctly. */
|
|
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
|
+
|
|
10
|
+
async function probe({ msgs }) {
|
|
11
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1000 } });
|
|
12
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
13
|
+
await ctx.addInitScript(() => {
|
|
14
|
+
try {
|
|
15
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
16
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
17
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
18
|
+
} catch {}
|
|
19
|
+
});
|
|
20
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
21
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
22
|
+
const r = await route.fetch();
|
|
23
|
+
const b = await r.json();
|
|
24
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
25
|
+
const sessions = ['alpha', 'beta', 'gamma'].map(a => ({
|
|
26
|
+
alias: a, status: 'idle', network_id: nid, project_dir: null,
|
|
27
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
28
|
+
}));
|
|
29
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
30
|
+
});
|
|
31
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
|
|
32
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
33
|
+
const page = await ctx.newPage();
|
|
34
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
35
|
+
await page.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 30000 });
|
|
36
|
+
await page.waitForTimeout(500);
|
|
37
|
+
const text = await page.evaluate(() => {
|
|
38
|
+
// The active-links chip is the LAST chip in the topo header row (after
|
|
39
|
+
// pressure-bar + vendor + working/online). Match by `active link`.
|
|
40
|
+
const spans = [...document.querySelectorAll('span')];
|
|
41
|
+
return spans.find(s => /active link/.test(s.textContent || ''))?.textContent?.replace(/\s+/g, ' ').trim() || null;
|
|
42
|
+
});
|
|
43
|
+
await ctx.close();
|
|
44
|
+
return text;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 1. No messages → 0 active links, no "· last X" tail
|
|
48
|
+
const empty = await probe({ msgs: [] });
|
|
49
|
+
// 2. One message just now → 1 active link · last Ns ago
|
|
50
|
+
const recent = await probe({ msgs: [
|
|
51
|
+
{ from_alias: 'alpha', to_alias: 'beta', content: 'hi', created_at: new Date(Date.now() - 30 * 1000).toISOString() },
|
|
52
|
+
] });
|
|
53
|
+
// 3. Five messages, latest 4 min ago → 5 active links · last 4m ago (singular link uses "link", plural "links")
|
|
54
|
+
const fourMinAgo = new Date(Date.now() - 4 * 60 * 1000).toISOString();
|
|
55
|
+
const eightMinAgo = new Date(Date.now() - 8 * 60 * 1000).toISOString();
|
|
56
|
+
const five = await probe({ msgs: [
|
|
57
|
+
{ from_alias: 'alpha', to_alias: 'beta', content: '1', created_at: eightMinAgo },
|
|
58
|
+
{ from_alias: 'alpha', to_alias: 'gamma', content: '2', created_at: eightMinAgo },
|
|
59
|
+
{ from_alias: 'alpha', to_alias: 'gamma', content: '3', created_at: eightMinAgo },
|
|
60
|
+
{ from_alias: 'beta', to_alias: 'gamma', content: '4', created_at: eightMinAgo },
|
|
61
|
+
{ from_alias: 'beta', to_alias: 'gamma', content: '5', created_at: fourMinAgo }, // latest
|
|
62
|
+
] });
|
|
63
|
+
|
|
64
|
+
await browser.close();
|
|
65
|
+
const results = {
|
|
66
|
+
emptyChipHasNoTail: empty === '0 active links',
|
|
67
|
+
recentSingularChip: recent !== null && /^1 active link · last \d+s ago$/.test(recent),
|
|
68
|
+
pluralChipShowsLatest: five !== null && /active links · last [34]m ago/.test(five),
|
|
69
|
+
};
|
|
70
|
+
const ok = Object.values(results).every(Boolean);
|
|
71
|
+
console.log(`${ok ? '✅' : '❌'} active-links chip:`, JSON.stringify(results),
|
|
72
|
+
`\n empty=${JSON.stringify(empty)}\n recent=${JSON.stringify(recent)}\n five=${JSON.stringify(five)}`);
|
|
73
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* Round 114 verification: active-links chip tooltip lists the actual
|
|
2
|
+
* flow pairs. Closes the info-density sweep on the last chip-row
|
|
3
|
+
* hover surface (R97-R113 covered everything else). Format:
|
|
4
|
+
* "alpha→beta (3), gamma→delta (1) — hover brightens all"
|
|
5
|
+
*
|
|
6
|
+
* Drive 3 flow pairs of different counts. Verify each pair appears
|
|
7
|
+
* in the title with its count; the trailing action hint says
|
|
8
|
+
* "hover brightens all". Empty-flow fleet → no title.
|
|
9
|
+
*/
|
|
10
|
+
import { chromium } from 'playwright';
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
|
|
13
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
14
|
+
const browser = await chromium.launch({ headless: true });
|
|
15
|
+
const fresh = new Date(Date.now() - 30 * 1000).toISOString();
|
|
16
|
+
|
|
17
|
+
const probe = async (withFlow) => {
|
|
18
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
19
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
20
|
+
await ctx.addInitScript(() => {
|
|
21
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
22
|
+
});
|
|
23
|
+
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 mk = (alias) => ({
|
|
28
|
+
alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
29
|
+
network_id: nid, project_dir: null,
|
|
30
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
31
|
+
});
|
|
32
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta'), mk('gamma'), mk('delta')] } });
|
|
33
|
+
});
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
const mkMsg = (id, from_alias, to_alias, ageMs) => ({
|
|
36
|
+
id, from_alias, to_alias, content: 'hi', network_id: 'default',
|
|
37
|
+
created_at: new Date(now - ageMs).toISOString(),
|
|
38
|
+
});
|
|
39
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: withFlow ? [
|
|
40
|
+
mkMsg('m1', 'alpha', 'beta', 30 * 1000),
|
|
41
|
+
mkMsg('m2', 'alpha', 'beta', 40 * 1000),
|
|
42
|
+
mkMsg('m3', 'alpha', 'beta', 50 * 1000),
|
|
43
|
+
mkMsg('m4', 'gamma', 'delta', 60 * 1000),
|
|
44
|
+
] : [] } }));
|
|
45
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
46
|
+
const page = await ctx.newPage();
|
|
47
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
48
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
49
|
+
await page.waitForTimeout(600);
|
|
50
|
+
const data = await page.evaluate(() => {
|
|
51
|
+
const el = document.querySelector('[data-active-links-chip]');
|
|
52
|
+
return {
|
|
53
|
+
title: el?.getAttribute('title'),
|
|
54
|
+
flowCount: el?.getAttribute('data-active-links-flow-count') || '',
|
|
55
|
+
text: el?.textContent?.trim() || '',
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
await ctx.close();
|
|
59
|
+
return data;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const empty = await probe(false);
|
|
63
|
+
const full = await probe(true);
|
|
64
|
+
await browser.close();
|
|
65
|
+
|
|
66
|
+
const results = {
|
|
67
|
+
empty_noTitle: empty.title === null,
|
|
68
|
+
empty_zeroCount: empty.flowCount === '0',
|
|
69
|
+
full_titleHasAlphaBeta: /alpha→beta \(3\)/.test(full.title || ''),
|
|
70
|
+
full_titleHasGammaDelta: /gamma→delta \(1\)/.test(full.title || ''),
|
|
71
|
+
full_titleActionHint: /hover brightens all/.test(full.title || ''),
|
|
72
|
+
full_flowCount2: full.flowCount === '2',
|
|
73
|
+
full_chipShowsCount: /2 active link/.test(full.text),
|
|
74
|
+
};
|
|
75
|
+
const ok = Object.values(results).every(Boolean);
|
|
76
|
+
console.log(`${ok ? '✅' : '❌'} active-links tooltip:`, JSON.stringify(results),
|
|
77
|
+
`\n empty=`, empty,
|
|
78
|
+
`\n full=`, full);
|
|
79
|
+
process.exit(ok ? 0 : 1);
|