@sleep2agi/agent-network-dashboard 0.4.6-preview.17 → 0.4.6-preview.170
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/050s9ndkk07i9.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/0d7u.7_u.l9sh.js +4 -0
- package/.next/static/chunks/0ggj5ummw2a84.js +7 -0
- package/.next/static/chunks/0glmrdj9112tc.js +7 -0
- package/.next/static/chunks/0gvra4akqlit0.js +1 -0
- package/.next/static/chunks/0otgjhpse~amj.css +2 -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/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 +126 -0
- package/app/components/ServersDrawer.tsx +64 -27
- package/app/components/TopoGraph.tsx +3338 -241
- package/app/components/utils.ts +8 -7
- package/app/globals.css +75 -5
- 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-nav-test.mjs +117 -0
- package/scripts/topo-active-links-chip-test.mjs +73 -0
- package/scripts/topo-active-links-freshness-test.mjs +113 -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-canvas-aria-test.mjs +108 -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-focus-test.mjs +95 -0
- package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
- package/scripts/topo-chrome-a11y-test.mjs +107 -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-hot-test.mjs +115 -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-hot-test.mjs +129 -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-a11y-test.mjs +113 -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-hover-lift-test.mjs +127 -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-hub-a11y-test.mjs +112 -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-hub-working-count-test.mjs +99 -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-layout-toggle-test.mjs +131 -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-hover-lift-test.mjs +109 -0
- package/scripts/topo-legend-row-tint-test.mjs +95 -0
- package/scripts/topo-legend-row-title-test.mjs +102 -0
- package/scripts/topo-minimap-a11y-test.mjs +101 -0
- package/scripts/topo-minimap-test.mjs +68 -0
- package/scripts/topo-mobile-chips-test.mjs +103 -0
- package/scripts/topo-node-a11y-test.mjs +107 -0
- package/scripts/topo-node-hover-lift-test.mjs +85 -0
- package/scripts/topo-node-title-peers-test.mjs +107 -0
- package/scripts/topo-node-title-test.mjs +101 -0
- package/scripts/topo-offline-dim-test.mjs +70 -0
- package/scripts/topo-online-chip-nav-test.mjs +143 -0
- package/scripts/topo-orbit-busyness-test.mjs +96 -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-panel-hover-elevation-test.mjs +118 -0
- package/scripts/topo-particle-stagger-test.mjs +82 -0
- package/scripts/topo-pin-intersection-empty-test.mjs +129 -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-freshness-test.mjs +109 -0
- package/scripts/topo-recent-panel-count-test.mjs +72 -0
- package/scripts/topo-recent-panel-hot-header-test.mjs +132 -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-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-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-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-spoke-busyness-test.mjs +110 -0
- package/scripts/topo-status-chip-hover-test.mjs +101 -0
- package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
- package/scripts/topo-svg-focus-test.mjs +112 -0
- package/scripts/topo-sweep-busyness-test.mjs +94 -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-chip-pin-test.mjs +148 -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/096hkjkwj4hb3.js +0 -1
- package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
- package/.next/static/chunks/0d6xn54cd2zo-.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/0iv2-b3po~sbg.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/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/13vw-ubg-9pjp.css +0 -2
- /package/.next/static/{TBtREVf7o4zYSqGqq3QT5 → m3FtdjT1mdUIOaZp0o9gq}/_buildManifest.js +0 -0
- /package/.next/static/{TBtREVf7o4zYSqGqq3QT5 → m3FtdjT1mdUIOaZp0o9gq}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{TBtREVf7o4zYSqGqq3QT5 → m3FtdjT1mdUIOaZp0o9gq}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/* Round 94 verification: each recent-signal panel row now carries a
|
|
2
|
+
* right-aligned relative timestamp (e.g. `2s` / `1m`). Composes with
|
|
3
|
+
* the R56 hover affordance and R57 panel elevation without changing
|
|
4
|
+
* geometry. Mirror what the R42 active-links chip already shows for
|
|
5
|
+
* the most-recent flow, but per-row so users can see which flows
|
|
6
|
+
* are current vs stale.
|
|
7
|
+
*
|
|
8
|
+
* Drive the dashboard with messages at known ages — 30s, 5m, 2h —
|
|
9
|
+
* and assert each row's timestamp matches the expected glyph. Also
|
|
10
|
+
* verify the timestamp <text> is non-interactive (pointerEvents:none)
|
|
11
|
+
* so it doesn't steal hover from the row hitbox.
|
|
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
|
+
} catch {}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
30
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
31
|
+
const r = await route.fetch();
|
|
32
|
+
const b = await r.json();
|
|
33
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
34
|
+
const mk = (alias) => ({
|
|
35
|
+
alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
36
|
+
network_id: nid, project_dir: null,
|
|
37
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
38
|
+
});
|
|
39
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta'), mk('gamma'), mk('delta')] } });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Mock messages so flowLinks aggregates them. Three flows at increasing
|
|
43
|
+
// ages — the panel sorts most-recent first (per the recent-signal
|
|
44
|
+
// aggregator), so row 0 is the freshest.
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
const mkMsg = (id, from_alias, to_alias, content, ageMs) => ({
|
|
47
|
+
id, from_alias, to_alias, content, network_id: 'default',
|
|
48
|
+
created_at: new Date(now - ageMs).toISOString(),
|
|
49
|
+
});
|
|
50
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
|
|
51
|
+
mkMsg('m1', 'alpha', 'beta', 'fix-the-bug', 30 * 1000), // 30s ago → "30s"
|
|
52
|
+
mkMsg('m2', 'gamma', 'delta', 'review-the-pr', 5 * 60 * 1000), // 5m ago → "5m"
|
|
53
|
+
mkMsg('m3', 'alpha', 'gamma', 'deploy-stage', 2 * 60 * 60 * 1000), // 2h ago → "2h"
|
|
54
|
+
] } }));
|
|
55
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
56
|
+
const page = await ctx.newPage();
|
|
57
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
58
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
59
|
+
// Wait for at least one recent-signal timestamp to render.
|
|
60
|
+
await page.waitForSelector('[data-recent-row-ts]', { timeout: 10000 });
|
|
61
|
+
await page.waitForTimeout(400);
|
|
62
|
+
|
|
63
|
+
const rows = await page.evaluate(() => {
|
|
64
|
+
return [...document.querySelectorAll('[data-recent-row]')].map(g => {
|
|
65
|
+
const rowKey = g.getAttribute('data-recent-row');
|
|
66
|
+
const tsEl = g.querySelector('[data-recent-row-ts]');
|
|
67
|
+
const rowText = g.querySelector('text:not([data-recent-row-ts])')?.textContent || '';
|
|
68
|
+
const tsText = tsEl?.textContent || null;
|
|
69
|
+
const tsPointerEvents = tsEl ? getComputedStyle(tsEl).pointerEvents : null;
|
|
70
|
+
return { rowKey, rowText, tsText, tsPointerEvents };
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await browser.close();
|
|
75
|
+
|
|
76
|
+
const results = {
|
|
77
|
+
threeRows: rows.length === 3,
|
|
78
|
+
// Row 0 (freshest) should be alpha→beta around "30s" or "29s" etc.
|
|
79
|
+
row0_hasFreshTs: rows[0] && /^[2-9]\d?s$/.test(rows[0].tsText || ''),
|
|
80
|
+
// Row 1 should read 5m (minutes precision).
|
|
81
|
+
row1_minuteTs: rows[1] && /^[4-6]m$/.test(rows[1].tsText || ''),
|
|
82
|
+
// Row 2 should read ~2h.
|
|
83
|
+
row2_hourTs: rows[2] && /^[12]h$/.test(rows[2].tsText || ''),
|
|
84
|
+
// ts <text> must be non-interactive so it doesn't intercept the row hover.
|
|
85
|
+
ts_nonInteractive: rows.every(r => r.tsPointerEvents === 'none'),
|
|
86
|
+
// Row content shows from/to/count/content via the existing layout.
|
|
87
|
+
// R138: arrow notation unified to unicode `→` (was ASCII `->`),
|
|
88
|
+
// matching filter pills / tooltips / edge-badge titles. Assertion
|
|
89
|
+
// flipped to the new typography contract.
|
|
90
|
+
row0_hasArrow: rows[0]?.rowText.includes('→'),
|
|
91
|
+
};
|
|
92
|
+
const ok = Object.values(results).every(Boolean);
|
|
93
|
+
console.log(`${ok ? '✅' : '❌'} recent-row ts:`, JSON.stringify(results),
|
|
94
|
+
`\n rows=`, rows);
|
|
95
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* Round 138 verification: recent-signal row typography unifies
|
|
2
|
+
* with the rest of the topology UI.
|
|
3
|
+
*
|
|
4
|
+
* Pre-R138 row text: "alpha -> beta / 12 / hi"
|
|
5
|
+
* Post-R138 row text: "alpha → beta · 12 · hi"
|
|
6
|
+
*
|
|
7
|
+
* Why: filter pills (R119), node tooltips (R98), active-links
|
|
8
|
+
* chip tooltip (R114), edge-badge titles all use the unicode
|
|
9
|
+
* arrow `→` and the data-delimiter ` · `. The recent-signal row
|
|
10
|
+
* was the lone holdout using ASCII `->` and ` / `. R138 brings
|
|
11
|
+
* it in line so the row reads like every other surface.
|
|
12
|
+
*
|
|
13
|
+
* Checks:
|
|
14
|
+
* - Row text contains "→" (unicode arrow)
|
|
15
|
+
* - Row text does NOT contain "->" (ASCII)
|
|
16
|
+
* - Row text uses " · " (middle dot) as separator
|
|
17
|
+
* - Row text does NOT contain " / " (forward slash)
|
|
18
|
+
* - Hot count tspan still works (R127 contract preserved)
|
|
19
|
+
* - Row pin still works (R116 contract preserved)
|
|
20
|
+
*/
|
|
21
|
+
import { chromium } from 'playwright';
|
|
22
|
+
import { readFileSync } from 'node:fs';
|
|
23
|
+
|
|
24
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
25
|
+
const browser = await chromium.launch({ headless: true });
|
|
26
|
+
|
|
27
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
28
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
29
|
+
await ctx.addInitScript(() => {
|
|
30
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
34
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
35
|
+
const r = await route.fetch();
|
|
36
|
+
const b = await r.json();
|
|
37
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
38
|
+
const mk = (alias, status) => ({
|
|
39
|
+
alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
40
|
+
network_id: nid, project_dir: null,
|
|
41
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
42
|
+
});
|
|
43
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
44
|
+
mk('alpha', 'working'), mk('beta', 'working'), mk('gamma', 'idle'),
|
|
45
|
+
] } });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
const mkMsg = (id, from, to, ageMs) => ({
|
|
50
|
+
id, from_alias: from, to_alias: to, content: 'hello',
|
|
51
|
+
network_id: 'default', created_at: new Date(now - ageMs).toISOString(),
|
|
52
|
+
});
|
|
53
|
+
const msgs = [];
|
|
54
|
+
// alpha→beta: 12 msgs (hot)
|
|
55
|
+
for (let i = 0; i < 12; i++) msgs.push(mkMsg(`a${i}`, 'alpha', 'beta', 20000 + i * 500));
|
|
56
|
+
// beta→gamma: 4 msgs (warm)
|
|
57
|
+
for (let i = 0; i < 4; i++) msgs.push(mkMsg(`b${i}`, 'beta', 'gamma', 30000 + i * 500));
|
|
58
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
|
|
59
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
60
|
+
|
|
61
|
+
const page = await ctx.newPage();
|
|
62
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
63
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
|
|
64
|
+
await page.waitForSelector('[data-recent-row="alpha->beta"]', { timeout: 10000 });
|
|
65
|
+
await page.waitForTimeout(400);
|
|
66
|
+
|
|
67
|
+
const inspect = (key) => page.evaluate((k) => {
|
|
68
|
+
const row = document.querySelector(`[data-recent-row="${k}"]`);
|
|
69
|
+
const text = row?.querySelector('text');
|
|
70
|
+
return {
|
|
71
|
+
fullText: text?.textContent,
|
|
72
|
+
pinned: row?.getAttribute('data-recent-row-pinned'),
|
|
73
|
+
hot: row?.getAttribute('data-recent-row-hot'),
|
|
74
|
+
};
|
|
75
|
+
}, key);
|
|
76
|
+
|
|
77
|
+
const a2b = await inspect('alpha->beta');
|
|
78
|
+
const b2g = await inspect('beta->gamma');
|
|
79
|
+
|
|
80
|
+
// Verify pin still works (R116 invariant)
|
|
81
|
+
await page.locator('[data-recent-row="alpha->beta"]').click();
|
|
82
|
+
await page.waitForTimeout(200);
|
|
83
|
+
const a2bPinned = await inspect('alpha->beta');
|
|
84
|
+
await page.keyboard.press('Escape');
|
|
85
|
+
await page.waitForTimeout(200);
|
|
86
|
+
|
|
87
|
+
await browser.close();
|
|
88
|
+
|
|
89
|
+
// Note: data-recent-row attribute itself still uses "alpha->beta" as
|
|
90
|
+
// the link.key (computed from from + '->' + to upstream). That's
|
|
91
|
+
// internal data, not user-visible text — R138 only touches the
|
|
92
|
+
// rendered <text> content.
|
|
93
|
+
const hasUnicodeArrow = (s) => !!s && s.includes('→');
|
|
94
|
+
const lacksAsciiArrow = (s) => !!s && !s.includes('->');
|
|
95
|
+
const usesMiddleDot = (s) => !!s && s.includes('·');
|
|
96
|
+
const lacksSlash = (s) => !!s && !/\s\/\s/.test(s);
|
|
97
|
+
|
|
98
|
+
const results = {
|
|
99
|
+
a2b_hasUnicodeArrow: hasUnicodeArrow(a2b.fullText),
|
|
100
|
+
a2b_lacksAsciiArrow: lacksAsciiArrow(a2b.fullText),
|
|
101
|
+
a2b_usesMiddleDot: usesMiddleDot(a2b.fullText),
|
|
102
|
+
a2b_lacksSlash: lacksSlash(a2b.fullText),
|
|
103
|
+
|
|
104
|
+
b2g_hasUnicodeArrow: hasUnicodeArrow(b2g.fullText),
|
|
105
|
+
b2g_usesMiddleDot: usesMiddleDot(b2g.fullText),
|
|
106
|
+
|
|
107
|
+
// R127 hot preserved
|
|
108
|
+
a2b_hotAttr: a2b.hot === 'true',
|
|
109
|
+
b2g_notHot: b2g.hot === 'false',
|
|
110
|
+
|
|
111
|
+
// R116 pin preserved
|
|
112
|
+
pin_landed: a2bPinned.pinned === 'true',
|
|
113
|
+
};
|
|
114
|
+
const ok = Object.values(results).every(Boolean);
|
|
115
|
+
console.log(`${ok ? '✅' : '❌'} recent-row typography:`, JSON.stringify(results),
|
|
116
|
+
`\n a2b=`, a2b,
|
|
117
|
+
`\n b2g=`, b2g,
|
|
118
|
+
`\n a2bPinned=`, a2bPinned);
|
|
119
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* Round 110 verification: recent-signal empty placeholder gains a
|
|
2
|
+
* sub-text hint so the empty state reads as intentional invitation
|
|
3
|
+
* rather than a stuck-loading state. Primary line "no flow yet"
|
|
4
|
+
* stays at the same position semantics (now y=54 instead of y=60
|
|
5
|
+
* to balance the two-line stack within the panel); sub-line at
|
|
6
|
+
* y=68 says "send a message between agents".
|
|
7
|
+
*
|
|
8
|
+
* Verifies:
|
|
9
|
+
* - both lines render when flowLinks is empty
|
|
10
|
+
* - both lines disappear when flowLinks has content
|
|
11
|
+
* - data-recent-signal-empty + data-recent-signal-empty-hint
|
|
12
|
+
* attributes are stable test hooks
|
|
13
|
+
*/
|
|
14
|
+
import { chromium } from 'playwright';
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
|
|
17
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
18
|
+
const browser = await chromium.launch({ headless: true });
|
|
19
|
+
const fresh = new Date(Date.now() - 30 * 1000).toISOString();
|
|
20
|
+
|
|
21
|
+
const probe = async (withFlow) => {
|
|
22
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1200 } });
|
|
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
|
+
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 mk = (alias) => ({
|
|
32
|
+
alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
33
|
+
network_id: nid, project_dir: null,
|
|
34
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
35
|
+
});
|
|
36
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta')] } });
|
|
37
|
+
});
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: withFlow ? [
|
|
40
|
+
{ id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'hi',
|
|
41
|
+
network_id: 'default', created_at: new Date(now - 10000).toISOString() },
|
|
42
|
+
] : [] } }));
|
|
43
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
44
|
+
const page = await ctx.newPage();
|
|
45
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
46
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
|
|
47
|
+
await page.waitForTimeout(600);
|
|
48
|
+
const data = await page.evaluate(() => ({
|
|
49
|
+
empty: document.querySelector('[data-recent-signal-empty]')?.textContent || null,
|
|
50
|
+
emptyHint: document.querySelector('[data-recent-signal-empty-hint]')?.textContent || null,
|
|
51
|
+
rowCount: document.querySelectorAll('[data-recent-row]').length,
|
|
52
|
+
}));
|
|
53
|
+
await ctx.close();
|
|
54
|
+
return data;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const empty = await probe(false);
|
|
58
|
+
const full = await probe(true);
|
|
59
|
+
await browser.close();
|
|
60
|
+
|
|
61
|
+
const results = {
|
|
62
|
+
empty_primaryLineShown: empty.empty === 'no flow yet',
|
|
63
|
+
empty_hintShown: /^send a message between agents$/.test(empty.emptyHint || ''),
|
|
64
|
+
empty_noRows: empty.rowCount === 0,
|
|
65
|
+
full_primaryGone: full.empty === null,
|
|
66
|
+
full_hintGone: full.emptyHint === null,
|
|
67
|
+
full_rowsRendered: full.rowCount >= 1,
|
|
68
|
+
};
|
|
69
|
+
const ok = Object.values(results).every(Boolean);
|
|
70
|
+
console.log(`${ok ? '✅' : '❌'} recent-signal empty hint:`, JSON.stringify(results),
|
|
71
|
+
`\n empty=`, empty,
|
|
72
|
+
`\n full=`, full);
|
|
73
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* Round 45 verification: recent-signal panel shows "no flow yet" placeholder
|
|
2
|
+
* when flowLinks is empty; renders normal link rows when populated. */
|
|
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
|
+
|
|
9
|
+
async function probe({ msgs }) {
|
|
10
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
|
11
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
12
|
+
await ctx.addInitScript(() => {
|
|
13
|
+
try {
|
|
14
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
15
|
+
localStorage.setItem('anet-topo-layout', 'grid');
|
|
16
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
17
|
+
} catch {}
|
|
18
|
+
});
|
|
19
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
20
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
21
|
+
const r = await route.fetch();
|
|
22
|
+
const b = await r.json();
|
|
23
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
24
|
+
const sessions = ['a', 'b'].map(a => ({
|
|
25
|
+
alias: a, status: 'idle', network_id: nid, project_dir: null,
|
|
26
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
27
|
+
}));
|
|
28
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
29
|
+
});
|
|
30
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
|
|
31
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
32
|
+
const page = await ctx.newPage();
|
|
33
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
34
|
+
await page.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 30000 });
|
|
35
|
+
await page.waitForTimeout(500);
|
|
36
|
+
const data = await page.evaluate(() => {
|
|
37
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
38
|
+
const empty = svg.querySelector('text[data-recent-signal-empty]');
|
|
39
|
+
// count flow-link description rows in the recent-signal panel (translate(16,16))
|
|
40
|
+
const panel = [...svg.querySelectorAll(':scope > g[transform^="translate(16, 16)"]')][0];
|
|
41
|
+
const linkRows = panel ? [...panel.querySelectorAll('text')].filter(t => /->.*\/.*\//.test(t.textContent || '')).length : 0;
|
|
42
|
+
return { emptyShown: !!empty, emptyText: empty?.textContent || null, linkRows };
|
|
43
|
+
});
|
|
44
|
+
await ctx.close();
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const noFlow = await probe({ msgs: [] });
|
|
49
|
+
const withFlow = await probe({ msgs: [
|
|
50
|
+
{ from_alias: 'a', to_alias: 'b', content: 'hello', created_at: new Date().toISOString() },
|
|
51
|
+
] });
|
|
52
|
+
await browser.close();
|
|
53
|
+
|
|
54
|
+
const results = {
|
|
55
|
+
emptyShowsPlaceholder: noFlow.emptyShown === true && /no flow yet/i.test(noFlow.emptyText || ''),
|
|
56
|
+
emptyHidesLinkRows: noFlow.linkRows === 0,
|
|
57
|
+
populatedHidesPlaceholder: withFlow.emptyShown === false,
|
|
58
|
+
populatedShowsLinkRows: withFlow.linkRows >= 1,
|
|
59
|
+
};
|
|
60
|
+
const ok = Object.values(results).every(Boolean);
|
|
61
|
+
console.log(`${ok ? '✅' : '❌'} recent-signal empty state:`, JSON.stringify(results),
|
|
62
|
+
`\n noFlow=`, noFlow, `\n withFlow=`, withFlow);
|
|
63
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* Round 36 verification: prefers-reduced-motion disables all SMIL
|
|
2
|
+
* animations in the topology. Round 29 zeroed CSS via media query;
|
|
3
|
+
* SVG <animate> / <animateMotion> elements need JS to opt out and
|
|
4
|
+
* this round wires that via the useReducedMotion hook. */
|
|
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({ reduce, layout }) {
|
|
12
|
+
const ctx = await browser.newContext({
|
|
13
|
+
viewport: { width: 1280, height: 1600 },
|
|
14
|
+
reducedMotion: reduce ? 'reduce' : 'no-preference',
|
|
15
|
+
});
|
|
16
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
17
|
+
await ctx.addInitScript((lay) => {
|
|
18
|
+
try {
|
|
19
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
20
|
+
localStorage.setItem('anet-topo-layout', lay);
|
|
21
|
+
localStorage.setItem('anet-topo-view', JSON.stringify({ zoom: 1, x: 0, y: 0 }));
|
|
22
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
23
|
+
} catch {}
|
|
24
|
+
}, layout);
|
|
25
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
26
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
27
|
+
const r = await route.fetch();
|
|
28
|
+
const b = await r.json();
|
|
29
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
30
|
+
// Include a 'working' status so the working pulse path renders.
|
|
31
|
+
const sessions = [
|
|
32
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null,
|
|
33
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
34
|
+
{ alias: 'idl', status: 'idle', 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
|
+
});
|
|
39
|
+
// Inject a tiny message so a flow link + particle render.
|
|
40
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
|
|
41
|
+
{ from_alias: 'wkr', to_alias: 'idl', content: 'hi', created_at: fresh },
|
|
42
|
+
] } }));
|
|
43
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
44
|
+
const page = await ctx.newPage();
|
|
45
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
46
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
|
|
47
|
+
await page.waitForTimeout(600);
|
|
48
|
+
// Click a node so the click ripple is invoked (it should NOT render under reduced motion).
|
|
49
|
+
await page.locator('g[data-node="wkr"]').click().catch(() => {});
|
|
50
|
+
await page.waitForTimeout(120);
|
|
51
|
+
const counts = await page.evaluate(() => {
|
|
52
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
53
|
+
return {
|
|
54
|
+
animate: svg.querySelectorAll('animate').length,
|
|
55
|
+
animateMotion: svg.querySelectorAll('animateMotion').length,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
await ctx.close();
|
|
59
|
+
return counts;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Baseline (no reduce) — many SMIL elements expected.
|
|
63
|
+
const normalRing = await probe({ reduce: false, layout: 'ring' });
|
|
64
|
+
// Reduced motion — every SMIL element should be gone.
|
|
65
|
+
const reducedRing = await probe({ reduce: true, layout: 'ring' });
|
|
66
|
+
const reducedGrid = await probe({ reduce: true, layout: 'grid' });
|
|
67
|
+
|
|
68
|
+
await browser.close();
|
|
69
|
+
const results = {
|
|
70
|
+
baselineHasAnimations: normalRing.animate + normalRing.animateMotion > 0,
|
|
71
|
+
reducedRingDropsAll: reducedRing.animate === 0 && reducedRing.animateMotion === 0,
|
|
72
|
+
reducedGridDropsAll: reducedGrid.animate === 0 && reducedGrid.animateMotion === 0,
|
|
73
|
+
};
|
|
74
|
+
const ok = Object.values(results).every(Boolean);
|
|
75
|
+
console.log(`${ok ? '✅' : '❌'} reduced motion:`, JSON.stringify(results),
|
|
76
|
+
`\n normalRing=`, normalRing, `\n reducedRing=`, reducedRing, `\n reducedGrid=`, reducedGrid);
|
|
77
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* Round 145 verification: R46 idle-spoke flow rate buckets on
|
|
2
|
+
* workingCount, completing the 4-layer busyness-driven motion
|
|
3
|
+
* family:
|
|
4
|
+
* R84 hub breath (centre)
|
|
5
|
+
* R131 outer-ring orbit (periphery)
|
|
6
|
+
* R132 groupbox march (per-team, grid layout)
|
|
7
|
+
* R145 idle-spoke flow (ring layout, hub→nodes) — new
|
|
8
|
+
*
|
|
9
|
+
* Bucket ladder shared across the four layers (same R84
|
|
10
|
+
* thresholds):
|
|
11
|
+
* workingCount === 0 → bucket 0
|
|
12
|
+
* workingCount in [1, 2] → bucket 1
|
|
13
|
+
* workingCount in [3, 5] → bucket 2
|
|
14
|
+
* workingCount >= 6 → bucket 3
|
|
15
|
+
*
|
|
16
|
+
* Idle-spoke durations per bucket: 2.8 / 2.4 / 2.0 / 1.6 seconds
|
|
17
|
+
* (R46 baseline 2.4s is bucket 1).
|
|
18
|
+
*
|
|
19
|
+
* Spokes only render in ring layout. Active spokes (carrying flow
|
|
20
|
+
* traffic) don't get the animation — they're solid bright strokes.
|
|
21
|
+
* Only idle spokes carry the .anet-topo-spoke-flow class and the
|
|
22
|
+
* --spoke-dur var. The test seeds 0 messages so all spokes are idle.
|
|
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
|
+
|
|
29
|
+
async function probe(workingCount, totalCount) {
|
|
30
|
+
const browser = await chromium.launch({ headless: true });
|
|
31
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
32
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
33
|
+
await ctx.addInitScript(() => {
|
|
34
|
+
try {
|
|
35
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
36
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
37
|
+
// ring layout is R145's target — it's also the default, but
|
|
38
|
+
// pinning explicitly defends against future default flips.
|
|
39
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
40
|
+
} catch {}
|
|
41
|
+
});
|
|
42
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
43
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
44
|
+
const r = await route.fetch();
|
|
45
|
+
const b = await r.json();
|
|
46
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
47
|
+
const sessions = [];
|
|
48
|
+
for (let i = 0; i < totalCount; i++) {
|
|
49
|
+
sessions.push({
|
|
50
|
+
alias: `node${i}`,
|
|
51
|
+
status: i < workingCount ? 'working' : 'idle',
|
|
52
|
+
model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
53
|
+
network_id: nid, project_dir: null,
|
|
54
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
58
|
+
});
|
|
59
|
+
// 0 messages → no active spokes → every spoke is idle (.anet-topo-spoke-flow)
|
|
60
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
61
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
62
|
+
|
|
63
|
+
const page = await ctx.newPage();
|
|
64
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
65
|
+
await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n, totalCount, { timeout: 30000 });
|
|
66
|
+
await page.waitForTimeout(400);
|
|
67
|
+
|
|
68
|
+
const out = await page.evaluate(() => {
|
|
69
|
+
const spokes = [...document.querySelectorAll('path.anet-topo-spoke-flow')];
|
|
70
|
+
return spokes.map(p => ({
|
|
71
|
+
bucket: p.getAttribute('data-topo-spoke-bucket'),
|
|
72
|
+
dur: p.getAttribute('data-topo-spoke-dur'),
|
|
73
|
+
// Resolved animation-duration from computed style — confirms
|
|
74
|
+
// the CSS var bridge through to the cascade.
|
|
75
|
+
cssDur: getComputedStyle(p).getPropertyValue('animation-duration').trim(),
|
|
76
|
+
}));
|
|
77
|
+
});
|
|
78
|
+
await browser.close();
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// One probe per bucket
|
|
83
|
+
const b0 = await probe(0, 4); // 0 working / 4 idle → bucket 0 (2.8s)
|
|
84
|
+
const b1 = await probe(2, 4); // 2 working / 2 idle → bucket 1 (2.4s)
|
|
85
|
+
const b2 = await probe(4, 5); // 4 working / 1 idle → bucket 2 (2.0s)
|
|
86
|
+
const b3 = await probe(7, 8); // 7 working / 1 idle → bucket 3 (1.6s)
|
|
87
|
+
|
|
88
|
+
const consistent = (spokes, bucket, dur) =>
|
|
89
|
+
spokes.length > 0 &&
|
|
90
|
+
spokes.every(s => s.bucket === String(bucket) && s.dur === String(dur) && s.cssDur === `${dur}s`);
|
|
91
|
+
|
|
92
|
+
const results = {
|
|
93
|
+
b0_someSpokes: b0.length > 0,
|
|
94
|
+
b0_bucket0_2_8s: consistent(b0, 0, 2.8),
|
|
95
|
+
b1_bucket1_2_4s: consistent(b1, 1, 2.4),
|
|
96
|
+
b2_bucket2_2_0s: consistent(b2, 2, 2.0),
|
|
97
|
+
b3_bucket3_1_6s: consistent(b3, 3, 1.6),
|
|
98
|
+
// dur strictly decreases as busy climbs
|
|
99
|
+
dur_monotonic:
|
|
100
|
+
Number(b0[0]?.dur) > Number(b1[0]?.dur) &&
|
|
101
|
+
Number(b1[0]?.dur) > Number(b2[0]?.dur) &&
|
|
102
|
+
Number(b2[0]?.dur) > Number(b3[0]?.dur),
|
|
103
|
+
};
|
|
104
|
+
const ok = Object.values(results).every(Boolean);
|
|
105
|
+
console.log(`${ok ? '✅' : '❌'} spoke busyness:`, JSON.stringify(results),
|
|
106
|
+
`\n b0(0w/4)=`, b0.slice(0,1),
|
|
107
|
+
`\n b1(2w/4)=`, b1.slice(0,1),
|
|
108
|
+
`\n b2(4w/5)=`, b2.slice(0,1),
|
|
109
|
+
`\n b3(7w/8)=`, b3.slice(0,1));
|
|
110
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Round 79 verification: header working + online count chips become
|
|
2
|
+
* hover affordances (sister to R77 active-links chip).
|
|
3
|
+
* - Hover "N working" → setHoveredStatus('working') → idle + offline
|
|
4
|
+
* nodes dim, working stays bright. Same outcome as R55 legend row
|
|
5
|
+
* hover, just from the chip surface.
|
|
6
|
+
* - Hover "N online" with working present → same as above.
|
|
7
|
+
* - Mouseleave restores baseline.
|
|
8
|
+
*
|
|
9
|
+
* Fleet: 1 working (wkr), 2 idle (idl1, idl2), 1 offline (off).
|
|
10
|
+
*/
|
|
11
|
+
import { chromium } from 'playwright';
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
|
|
14
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
15
|
+
const browser = await chromium.launch({ headless: true });
|
|
16
|
+
|
|
17
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
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-topo-layout', 'ring');
|
|
23
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
24
|
+
sessionStorage.removeItem('anet-topo-pinned-status');
|
|
25
|
+
sessionStorage.removeItem('anet-topo-pinned-group');
|
|
26
|
+
} catch {}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
30
|
+
const stale = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
31
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
32
|
+
const r = await route.fetch();
|
|
33
|
+
const b = await r.json();
|
|
34
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
35
|
+
const sessions = [
|
|
36
|
+
{ alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
37
|
+
{ alias: 'idl1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
38
|
+
{ alias: 'idl2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
|
|
39
|
+
{ alias: 'off', status: 'offline', network_id: nid, project_dir: null, created_at: stale, updated_at: stale, last_seen_at: stale },
|
|
40
|
+
];
|
|
41
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
42
|
+
});
|
|
43
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
44
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
45
|
+
const page = await ctx.newPage();
|
|
46
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
47
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
48
|
+
await page.waitForTimeout(500);
|
|
49
|
+
|
|
50
|
+
const opacities = () => page.evaluate(() => {
|
|
51
|
+
const o = {};
|
|
52
|
+
for (const a of ['wkr', 'idl1', 'idl2', 'off']) {
|
|
53
|
+
const g = document.querySelector(`g[data-node="${a}"]`);
|
|
54
|
+
o[a] = g ? +(g.style.opacity || '1') : null;
|
|
55
|
+
}
|
|
56
|
+
return o;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const before = await opacities();
|
|
60
|
+
|
|
61
|
+
// Hover working chip
|
|
62
|
+
await page.locator('[data-working-chip]').hover();
|
|
63
|
+
await page.waitForTimeout(250);
|
|
64
|
+
const onWorking = await opacities();
|
|
65
|
+
|
|
66
|
+
// Move away
|
|
67
|
+
await page.mouse.move(10, 10);
|
|
68
|
+
await page.waitForTimeout(250);
|
|
69
|
+
const afterWorking = await opacities();
|
|
70
|
+
|
|
71
|
+
// Hover online chip (working present → routes to 'working')
|
|
72
|
+
await page.locator('[data-online-chip]').hover();
|
|
73
|
+
await page.waitForTimeout(250);
|
|
74
|
+
const onOnline = await opacities();
|
|
75
|
+
|
|
76
|
+
await page.mouse.move(10, 10);
|
|
77
|
+
await page.waitForTimeout(250);
|
|
78
|
+
const afterOnline = await opacities();
|
|
79
|
+
|
|
80
|
+
await browser.close();
|
|
81
|
+
|
|
82
|
+
const bright = (v) => v != null && v >= 0.55;
|
|
83
|
+
const dim = (v) => v != null && v < 0.4;
|
|
84
|
+
const results = {
|
|
85
|
+
before_baseline: bright(before.wkr) && bright(before.idl1) && bright(before.idl2),
|
|
86
|
+
working_keepsWkrBright: bright(onWorking.wkr),
|
|
87
|
+
working_dimsIdle: dim(onWorking.idl1) && dim(onWorking.idl2),
|
|
88
|
+
working_dimsOffline: dim(onWorking.off),
|
|
89
|
+
workingLeave_restoresAll: bright(afterWorking.wkr) && bright(afterWorking.idl1),
|
|
90
|
+
online_keepsWkrBright: bright(onOnline.wkr), // routes to working filter
|
|
91
|
+
online_dimsIdle: dim(onOnline.idl1) && dim(onOnline.idl2),
|
|
92
|
+
onlineLeave_restoresAll: bright(afterOnline.wkr) && bright(afterOnline.idl1),
|
|
93
|
+
};
|
|
94
|
+
const ok = Object.values(results).every(Boolean);
|
|
95
|
+
console.log(`${ok ? '✅' : '❌'} status-chip hover:`, JSON.stringify(results),
|
|
96
|
+
`\n before=`, before,
|
|
97
|
+
`\n onWorking=`, onWorking,
|
|
98
|
+
`\n afterWorking=`, afterWorking,
|
|
99
|
+
`\n onOnline=`, onOnline,
|
|
100
|
+
`\n afterOnline=`, afterOnline);
|
|
101
|
+
process.exit(ok ? 0 : 1);
|