@sleep2agi/agent-network-dashboard 0.4.5-preview.9 → 0.4.5-preview.91
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/.mcp.json +11 -0
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +48 -48
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page/next-font-manifest.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +4 -4
- 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/next-font-manifest.json +1 -1
- package/.next/server/app/admin/page.js.nft.json +1 -1
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +4 -4
- package/.next/server/app/admin.rsc +15 -15
- package/.next/server/app/admin.segments/_full.segment.rsc +15 -15
- 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 +3 -3
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/index.html +4 -4
- package/.next/server/app/index.rsc +15 -15
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +15 -15
- 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 +3 -3
- package/.next/server/app/login/page/next-font-manifest.json +1 -1
- package/.next/server/app/login/page.js.nft.json +1 -1
- 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 +15 -15
- package/.next/server/app/login.segments/_full.segment.rsc +15 -15
- 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 +3 -3
- 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/next-font-manifest.json +1 -1
- package/.next/server/app/logs/page.js.nft.json +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +4 -4
- package/.next/server/app/logs.rsc +15 -15
- package/.next/server/app/logs.segments/_full.segment.rsc +15 -15
- 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 +3 -3
- 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/next-font-manifest.json +1 -1
- package/.next/server/app/messages/page.js.nft.json +1 -1
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +4 -4
- package/.next/server/app/messages.rsc +15 -15
- package/.next/server/app/messages.segments/_full.segment.rsc +15 -15
- 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 +3 -3
- 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/next-font-manifest.json +1 -1
- package/.next/server/app/node/page.js.nft.json +1 -1
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +4 -4
- package/.next/server/app/node.rsc +15 -15
- package/.next/server/app/node.segments/_full.segment.rsc +15 -15
- 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 +3 -3
- 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/next-font-manifest.json +1 -1
- package/.next/server/app/nodes/page.js.nft.json +1 -1
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +4 -4
- package/.next/server/app/nodes.rsc +15 -15
- package/.next/server/app/nodes.segments/_full.segment.rsc +15 -15
- 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 +3 -3
- 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/next-font-manifest.json +1 -1
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page/next-font-manifest.json +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +4 -4
- package/.next/server/app/server-logs.rsc +15 -15
- package/.next/server/app/server-logs.segments/_full.segment.rsc +15 -15
- 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 +3 -3
- 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/next-font-manifest.json +1 -1
- package/.next/server/app/settings/networks/page.js.nft.json +1 -1
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +4 -4
- package/.next/server/app/settings/networks.rsc +15 -15
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +15 -15
- 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 +3 -3
- 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/next-font-manifest.json +1 -1
- package/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page/next-font-manifest.json +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +4 -4
- package/.next/server/app/settings/tokens.rsc +15 -15
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +15 -15
- 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 +3 -3
- 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 +4 -4
- package/.next/server/app/settings.rsc +15 -15
- package/.next/server/app/settings.segments/_full.segment.rsc +15 -15
- 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 +3 -3
- 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/next-font-manifest.json +1 -1
- package/.next/server/app/tasks/[id]/page.js.nft.json +1 -1
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page/next-font-manifest.json +1 -1
- package/.next/server/app/tasks/page.js.nft.json +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +4 -4
- package/.next/server/app/tasks.rsc +15 -15
- package/.next/server/app/tasks.segments/_full.segment.rsc +15 -15
- 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 +3 -3
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0jl4ukn._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0jl4ukn._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0n46s~k._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0n46s~k._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0w2ay2h._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0w2ay2h._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js.map +1 -1
- package/.next/server/chunks/ssr/_0nne7mz._.js +3 -0
- package/.next/server/chunks/ssr/_0nne7mz._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0.zbtn5._.js +9 -0
- package/.next/server/chunks/ssr/app_0.zbtn5._.js.map +1 -0
- package/.next/server/chunks/ssr/app_01y-~mp._.js +3 -0
- package/.next/server/chunks/ssr/app_01y-~mp._.js.map +1 -0
- package/.next/server/chunks/ssr/app_04n78pr._.js +3 -0
- package/.next/server/chunks/ssr/app_04n78pr._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0_agk0r._.js +3 -0
- package/.next/server/chunks/ssr/app_0_agk0r._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0aenxef._.js +3 -0
- package/.next/server/chunks/ssr/app_0aenxef._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0aum_g5._.js +3 -0
- package/.next/server/chunks/ssr/app_0aum_g5._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0emvkn2._.js +3 -0
- package/.next/server/chunks/ssr/app_0emvkn2._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0esuljp._.js +3 -0
- package/.next/server/chunks/ssr/app_0esuljp._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0izi28j._.js +1 -1
- package/.next/server/chunks/ssr/app_0izi28j._.js.map +1 -1
- package/.next/server/chunks/ssr/app_components_08ys5y3._.js +5 -5
- package/.next/server/chunks/ssr/app_components_08ys5y3._.js.map +1 -1
- package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js +1 -1
- package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js.map +1 -1
- package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js +1 -1
- package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/next-font-manifest.js +1 -1
- package/.next/server/next-font-manifest.json +14 -14
- package/.next/server/pages/404.html +4 -4
- 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-hlcuoemhe7w.js +7 -0
- package/.next/static/chunks/02_zpo7taex9o.js +7 -0
- package/.next/static/chunks/03b6jkic-1r9u.css +2 -0
- package/.next/static/chunks/06fliypy0.f0r.js +1 -0
- package/.next/static/chunks/06n3d~fbbtagk.js +1 -0
- package/.next/static/chunks/07eba1lz2u3p4.js +1 -0
- package/.next/static/chunks/0av2~0.96~d~g.js +1 -0
- package/.next/static/chunks/{07okhs5561y5y.js → 0c1bp~g7.8ru..js} +1 -1
- package/.next/static/chunks/0g9aof0ob_fcj.js +1 -0
- package/.next/static/chunks/0gtbwcilfu6b7.js +1 -0
- package/.next/static/chunks/0s8jo6jypzkx5.js +1 -0
- package/.next/static/chunks/0ubqqhhpsaez4.js +1 -0
- package/.next/static/chunks/0vmfu_gz9tp4f.js +1 -0
- package/.next/static/chunks/0~qw9kxksr1v2.js +1 -0
- package/.next/static/chunks/12gprw3l0kvvf.js +1 -0
- package/.next/static/chunks/12mjtyh-j74~k.js +1 -0
- package/.next/static/chunks/12yuo353giomx.js +1 -0
- package/.next/static/media/4fa387ec64143e14-s.0.qu-9752pffj.woff2 +0 -0
- package/.next/static/media/5ce348bf30bf5439-s.0ee55_hj9qcer.woff2 +0 -0
- package/.next/static/media/6306c77e7c8268e4-s.0mao5jbfbduzp.woff2 +0 -0
- package/.next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2 +0 -0
- package/.next/static/media/7d817b4c03b0c5f1-s.0uzt.a6d44yda.woff2 +0 -0
- package/.next/static/media/bbc41e54d2fcbd21-s.0mvwgmnhv29no.woff2 +0 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/admin/page.tsx +97 -27
- package/app/components/AgentCard.tsx +19 -5
- package/app/components/BroadcastBar.tsx +22 -4
- package/app/components/DispatchPanel.tsx +9 -5
- package/app/components/EmptyState.tsx +127 -12
- package/app/components/HealthBanner.tsx +11 -2
- package/app/components/HelpOverlay.tsx +50 -10
- package/app/components/InboxPanel.tsx +7 -4
- package/app/components/Sidebar.tsx +81 -44
- package/app/components/StatsBar.tsx +59 -36
- package/app/components/TaskChatPanel.tsx +8 -10
- package/app/components/TaskDrawer.tsx +72 -28
- package/app/components/TopoGraph.tsx +527 -41
- package/app/components/utils.ts +13 -0
- package/app/globals.css +90 -0
- package/app/lib/status.ts +85 -0
- package/app/lib/version.ts +7 -0
- package/app/login/page.tsx +40 -7
- package/app/logs/page.tsx +70 -21
- package/app/messages/page.tsx +34 -9
- package/app/node/page.tsx +52 -27
- package/app/nodes/page.tsx +45 -21
- package/app/page.tsx +103 -32
- package/app/server-logs/page.tsx +104 -28
- package/app/settings/networks/page.tsx +40 -16
- package/app/settings/page.tsx +39 -11
- package/app/tasks/[id]/page.tsx +2 -13
- package/app/tasks/page.tsx +66 -73
- package/package.json +1 -1
- package/public/intern_avatar.png +0 -0
- package/scripts/empty-state-test.mjs +41 -0
- package/scripts/fold-only.mjs +47 -0
- package/scripts/intern-alias-test.mjs +46 -0
- package/scripts/loop-review.mjs +270 -0
- package/scripts/network-derive-shot.mjs +16 -0
- package/scripts/network-derive-test.mjs +28 -0
- package/scripts/topo-brand-shot.mjs +61 -0
- package/scripts/topo-cluster-test.mjs +41 -0
- package/scripts/topo-controls-shot.mjs +24 -0
- package/scripts/topo-group-test.mjs +37 -0
- package/scripts/topo-inspect.mjs +56 -0
- package/scripts/topo-interaction-test.mjs +80 -0
- package/scripts/topo-mock-large.mjs +58 -0
- package/scripts/topo-r104-test.mjs +54 -0
- package/scripts/topo-shot.mjs +31 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__06w_u7t._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__06w_u7t._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0ehu0jb._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0ehu0jb._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0nrckjd._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0nrckjd._.js.map +0 -1
- package/.next/server/chunks/ssr/_13_v7-3._.js +0 -3
- package/.next/server/chunks/ssr/_13_v7-3._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0.0wpa~._.js +0 -3
- package/.next/server/chunks/ssr/app_0.0wpa~._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0ap_oiq._.js +0 -3
- package/.next/server/chunks/ssr/app_0ap_oiq._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0dk5wao._.js +0 -3
- package/.next/server/chunks/ssr/app_0dk5wao._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0et5yua._.js +0 -3
- package/.next/server/chunks/ssr/app_0et5yua._.js.map +0 -1
- package/.next/server/chunks/ssr/app_10b5blc._.js +0 -9
- package/.next/server/chunks/ssr/app_10b5blc._.js.map +0 -1
- package/.next/server/chunks/ssr/app_11vl967._.js +0 -3
- package/.next/server/chunks/ssr/app_11vl967._.js.map +0 -1
- package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js +0 -3
- package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js.map +0 -1
- package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js +0 -3
- package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js.map +0 -1
- package/.next/static/chunks/0-.hjh--kcdiy.js +0 -1
- package/.next/static/chunks/02afyxgpty3uf.js +0 -1
- package/.next/static/chunks/02flrewxlf_m_.js +0 -1
- package/.next/static/chunks/051gslfjka1kj.js +0 -1
- package/.next/static/chunks/07oobuohrpigy.js +0 -1
- package/.next/static/chunks/09al3qm76q7nd.js +0 -1
- package/.next/static/chunks/0e7c40p~wq665.js +0 -1
- package/.next/static/chunks/0g7m7zk.v7g6h.js +0 -1
- package/.next/static/chunks/0h.ajsk_uxigl.js +0 -1
- package/.next/static/chunks/0msa~bub38ytf.css +0 -2
- package/.next/static/chunks/0ogeals5ufhho.js +0 -1
- package/.next/static/chunks/10ir.g.n~hre0.js +0 -7
- package/.next/static/chunks/10njj7kn9ylkf.js +0 -1
- package/.next/static/chunks/12dcb86elsl-e.js +0 -7
- package/.next/static/chunks/13.dst7liziwo.js +0 -1
- package/.next/static/chunks/132e__998_q9r.js +0 -1
- package/.next/static/media/4fa387ec64143e14-s.0q3udbd2bu5yp.woff2 +0 -0
- package/.next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.woff2 +0 -0
- package/.next/static/media/bbc41e54d2fcbd21-s.0gw~uztddq1df.woff2 +0 -0
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/next.svg +0 -1
- package/public/vercel.svg +0 -1
- package/public/window.svg +0 -1
- /package/.next/static/{stSZtBF9uGYc3L399m0h1 → u-HIIfy-O0UVQ02APphkX}/_buildManifest.js +0 -0
- /package/.next/static/{stSZtBF9uGYc3L399m0h1 → u-HIIfy-O0UVQ02APphkX}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{stSZtBF9uGYc3L399m0h1 → u-HIIfy-O0UVQ02APphkX}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
const TOKEN = process.env.LOOP_REVIEW_TOKEN;
|
|
3
|
+
const browser = await chromium.launch({ headless: true });
|
|
4
|
+
|
|
5
|
+
async function shoot(name, { emptyStats }) {
|
|
6
|
+
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
|
|
7
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
8
|
+
await ctx.addInitScript(() => { try { localStorage.setItem('anet-theme','cyber'); sessionStorage.setItem('anet_v3_auth','1'); } catch {} });
|
|
9
|
+
// force 0 sessions
|
|
10
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
11
|
+
const r = await route.fetch(); const b = await r.json();
|
|
12
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [] } });
|
|
13
|
+
});
|
|
14
|
+
if (emptyStats) {
|
|
15
|
+
// also zero out task history → true first-run
|
|
16
|
+
await ctx.route('**/api/hub/stats*', async (route) => {
|
|
17
|
+
const r = await route.fetch(); const b = await r.json();
|
|
18
|
+
await route.fulfill({ response: r, json: { ...b, tasks: { by_status: [] } } });
|
|
19
|
+
});
|
|
20
|
+
await ctx.route('**/api/hub/tasks*', async (route) => {
|
|
21
|
+
const r = await route.fetch(); const b = await r.json();
|
|
22
|
+
await route.fulfill({ response: r, json: { ...b, tasks: [] } });
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const page = await ctx.newPage();
|
|
26
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
27
|
+
await page.waitForTimeout(3000);
|
|
28
|
+
// find the empty-state card text
|
|
29
|
+
const txt = await page.evaluate(() => {
|
|
30
|
+
const h3 = [...document.querySelectorAll('h3')].map(h => h.textContent?.trim());
|
|
31
|
+
return h3.filter(t => /agent/i.test(t || ''));
|
|
32
|
+
});
|
|
33
|
+
console.log(name, '→ empty-state headline(s):', JSON.stringify(txt));
|
|
34
|
+
await page.screenshot({ path: `/tmp/anet-issue-50/empty-${name}.png`, fullPage: true });
|
|
35
|
+
await ctx.close();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await shoot('with-history', { emptyStats: false }); // 0 sessions, real task history → "No agents online"
|
|
39
|
+
await shoot('true-firstrun', { emptyStats: true }); // 0 sessions, 0 tasks → "Spin up your first agent"
|
|
40
|
+
await browser.close();
|
|
41
|
+
console.log('done');
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/* Captures the fold (viewport-only screenshot) for a given page so we
|
|
2
|
+
* can inspect what the user actually sees on first paint. */
|
|
3
|
+
|
|
4
|
+
import { chromium } from 'playwright';
|
|
5
|
+
import { mkdirSync } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
const TOKEN = process.env.LOOP_REVIEW_TOKEN;
|
|
9
|
+
const OUT = path.resolve(process.cwd(), 'test-results/loop-review/folds');
|
|
10
|
+
mkdirSync(OUT, { recursive: true });
|
|
11
|
+
|
|
12
|
+
const targets = [
|
|
13
|
+
{ name: 'tasks-cyber-desktop', path: '/tasks', theme: 'cyber', w: 1440, h: 900 },
|
|
14
|
+
{ name: 'tasks-cyber-mobile', path: '/tasks', theme: 'cyber', w: 390, h: 844 },
|
|
15
|
+
{ name: 'nodes-cyber-desktop', path: '/nodes', theme: 'cyber', w: 1440, h: 900 },
|
|
16
|
+
{ name: 'nodes-cyber-mobile', path: '/nodes', theme: 'cyber', w: 390, h: 844 },
|
|
17
|
+
{ name: 'overview-cyber-desktop', path: '/', theme: 'cyber', w: 1440, h: 900 },
|
|
18
|
+
{ name: 'overview-light-desktop', path: '/', theme: 'light', w: 1440, h: 900 },
|
|
19
|
+
{ name: 'server-logs-cyber-desktop', path: '/server-logs', theme: 'cyber', w: 1440, h: 900 },
|
|
20
|
+
{ name: 'server-logs-cyber-mobile', path: '/server-logs', theme: 'cyber', w: 390, h: 844 },
|
|
21
|
+
{ name: 'admin-cyber-desktop', path: '/admin', theme: 'cyber', w: 1440, h: 900 },
|
|
22
|
+
{ name: 'admin-light-desktop', path: '/admin', theme: 'light', w: 1440, h: 900 },
|
|
23
|
+
{ name: 'tasks-light-desktop', path: '/tasks', theme: 'light', w: 1440, h: 900 },
|
|
24
|
+
{ name: 'server-logs-light-desktop', path: '/server-logs', theme: 'light', w: 1440, h: 900 },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const browser = await chromium.launch({ headless: true });
|
|
28
|
+
for (const t of targets) {
|
|
29
|
+
const ctx = await browser.newContext({ viewport: { width: t.w, height: t.h } });
|
|
30
|
+
await ctx.addCookies([{
|
|
31
|
+
name: 'anet_dashboard_session',
|
|
32
|
+
value: `v3:${TOKEN}`,
|
|
33
|
+
domain: '127.0.0.1', path: '/',
|
|
34
|
+
}]);
|
|
35
|
+
await ctx.addInitScript(theme => {
|
|
36
|
+
try { localStorage.setItem('anet-theme', theme); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
37
|
+
}, t.theme);
|
|
38
|
+
const page = await ctx.newPage();
|
|
39
|
+
await page.goto(`http://127.0.0.1:3000${t.path}`, { waitUntil: 'domcontentloaded' });
|
|
40
|
+
await page.waitForTimeout(2500);
|
|
41
|
+
await page.evaluate(theme => document.documentElement.setAttribute('data-theme', theme), t.theme);
|
|
42
|
+
await page.waitForTimeout(300);
|
|
43
|
+
await page.screenshot({ path: path.join(OUT, `${t.name}.png`), fullPage: false, animations: 'disabled' });
|
|
44
|
+
await ctx.close();
|
|
45
|
+
}
|
|
46
|
+
await browser.close();
|
|
47
|
+
console.log('captured', targets.length, 'fold-only screenshots to', OUT);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
const TOKEN = process.env.LOOP_REVIEW_TOKEN;
|
|
3
|
+
const browser = await chromium.launch({ headless: true });
|
|
4
|
+
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
|
|
5
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
6
|
+
await ctx.addInitScript(() => { try { localStorage.setItem('anet-theme','cyber'); localStorage.removeItem('anet-brand'); localStorage.removeItem('anet-topo-view'); sessionStorage.setItem('anet_v3_auth','1'); } catch {} });
|
|
7
|
+
// inject a 书生 fleet alongside the real sessions
|
|
8
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
9
|
+
const r = await route.fetch(); const b = await r.json();
|
|
10
|
+
const real = b.sessions || [];
|
|
11
|
+
const fleet = Array.from({length: 8}, (_, i) => ({
|
|
12
|
+
alias: `书生${i+1}号`, status: i % 3 === 0 ? 'working' : 'idle',
|
|
13
|
+
network_id: real[0]?.network_id || 'default',
|
|
14
|
+
created_at:'2026-05-14T00:00:00Z', updated_at:'2026-05-14T00:00:00Z', last_seen_at:'2026-05-14T00:00:00Z',
|
|
15
|
+
}));
|
|
16
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [...real, ...fleet] } });
|
|
17
|
+
});
|
|
18
|
+
const page = await ctx.newPage();
|
|
19
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' }); // NO ?brand=intern
|
|
20
|
+
await page.waitForFunction(() => {
|
|
21
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
22
|
+
return !!svg && svg.querySelectorAll('circle[r="26"]').length >= 14;
|
|
23
|
+
}, { timeout: 30000 }).catch(() => {});
|
|
24
|
+
await page.waitForTimeout(1000);
|
|
25
|
+
const result = await page.evaluate(() => {
|
|
26
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
27
|
+
const nodeGs = [...svg.querySelectorAll('g')].filter(g =>
|
|
28
|
+
g.querySelector(':scope > circle[r="26"], :scope > circle[r="18"]') &&
|
|
29
|
+
g.querySelector(':scope > g > text[font-weight="700"]'));
|
|
30
|
+
let internWithImage = 0, internWithoutImage = 0, nonInternWithImage = 0, nonInternGeneric = 0;
|
|
31
|
+
const samples = [];
|
|
32
|
+
nodeGs.forEach(g => {
|
|
33
|
+
const alias = g.querySelector(':scope > g > text[font-weight="700"]').textContent.trim();
|
|
34
|
+
const hasImage = !!g.querySelector(':scope > image[href*="intern_avatar"]');
|
|
35
|
+
const isIntern = /书生|书小生|intern/i.test(alias);
|
|
36
|
+
if (isIntern && hasImage) internWithImage++;
|
|
37
|
+
else if (isIntern && !hasImage) { internWithoutImage++; samples.push('MISS:'+alias); }
|
|
38
|
+
else if (!isIntern && hasImage) { nonInternWithImage++; samples.push('LEAK:'+alias); }
|
|
39
|
+
else nonInternGeneric++;
|
|
40
|
+
});
|
|
41
|
+
return { internWithImage, internWithoutImage, nonInternWithImage, nonInternGeneric, samples };
|
|
42
|
+
});
|
|
43
|
+
console.log(JSON.stringify(result, null, 1));
|
|
44
|
+
await page.locator('section:has(h2:text("Command mesh"))').screenshot({ path: '/tmp/anet-issue-50/intern-alias-cyber.png' });
|
|
45
|
+
await browser.close();
|
|
46
|
+
console.log('done');
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/* Loop self-review — captures Overview in cyber+light × desktop+mobile,
|
|
2
|
+
* runs a few observable checks, prints a per-axis score. Not a regression
|
|
3
|
+
* test — the cron's "review and score then propose next round" output.
|
|
4
|
+
*
|
|
5
|
+
* Round 71: replaced the toy checks (overflow, sidebar visible, blur count)
|
|
6
|
+
* with checks that actually catch the bugs we keep shipping:
|
|
7
|
+
* - CTA-above-fold: does the primary "Spin up" / "Get started" / etc
|
|
8
|
+
* button live within the first viewport? (deduct 3)
|
|
9
|
+
* - Zero-card density: how many "0" stat cards are crammed in above the
|
|
10
|
+
* CTA? (deduct 1 per zero-card beyond the first 2)
|
|
11
|
+
* - Whitespace gap: any element-to-element vertical gap >200px in the
|
|
12
|
+
* top half? (deduct 1)
|
|
13
|
+
* - Misleading-copy: "All systems go" text rendered when nodes.total=0
|
|
14
|
+
* (deduct 2) — round-70-class bug guard
|
|
15
|
+
* - Old: theme, horizontal overflow, sidebar, glow tell — still in
|
|
16
|
+
*
|
|
17
|
+
* Score is 10 max; deductions accumulate. The pre-r70 design that scored
|
|
18
|
+
* 10/10 here should now score ~4–5.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { chromium } from 'playwright';
|
|
22
|
+
import { mkdirSync } from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
|
|
25
|
+
const BASE = process.env.LOOP_REVIEW_BASE || 'http://127.0.0.1:3000';
|
|
26
|
+
const TOKEN = process.env.LOOP_REVIEW_TOKEN; // utok_...
|
|
27
|
+
const OUT = path.resolve(process.cwd(), 'test-results/loop-review');
|
|
28
|
+
mkdirSync(OUT, { recursive: true });
|
|
29
|
+
|
|
30
|
+
if (!TOKEN) {
|
|
31
|
+
console.error('LOOP_REVIEW_TOKEN env var required (utok_...)');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const viewports = [
|
|
36
|
+
{ tag: 'desktop', width: 1440, height: 900 },
|
|
37
|
+
{ tag: 'mobile', width: 390, height: 844 },
|
|
38
|
+
];
|
|
39
|
+
const themes = ['cyber', 'light'];
|
|
40
|
+
const pages = [
|
|
41
|
+
{ name: 'overview', path: '/' },
|
|
42
|
+
{ name: 'nodes', path: '/nodes' },
|
|
43
|
+
{ name: 'tasks', path: '/tasks' },
|
|
44
|
+
{ name: 'messages', path: '/messages' },
|
|
45
|
+
{ name: 'admin', path: '/admin' },
|
|
46
|
+
{ name: 'settings', path: '/settings' },
|
|
47
|
+
{ name: 'networks', path: '/settings/networks' },
|
|
48
|
+
{ name: 'logs', path: '/logs' },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const matrix = [];
|
|
52
|
+
for (const t of themes) for (const v of viewports) for (const p of pages) {
|
|
53
|
+
matrix.push({ theme: t, viewport: { width: v.width, height: v.height }, tag: v.tag, pageName: p.name, pagePath: p.path });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const browser = await chromium.launch({ headless: true });
|
|
57
|
+
|
|
58
|
+
const report = [];
|
|
59
|
+
|
|
60
|
+
for (const m of matrix) {
|
|
61
|
+
const ctx = await browser.newContext({
|
|
62
|
+
viewport: m.viewport,
|
|
63
|
+
deviceScaleFactor: 1,
|
|
64
|
+
});
|
|
65
|
+
await ctx.addCookies([{
|
|
66
|
+
name: 'anet_dashboard_session',
|
|
67
|
+
value: `v3:${TOKEN}`,
|
|
68
|
+
domain: '127.0.0.1',
|
|
69
|
+
path: '/',
|
|
70
|
+
}]);
|
|
71
|
+
await ctx.addInitScript((theme) => {
|
|
72
|
+
try {
|
|
73
|
+
localStorage.setItem('anet-theme', theme);
|
|
74
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
75
|
+
} catch {}
|
|
76
|
+
}, m.theme);
|
|
77
|
+
|
|
78
|
+
const page = await ctx.newPage();
|
|
79
|
+
await page.goto(`${BASE}${m.pagePath}`, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
80
|
+
await page.waitForTimeout(1200);
|
|
81
|
+
|
|
82
|
+
await page.evaluate((t) => document.documentElement.setAttribute('data-theme', t), m.theme);
|
|
83
|
+
await page.waitForTimeout(300);
|
|
84
|
+
|
|
85
|
+
const file = path.join(OUT, `${m.pageName}-${m.theme}-${m.tag}.png`);
|
|
86
|
+
// animations: 'disabled' so polling/spinner-induced render churn does
|
|
87
|
+
// not stall the screenshot when the dash is populated.
|
|
88
|
+
await page.screenshot({ path: file, fullPage: true, animations: 'disabled', timeout: 60000 });
|
|
89
|
+
|
|
90
|
+
const checks = await page.evaluate(() => {
|
|
91
|
+
const doc = document.documentElement;
|
|
92
|
+
const theme = doc.getAttribute('data-theme');
|
|
93
|
+
const body = document.body;
|
|
94
|
+
const bodyBg = getComputedStyle(body).backgroundColor;
|
|
95
|
+
const bodyFg = getComputedStyle(body).color;
|
|
96
|
+
|
|
97
|
+
const viewportH = window.innerHeight;
|
|
98
|
+
const viewportW = window.innerWidth;
|
|
99
|
+
|
|
100
|
+
// --- CTA-above-fold ---
|
|
101
|
+
// We treat any of these texts as the page's primary CTA:
|
|
102
|
+
// "Spin up your first agent" — empty Overview onboarding
|
|
103
|
+
// "Get started" — generic
|
|
104
|
+
// "Dispatch" (when not empty) — populated state primary action
|
|
105
|
+
const ctaTextRe = /Spin up your first agent|Get started/i;
|
|
106
|
+
const ctaCandidates = [...document.querySelectorAll('h1, h2, h3, p, div, button, a')]
|
|
107
|
+
.filter(el => ctaTextRe.test(el.textContent || ''));
|
|
108
|
+
let ctaTop = null;
|
|
109
|
+
if (ctaCandidates.length) {
|
|
110
|
+
// Pick the smallest (innermost) element matching to avoid catching a wrapping section
|
|
111
|
+
const sorted = ctaCandidates.sort((a, b) => (a.textContent?.length || 0) - (b.textContent?.length || 0));
|
|
112
|
+
const r = sorted[0].getBoundingClientRect();
|
|
113
|
+
ctaTop = r.top;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- Zero-card density ---
|
|
117
|
+
// Count visible elements whose trimmed text === "0". These are usually
|
|
118
|
+
// big stat-card values dominating prime real estate.
|
|
119
|
+
const zeroCards = [...document.querySelectorAll('div, span')]
|
|
120
|
+
.filter(el => {
|
|
121
|
+
const t = el.textContent?.trim();
|
|
122
|
+
if (t !== '0' && t !== '0/0') return false;
|
|
123
|
+
const r = el.getBoundingClientRect();
|
|
124
|
+
return r.top >= 0 && r.top < viewportH && r.width > 20 && r.height > 20;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// --- Whitespace gap ---
|
|
128
|
+
// Look for sibling-to-sibling top gaps >200px in the page content
|
|
129
|
+
// container. AppShell wraps page children in
|
|
130
|
+
// <main><HealthBanner/><div><div class="min-h-screen ...">{page}</div></div></main>,
|
|
131
|
+
// so we walk into that inner page wrapper to read real section gaps.
|
|
132
|
+
const main = document.querySelector('main');
|
|
133
|
+
const inner = main?.querySelector(':scope > div:last-of-type > div') || main || document.body;
|
|
134
|
+
const directKids = [...inner.children].filter(el => {
|
|
135
|
+
const r = el.getBoundingClientRect();
|
|
136
|
+
return r.height > 0 && r.width > 0;
|
|
137
|
+
});
|
|
138
|
+
let maxGap = 0;
|
|
139
|
+
for (let i = 0; i + 1 < directKids.length; i++) {
|
|
140
|
+
const a = directKids[i].getBoundingClientRect();
|
|
141
|
+
const b = directKids[i + 1].getBoundingClientRect();
|
|
142
|
+
if (a.bottom < 1600 && b.top - a.bottom > maxGap) {
|
|
143
|
+
maxGap = Math.round(b.top - a.bottom);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// --- Misleading copy guard ---
|
|
148
|
+
// "All systems go" must not appear anywhere when nodes.total === 0.
|
|
149
|
+
// We can't read state directly, but if the page also shows
|
|
150
|
+
// "Waiting for first agent" then we know fleet is empty.
|
|
151
|
+
const bodyText = document.body.innerText;
|
|
152
|
+
const fleetEmpty = /Waiting for first agent/i.test(bodyText);
|
|
153
|
+
const sayingAllSystemsGo = /All systems go/i.test(bodyText);
|
|
154
|
+
const misleadingCopy = fleetEmpty && sayingAllSystemsGo;
|
|
155
|
+
|
|
156
|
+
// --- Carryover ---
|
|
157
|
+
const docWidth = doc.scrollWidth;
|
|
158
|
+
const horizontalOverflow = docWidth > viewportW + 2;
|
|
159
|
+
|
|
160
|
+
// --- Cut-off-element guard ---
|
|
161
|
+
// Round 96: scrollWidth check above is fooled when an overflowing
|
|
162
|
+
// parent has `overflow-x: hidden` (the page's own min-h-screen
|
|
163
|
+
// wrapper does this). Walk visible elements and flag any whose
|
|
164
|
+
// bounding rect ends past the viewport edge — that's a real cut-off
|
|
165
|
+
// even when the body's scroll-width matches. Skip elements with
|
|
166
|
+
// their own overflow scrolling (intentional inner scroll containers).
|
|
167
|
+
let cutoffCount = 0;
|
|
168
|
+
let cutoffSample = '';
|
|
169
|
+
// Cap the walk at 3000 elements (populated /server-logs has 500+ log
|
|
170
|
+
// rows × multiple spans each; without a cap the harness OOMs under
|
|
171
|
+
// chromium during the 32-capture sweep). Walk up ancestors to skip
|
|
172
|
+
// elements whose parent is an intentional horizontal-scroll
|
|
173
|
+
// container (overflow-x: auto / scroll) — those extending past the
|
|
174
|
+
// viewport edge is by design.
|
|
175
|
+
const hasScrollableAncestor = (el) => {
|
|
176
|
+
let cur = el;
|
|
177
|
+
while (cur && cur !== document.body) {
|
|
178
|
+
const cs = getComputedStyle(cur);
|
|
179
|
+
if (cs.overflowX === 'auto' || cs.overflowX === 'scroll') return true;
|
|
180
|
+
cur = cur.parentElement;
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
};
|
|
184
|
+
const allEls = document.querySelectorAll('body *');
|
|
185
|
+
const walk = Math.min(allEls.length, 3000);
|
|
186
|
+
for (let i = 0; i < walk; i++) {
|
|
187
|
+
const el = allEls[i];
|
|
188
|
+
const r = el.getBoundingClientRect();
|
|
189
|
+
if (r.width === 0 || r.height === 0) continue;
|
|
190
|
+
if (r.right <= viewportW + 8) continue;
|
|
191
|
+
if (hasScrollableAncestor(el)) continue;
|
|
192
|
+
cutoffCount++;
|
|
193
|
+
if (!cutoffSample) {
|
|
194
|
+
const txt = (el.textContent || '').trim().slice(0, 30);
|
|
195
|
+
cutoffSample = `${el.tagName} right=${Math.round(r.right)} "${txt}"`;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
let glowyCount = 0;
|
|
199
|
+
for (const el of document.querySelectorAll('body *')) {
|
|
200
|
+
const cs = getComputedStyle(el);
|
|
201
|
+
if (cs.filter && cs.filter.includes('blur(') && !cs.filter.includes('blur(0px)')) glowyCount++;
|
|
202
|
+
}
|
|
203
|
+
const sidebar = document.querySelector('aside, [data-sidebar], nav');
|
|
204
|
+
const sidebarRect = sidebar?.getBoundingClientRect();
|
|
205
|
+
const sidebarVisible = !!(sidebarRect && sidebarRect.width > 0 && sidebarRect.height > 0);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
theme, bodyBg, bodyFg,
|
|
209
|
+
viewportH, viewportW,
|
|
210
|
+
ctaTop, ctaPresent: ctaCandidates.length > 0,
|
|
211
|
+
zeroCardCount: zeroCards.length,
|
|
212
|
+
maxGap,
|
|
213
|
+
fleetEmpty, sayingAllSystemsGo, misleadingCopy,
|
|
214
|
+
docWidth, horizontalOverflow,
|
|
215
|
+
cutoffCount, cutoffSample,
|
|
216
|
+
glowyCount, sidebarVisible,
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
let score = 10;
|
|
221
|
+
const notes = [];
|
|
222
|
+
if (checks.theme !== m.theme) { score -= 3; notes.push(`theme=${checks.theme}`); }
|
|
223
|
+
if (checks.horizontalOverflow) { score -= 2; notes.push(`horiz overflow ${checks.docWidth}/${checks.viewportW}`); }
|
|
224
|
+
if (m.tag === 'desktop' && !checks.sidebarVisible) { score -= 1; notes.push('sidebar missing on desktop'); }
|
|
225
|
+
if (checks.glowyCount > 8) { score -= 1; notes.push(`${checks.glowyCount} blur-filter elements (glow tell)`); }
|
|
226
|
+
|
|
227
|
+
// New checks — the ones that would have caught r70's bug
|
|
228
|
+
if (checks.ctaPresent && checks.ctaTop !== null) {
|
|
229
|
+
if (checks.ctaTop > checks.viewportH) {
|
|
230
|
+
score -= 3;
|
|
231
|
+
notes.push(`CTA below fold (top=${Math.round(checks.ctaTop)} > vh=${checks.viewportH})`);
|
|
232
|
+
} else if (checks.ctaTop > checks.viewportH * 0.7) {
|
|
233
|
+
score -= 1;
|
|
234
|
+
notes.push(`CTA low in fold (top=${Math.round(checks.ctaTop)})`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (checks.zeroCardCount > 4) {
|
|
238
|
+
const extra = Math.min(checks.zeroCardCount - 4, 3);
|
|
239
|
+
score -= extra;
|
|
240
|
+
notes.push(`${checks.zeroCardCount} zero-value cards above fold`);
|
|
241
|
+
}
|
|
242
|
+
if (checks.maxGap > 200) {
|
|
243
|
+
score -= 1;
|
|
244
|
+
notes.push(`whitespace gap ${checks.maxGap}px`);
|
|
245
|
+
}
|
|
246
|
+
if (checks.misleadingCopy) {
|
|
247
|
+
score -= 2;
|
|
248
|
+
notes.push(`'All systems go' shown while fleet empty`);
|
|
249
|
+
}
|
|
250
|
+
if (checks.cutoffCount > 0) {
|
|
251
|
+
score -= 2;
|
|
252
|
+
notes.push(`${checks.cutoffCount} cut-off element(s); first: ${checks.cutoffSample}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
report.push({ ...m, file, checks, score: Math.max(score, 0), notes });
|
|
256
|
+
|
|
257
|
+
await page.close();
|
|
258
|
+
await ctx.close();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
await browser.close();
|
|
262
|
+
|
|
263
|
+
console.log('\n=== Loop self-review (harness r73) ===\n');
|
|
264
|
+
for (const r of report) {
|
|
265
|
+
console.log(`${r.pageName.padEnd(8)} ${r.theme}/${r.tag.padEnd(7)} score=${r.score}/10`);
|
|
266
|
+
console.log(` cta_top=${r.checks.ctaTop !== null ? Math.round(r.checks.ctaTop) : '--'} zero_cards=${r.checks.zeroCardCount} max_gap=${r.checks.maxGap}px fleet_empty=${r.checks.fleetEmpty}`);
|
|
267
|
+
if (r.notes.length) console.log(` notes: ${r.notes.join(' · ')}`);
|
|
268
|
+
}
|
|
269
|
+
const avg = report.reduce((s, r) => s + r.score, 0) / report.length;
|
|
270
|
+
console.log(`\noverall avg = ${avg.toFixed(1)}/10 across ${report.length} captures`);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
const TOKEN = process.env.LOOP_REVIEW_TOKEN;
|
|
3
|
+
const browser = await chromium.launch({ headless: true });
|
|
4
|
+
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
|
|
5
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
6
|
+
await ctx.addInitScript(() => { try { localStorage.setItem('anet-theme','cyber'); sessionStorage.setItem('anet_v3_auth','1'); } catch {} });
|
|
7
|
+
await ctx.route('**/api/hub/networks*', async (route) => {
|
|
8
|
+
await route.fulfill({ json: { ok: true, networks: [{ network_id: 'default', network_name: 'default', role: 'owner' }] } });
|
|
9
|
+
});
|
|
10
|
+
const page = await ctx.newPage();
|
|
11
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
12
|
+
await page.waitForTimeout(3500);
|
|
13
|
+
// crop the sidebar region
|
|
14
|
+
await page.screenshot({ path: '/tmp/anet-issue-50/network-derive.png', clip: { x: 0, y: 0, width: 230, height: 420 } });
|
|
15
|
+
await browser.close();
|
|
16
|
+
console.log('done');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
const TOKEN = process.env.LOOP_REVIEW_TOKEN;
|
|
3
|
+
const browser = await chromium.launch({ headless: true });
|
|
4
|
+
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
|
|
5
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
6
|
+
await ctx.addInitScript(() => { try { localStorage.setItem('anet-theme','cyber'); sessionStorage.setItem('anet_v3_auth','1'); } catch {} });
|
|
7
|
+
// simulate the #92 bug: /api/networks scope-limited to only "default"
|
|
8
|
+
await ctx.route('**/api/hub/networks*', async (route) => {
|
|
9
|
+
await route.fulfill({ json: { ok: true, networks: [{ network_id: 'default', network_name: 'default', role: 'owner' }] } });
|
|
10
|
+
});
|
|
11
|
+
// see what real network_ids the sessions carry
|
|
12
|
+
const page = await ctx.newPage();
|
|
13
|
+
const apiRes = await page.goto('http://127.0.0.1:3000/api/hub/status', { waitUntil: 'domcontentloaded' });
|
|
14
|
+
const statusBody = await apiRes.json();
|
|
15
|
+
const realNetIds = [...new Set((statusBody.sessions||[]).map(s => s.network_id).filter(Boolean))];
|
|
16
|
+
console.log('real network_ids in sessions:', JSON.stringify(realNetIds));
|
|
17
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
18
|
+
await page.waitForTimeout(3500);
|
|
19
|
+
// read the Sidebar network buttons
|
|
20
|
+
const sidebarNets = await page.evaluate(() => {
|
|
21
|
+
const hdr = [...document.querySelectorAll('div')].find(d => d.textContent?.trim() === 'Networks');
|
|
22
|
+
if (!hdr) return { error: 'no Networks header' };
|
|
23
|
+
const list = hdr.nextElementSibling;
|
|
24
|
+
return [...list.querySelectorAll('button')].map(b => b.textContent.trim());
|
|
25
|
+
});
|
|
26
|
+
console.log('Sidebar shows networks:', JSON.stringify(sidebarNets));
|
|
27
|
+
await browser.close();
|
|
28
|
+
console.log('done');
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* Screenshot TopoGraph with brand=intern flag (书小生 avatars). */
|
|
2
|
+
import { chromium } from 'playwright';
|
|
3
|
+
|
|
4
|
+
const TOKEN = process.env.LOOP_REVIEW_TOKEN;
|
|
5
|
+
const browser = await chromium.launch({ headless: true });
|
|
6
|
+
|
|
7
|
+
function makeFakeSession(index) {
|
|
8
|
+
return {
|
|
9
|
+
alias: `agent${index}号`,
|
|
10
|
+
status: index % 5 === 0 ? 'working' : 'idle',
|
|
11
|
+
network_id: 'default',
|
|
12
|
+
created_at: '2026-05-13T00:00:00Z',
|
|
13
|
+
updated_at: '2026-05-13T00:00:00Z',
|
|
14
|
+
last_seen_at: '2026-05-13T00:00:00Z',
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (const [variant, themes] of [['normal', ['cyber', 'light']], ['large', ['cyber']]]) {
|
|
19
|
+
for (const theme of themes) {
|
|
20
|
+
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
|
|
21
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
22
|
+
await ctx.addInitScript(t => {
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem('anet-theme', t);
|
|
25
|
+
localStorage.setItem('anet-brand', 'intern');
|
|
26
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
27
|
+
} catch {}
|
|
28
|
+
}, theme);
|
|
29
|
+
|
|
30
|
+
if (variant === 'large') {
|
|
31
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
32
|
+
const response = await route.fetch();
|
|
33
|
+
const body = await response.json();
|
|
34
|
+
const real = body.sessions || [];
|
|
35
|
+
const padded = [...real];
|
|
36
|
+
while (padded.length < 25) padded.push(makeFakeSession(padded.length));
|
|
37
|
+
await route.fulfill({ response, json: { ...body, sessions: padded } });
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const page = await ctx.newPage();
|
|
42
|
+
await page.goto('http://127.0.0.1:3000/?brand=intern', { waitUntil: 'domcontentloaded' });
|
|
43
|
+
await page.waitForFunction(() => {
|
|
44
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
45
|
+
const minNodes = window.location.search.includes('large') ? 14 : 8;
|
|
46
|
+
return !!svg && svg.querySelectorAll('image[href*="intern_avatar"]').length > 0;
|
|
47
|
+
}, { timeout: 30000 }).catch(() => {});
|
|
48
|
+
await page.waitForTimeout(1500);
|
|
49
|
+
await page.evaluate(t => document.documentElement.setAttribute('data-theme', t), theme);
|
|
50
|
+
await page.waitForTimeout(400);
|
|
51
|
+
await page.evaluate(() => {
|
|
52
|
+
const heading = [...document.querySelectorAll('h2')].find(h => /Command mesh/i.test(h.textContent || ''));
|
|
53
|
+
heading?.scrollIntoView({ behavior: 'instant', block: 'start' });
|
|
54
|
+
});
|
|
55
|
+
await page.waitForTimeout(800);
|
|
56
|
+
await page.screenshot({ path: `/tmp/anet-issue-50/topo-brand-${variant}-${theme}.png`, fullPage: false, animations: 'disabled', timeout: 30000 });
|
|
57
|
+
await ctx.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
await browser.close();
|
|
61
|
+
console.log('done');
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
const TOKEN = process.env.LOOP_REVIEW_TOKEN;
|
|
3
|
+
const browser = await chromium.launch({ headless: true });
|
|
4
|
+
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
|
|
5
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
6
|
+
await ctx.addInitScript(() => { try { localStorage.setItem('anet-theme','cyber'); localStorage.removeItem('anet-topo-view'); sessionStorage.setItem('anet_v3_auth','1'); } catch {} });
|
|
7
|
+
const page = await ctx.newPage();
|
|
8
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
9
|
+
await page.waitForFunction(() => {
|
|
10
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
11
|
+
return !!svg && svg.querySelectorAll('circle[r="26"]').length > 0;
|
|
12
|
+
}, { timeout: 30000 }).catch(() => {});
|
|
13
|
+
await page.waitForTimeout(800);
|
|
14
|
+
const ordered = await page.evaluate(() => {
|
|
15
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
16
|
+
const cx = 500, cy = 330;
|
|
17
|
+
// each agent node <g> contains: status-ring circle[r=26|18] + a label <g> with the alias text
|
|
18
|
+
const nodeGs = [...svg.querySelectorAll('g')].filter(g => {
|
|
19
|
+
return g.querySelector(':scope > circle[r="26"], :scope > circle[r="18"]') &&
|
|
20
|
+
g.querySelector(':scope > g > text[font-weight="700"]');
|
|
21
|
+
});
|
|
22
|
+
const nodes = nodeGs.map(g => {
|
|
23
|
+
const ring = g.querySelector(':scope > circle[r="26"], :scope > circle[r="18"]');
|
|
24
|
+
const label = g.querySelector(':scope > g > text[font-weight="700"]');
|
|
25
|
+
const x = parseFloat(ring.getAttribute('cx')), y = parseFloat(ring.getAttribute('cy'));
|
|
26
|
+
return { alias: label.textContent.trim(), ang: Math.atan2(y - cy, x - cx) };
|
|
27
|
+
});
|
|
28
|
+
nodes.sort((a, b) => a.ang - b.ang);
|
|
29
|
+
return nodes.map(n => n.alias);
|
|
30
|
+
});
|
|
31
|
+
console.log('node count:', ordered.length);
|
|
32
|
+
console.log('ring order (by angle):');
|
|
33
|
+
console.log(ordered.join(' · '));
|
|
34
|
+
// check 通信* contiguity
|
|
35
|
+
const tx = ordered.map((a, i) => a.startsWith('通信') ? i : -1).filter(i => i >= 0);
|
|
36
|
+
const contiguous = tx.length === 0 || tx.every((v, i) => i === 0 || v === tx[i-1] + 1) ||
|
|
37
|
+
// allow wrap-around contiguity
|
|
38
|
+
(() => { const n = ordered.length; const set = new Set(tx); for (let s = 0; s < n; s++) { let ok = true; for (let k = 0; k < tx.length; k++) if (!set.has((s + k) % n)) { ok = false; break; } if (ok) return true; } return false; })();
|
|
39
|
+
console.log('通信* indices:', tx.join(','), '→ contiguous:', contiguous);
|
|
40
|
+
await browser.close();
|
|
41
|
+
console.log('done');
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
const TOKEN = process.env.LOOP_REVIEW_TOKEN;
|
|
3
|
+
const browser = await chromium.launch({ headless: true });
|
|
4
|
+
for (const theme of ['cyber', 'light']) {
|
|
5
|
+
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
|
|
6
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
7
|
+
await ctx.addInitScript(t => { try { localStorage.setItem('anet-theme', t); sessionStorage.setItem('anet_v3_auth','1'); localStorage.removeItem('anet-topo-view'); } catch {} }, theme);
|
|
8
|
+
const page = await ctx.newPage();
|
|
9
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
10
|
+
await page.waitForFunction(() => {
|
|
11
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
12
|
+
return !!svg && svg.querySelectorAll('circle[r="26"]').length > 0;
|
|
13
|
+
}, { timeout: 30000 }).catch(() => {});
|
|
14
|
+
await page.waitForTimeout(800);
|
|
15
|
+
await page.evaluate(t => document.documentElement.setAttribute('data-theme', t), theme);
|
|
16
|
+
await page.waitForTimeout(300);
|
|
17
|
+
const sec = page.locator('section:has(h2:text("Command mesh"))');
|
|
18
|
+
await sec.scrollIntoViewIfNeeded();
|
|
19
|
+
await page.waitForTimeout(400);
|
|
20
|
+
await sec.screenshot({ path: `/tmp/anet-issue-50/topo-controls-${theme}.png` });
|
|
21
|
+
await ctx.close();
|
|
22
|
+
}
|
|
23
|
+
await browser.close();
|
|
24
|
+
console.log('done');
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
const TOKEN = process.env.LOOP_REVIEW_TOKEN;
|
|
3
|
+
const browser = await chromium.launch({ headless: true });
|
|
4
|
+
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
|
|
5
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
6
|
+
await ctx.addInitScript(() => { try { localStorage.setItem('anet-theme','cyber'); localStorage.removeItem('anet-topo-view'); sessionStorage.setItem('anet_v3_auth','1'); } catch {} });
|
|
7
|
+
const page = await ctx.newPage();
|
|
8
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
9
|
+
await page.waitForFunction(() => {
|
|
10
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
11
|
+
return !!svg && svg.querySelectorAll('circle[r="26"]').length > 0;
|
|
12
|
+
}, { timeout: 30000 }).catch(() => {});
|
|
13
|
+
await page.waitForTimeout(800);
|
|
14
|
+
// for each node: alias + avatar bg fill — same prefix should share fill
|
|
15
|
+
const map = await page.evaluate(() => {
|
|
16
|
+
const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
|
|
17
|
+
const nodeGs = [...svg.querySelectorAll('g')].filter(g =>
|
|
18
|
+
g.querySelector(':scope > circle[r="26"], :scope > circle[r="18"]') &&
|
|
19
|
+
g.querySelector(':scope > g > text[font-weight="700"]'));
|
|
20
|
+
return nodeGs.map(g => {
|
|
21
|
+
const label = g.querySelector(':scope > g > text[font-weight="700"]').textContent.trim();
|
|
22
|
+
// avatar bg circle: the small circle that is NOT the status ring (r 26/18) and NOT inner — it's r=14/10 with a fill
|
|
23
|
+
const avatarCircle = [...g.querySelectorAll(':scope > circle')].find(c => {
|
|
24
|
+
const r = parseFloat(c.getAttribute('r'));
|
|
25
|
+
return (r === 14 || r === 10) && c.getAttribute('fill') && c.getAttribute('fill').startsWith('hsl');
|
|
26
|
+
});
|
|
27
|
+
return { alias: label, fill: avatarCircle ? avatarCircle.getAttribute('fill') : null };
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
// group by fill
|
|
31
|
+
const byFill = {};
|
|
32
|
+
map.forEach(({alias, fill}) => { (byFill[fill] ??= []).push(alias); });
|
|
33
|
+
console.log('avatar fill → aliases:');
|
|
34
|
+
Object.entries(byFill).forEach(([fill, aliases]) => console.log(' ' + fill + ': ' + aliases.join(', ')));
|
|
35
|
+
await page.locator('section:has(h2:text("Command mesh"))').screenshot({ path: '/tmp/anet-issue-50/topo-grouped.png' });
|
|
36
|
+
await browser.close();
|
|
37
|
+
console.log('done');
|