@sleep2agi/agent-network-dashboard 0.4.5-preview.6 → 0.4.5-preview.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +49 -49
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +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.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 +14 -14
- package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
- package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
- package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/index.html +4 -4
- 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.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 +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.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 +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.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 +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.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 +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.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 +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.js.nft.json +1 -1
- 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 +4 -4
- 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.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 +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.js.nft.json +1 -1
- 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 +4 -4
- 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 +4 -4
- 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.js.nft.json +1 -1
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +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 +14 -14
- package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__02ee4km._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__02ee4km._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0n4l2is._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0n4l2is._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0o8yj3c._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0o8yj3c._.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_0aum_g5._.js +3 -0
- package/.next/server/chunks/ssr/app_0aum_g5._.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_0py1sns._.js +3 -0
- package/.next/server/chunks/ssr/app_0py1sns._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0tncr1_._.js +3 -0
- package/.next/server/chunks/ssr/app_0tncr1_._.js.map +1 -0
- 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/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-27cd~m2b0lg.js +1 -0
- package/.next/static/chunks/023enh~oz30n..js +1 -0
- package/.next/static/chunks/09.q998r20bia.css +2 -0
- package/.next/static/chunks/0985ut18w-kpb.js +1 -0
- package/.next/static/chunks/{07okhs5561y5y.js → 0c1bp~g7.8ru..js} +1 -1
- package/.next/static/chunks/0edt_z0rlxgfk.js +1 -0
- package/.next/static/chunks/0et33r-3bdzlg.js +7 -0
- package/.next/static/chunks/0f6w7h-pu0tng.js +1 -0
- package/.next/static/chunks/0i89ioku1y61m.js +1 -0
- package/.next/static/chunks/0irkayokwc1fm.js +1 -0
- package/.next/static/chunks/0l2rkodu2.v~r.js +1 -0
- package/.next/static/chunks/0mdu-2u9-dx.4.js +1 -0
- package/.next/static/chunks/0rk5zmyp19fv8.js +7 -0
- package/.next/static/chunks/0zax.-67r-uuj.js +1 -0
- package/.next/static/chunks/12hyhrnzrx~ys.js +1 -0
- package/.next/static/chunks/12tlkbch057lv.js +1 -0
- package/.next/static/chunks/15c9pv6gxir.5.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/admin/page.tsx +90 -26
- 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 +77 -4
- package/app/components/HealthBanner.tsx +11 -2
- package/app/components/HelpOverlay.tsx +50 -10
- package/app/components/InboxPanel.tsx +6 -3
- package/app/components/Sidebar.tsx +49 -41
- 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 +118 -24
- package/app/globals.css +90 -0
- package/app/lib/status.ts +63 -0
- package/app/lib/version.ts +7 -0
- package/app/login/page.tsx +40 -7
- package/app/logs/page.tsx +72 -21
- package/app/messages/page.tsx +12 -3
- package/app/node/page.tsx +48 -25
- package/app/nodes/page.tsx +22 -6
- package/app/page.tsx +105 -34
- package/app/server-logs/page.tsx +70 -12
- package/app/settings/networks/page.tsx +37 -13
- package/app/settings/page.tsx +39 -11
- package/app/tasks/[id]/page.tsx +2 -13
- package/app/tasks/page.tsx +98 -93
- package/package.json +1 -1
- package/scripts/loop-review.mjs +224 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__06pu~4t._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__06pu~4t._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0_jnflc._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0_jnflc._.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/_0d-fuox._.js +0 -3
- package/.next/server/chunks/ssr/_0d-fuox._.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_0k8xbx~._.js +0 -9
- package/.next/server/chunks/ssr/app_0k8xbx~._.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/02flrewxlf_m_.js +0 -1
- package/.next/static/chunks/051gslfjka1kj.js +0 -1
- package/.next/static/chunks/06t_vzei8s9_h.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/0m328uz90rp81.js +0 -1
- package/.next/static/chunks/0ogeals5ufhho.js +0 -1
- package/.next/static/chunks/0p4_o2jw2v9n_.css +0 -2
- package/.next/static/chunks/0v~9m62eciynd.js +0 -1
- package/.next/static/chunks/0z.pzy8zpa~js.js +0 -7
- package/.next/static/chunks/1031v6nw33ud3.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/13.dst7liziwo.js +0 -1
- 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/{aliOmgYOuDD7xT9T9MLOA → wt8PpIEMpmwboFwbqXVtO}/_buildManifest.js +0 -0
- /package/.next/static/{aliOmgYOuDD7xT9T9MLOA → wt8PpIEMpmwboFwbqXVtO}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{aliOmgYOuDD7xT9T9MLOA → wt8PpIEMpmwboFwbqXVtO}/_ssgManifest.js +0 -0
package/app/settings/page.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import Link from 'next/link';
|
|
5
5
|
import { useAnetConfig, useHealth, useLicense } from '../lib/hooks';
|
|
6
|
+
import { DASHBOARD_VERSION } from '../lib/version';
|
|
6
7
|
|
|
7
8
|
export default function SettingsPage() {
|
|
8
9
|
const { config } = useAnetConfig();
|
|
@@ -18,12 +19,31 @@ export default function SettingsPage() {
|
|
|
18
19
|
|
|
19
20
|
return (
|
|
20
21
|
<div className="min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono">
|
|
21
|
-
<h1 className="text-2xl font-bold text-white mb-
|
|
22
|
+
<h1 className="text-2xl font-bold text-white mb-3 lg:ml-0 ml-10">Settings</h1>
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
{/* Section anchor nav (round 28) — jump to a group instead of scrolling
|
|
25
|
+
through the whole page. Mirrors the section headers below; pages
|
|
26
|
+
with 7+ cards benefit from a visible table-of-contents. */}
|
|
27
|
+
<nav className="mb-8 flex flex-wrap gap-1 text-xs">
|
|
28
|
+
{[
|
|
29
|
+
{ href: '#connection', label: 'Connection' },
|
|
30
|
+
{ href: '#account', label: 'Account' },
|
|
31
|
+
{ href: '#resources', label: 'Resources' },
|
|
32
|
+
].map(a => (
|
|
33
|
+
<a key={a.href} href={a.href}
|
|
34
|
+
className="rounded-md px-2.5 py-1 text-gray-500 hover:text-cyan-300 hover:bg-cyan-500/10 transition-colors">
|
|
35
|
+
{a.label}
|
|
36
|
+
</a>
|
|
37
|
+
))}
|
|
38
|
+
</nav>
|
|
39
|
+
|
|
40
|
+
<div className="max-w-2xl space-y-10">
|
|
24
41
|
{/* ── Group: Connection ─────────────────────────────────── */}
|
|
25
|
-
<div className="space-y-4">
|
|
26
|
-
<div className="
|
|
42
|
+
<div id="connection" className="space-y-4 scroll-mt-6">
|
|
43
|
+
<div className="flex items-center gap-2 px-1">
|
|
44
|
+
<div className="text-[11px] uppercase tracking-[0.14em] text-gray-400 font-semibold">Connection</div>
|
|
45
|
+
<div className="flex-1 h-px bg-[#2a2a4a]" />
|
|
46
|
+
</div>
|
|
27
47
|
|
|
28
48
|
{/* CommHub Connection */}
|
|
29
49
|
<section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
|
|
@@ -72,7 +92,7 @@ export default function SettingsPage() {
|
|
|
72
92
|
<span className={`text-gray-300 ${valueClass}`}>{health?.sessions ?? '--'}</span>
|
|
73
93
|
</div>
|
|
74
94
|
<div className={rowClass}>
|
|
75
|
-
<span className="text-gray-500">SSE
|
|
95
|
+
<span className="text-gray-500">SSE streams <span className="text-gray-600">· server</span></span>
|
|
76
96
|
<span className={`text-gray-300 ${valueClass}`}>{health?.sse_connections ?? '--'}</span>
|
|
77
97
|
</div>
|
|
78
98
|
<div className={rowClass}>
|
|
@@ -88,7 +108,9 @@ export default function SettingsPage() {
|
|
|
88
108
|
<div className="space-y-3 text-sm">
|
|
89
109
|
<div className={rowClass}>
|
|
90
110
|
<span className="text-gray-500">Version</span>
|
|
91
|
-
<span className={`text-gray-300 ${valueClass}`}
|
|
111
|
+
<span className={`text-gray-300 font-mono ${valueClass}`} title="From package.json @sleep2agi/agent-network-dashboard">
|
|
112
|
+
{DASHBOARD_VERSION}
|
|
113
|
+
</span>
|
|
92
114
|
</div>
|
|
93
115
|
<div className={rowClass}>
|
|
94
116
|
<span className="text-gray-500">Data Layer</span>
|
|
@@ -96,15 +118,18 @@ export default function SettingsPage() {
|
|
|
96
118
|
</div>
|
|
97
119
|
<div className={rowClass}>
|
|
98
120
|
<span className="text-gray-500">Pages</span>
|
|
99
|
-
<span className={`text-gray-300 ${valueClass}`}>Overview, Tasks, Nodes, Messages, Settings</span>
|
|
121
|
+
<span className={`text-gray-300 ${valueClass}`}>Overview, Tasks, Nodes, Messages, Networks, Audit, Server Logs, Admin, Settings</span>
|
|
100
122
|
</div>
|
|
101
123
|
</div>
|
|
102
124
|
</section>
|
|
103
125
|
</div>
|
|
104
126
|
|
|
105
127
|
{/* ── Group: Account ────────────────────────────────────── */}
|
|
106
|
-
<div className="space-y-4">
|
|
107
|
-
<div className="
|
|
128
|
+
<div id="account" className="space-y-4 scroll-mt-6">
|
|
129
|
+
<div className="flex items-center gap-2 px-1">
|
|
130
|
+
<div className="text-[11px] uppercase tracking-[0.14em] text-gray-400 font-semibold">Account</div>
|
|
131
|
+
<div className="flex-1 h-px bg-[#2a2a4a]" />
|
|
132
|
+
</div>
|
|
108
133
|
|
|
109
134
|
{/* License */}
|
|
110
135
|
<section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
|
|
@@ -232,8 +257,11 @@ export default function SettingsPage() {
|
|
|
232
257
|
</div>
|
|
233
258
|
|
|
234
259
|
{/* ── Group: Resources ──────────────────────────────────── */}
|
|
235
|
-
<div className="space-y-4">
|
|
236
|
-
<div className="
|
|
260
|
+
<div id="resources" className="space-y-4 scroll-mt-6">
|
|
261
|
+
<div className="flex items-center gap-2 px-1">
|
|
262
|
+
<div className="text-[11px] uppercase tracking-[0.14em] text-gray-400 font-semibold">Resources</div>
|
|
263
|
+
<div className="flex-1 h-px bg-[#2a2a4a]" />
|
|
264
|
+
</div>
|
|
237
265
|
|
|
238
266
|
{/* API Tokens + Networks */}
|
|
239
267
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
package/app/tasks/[id]/page.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { use } from 'react';
|
|
|
4
4
|
import Link from 'next/link';
|
|
5
5
|
import useSWR from 'swr';
|
|
6
6
|
import { timeAgo } from '../../components/utils';
|
|
7
|
+
import { STATUS_CHIP_CLASS } from '../../lib/status';
|
|
7
8
|
|
|
8
9
|
const fetcher = async (url: string) => {
|
|
9
10
|
const res = await fetch(url);
|
|
@@ -55,18 +56,6 @@ function Timeline({ steps }: { steps: TimelineStep[] }) {
|
|
|
55
56
|
);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
const STATUS_COLORS: Record<string, string> = {
|
|
59
|
-
created: 'bg-gray-500/10 text-gray-400 border-gray-500/20',
|
|
60
|
-
delivered: 'bg-blue-500/10 text-blue-300 border-blue-500/20',
|
|
61
|
-
acked: 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20',
|
|
62
|
-
running: 'bg-green-500/10 text-green-300 border-green-500/20',
|
|
63
|
-
replied: 'bg-purple-500/10 text-purple-300 border-purple-500/20',
|
|
64
|
-
closed: 'bg-gray-500/10 text-gray-500 border-gray-500/20',
|
|
65
|
-
failed: 'bg-red-500/10 text-red-300 border-red-500/20',
|
|
66
|
-
cancelled: 'bg-yellow-500/10 text-yellow-300 border-yellow-500/20',
|
|
67
|
-
expired: 'bg-orange-500/10 text-orange-300 border-orange-500/20',
|
|
68
|
-
};
|
|
69
|
-
|
|
70
59
|
export default function TaskDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
|
71
60
|
const { id } = use(params);
|
|
72
61
|
const { data, isLoading } = useSWR(
|
|
@@ -105,7 +94,7 @@ export default function TaskDetailPage({ params }: { params: Promise<{ id: strin
|
|
|
105
94
|
);
|
|
106
95
|
}
|
|
107
96
|
|
|
108
|
-
const statusColor =
|
|
97
|
+
const statusColor = STATUS_CHIP_CLASS[task.status] || STATUS_CHIP_CLASS.created;
|
|
109
98
|
const steps: TimelineStep[] = [
|
|
110
99
|
{ label: 'Created', time: task.created_at, color: 'text-gray-400' },
|
|
111
100
|
{ label: 'Delivered', time: task.delivered_at, color: 'text-blue-400' },
|
package/app/tasks/page.tsx
CHANGED
|
@@ -6,6 +6,8 @@ import Link from 'next/link';
|
|
|
6
6
|
import { useNetworkId } from '../lib/network-context';
|
|
7
7
|
import { TaskDrawer } from '../components/TaskDrawer';
|
|
8
8
|
import { EmptyState } from '../components/EmptyState';
|
|
9
|
+
import { AliasAvatar } from '../components/AliasAvatar';
|
|
10
|
+
import { TASK_STATUSES, STATUS_CHIP_CLASS, STATUS_DOT_HEX, STATUS_BAR_CLASS } from '../lib/status';
|
|
9
11
|
|
|
10
12
|
interface Task {
|
|
11
13
|
task_id: string;
|
|
@@ -23,48 +25,8 @@ interface Task {
|
|
|
23
25
|
expires_at: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
const STATUS_OPTIONS = [
|
|
27
|
-
'',
|
|
28
|
-
'created',
|
|
29
|
-
'delivered',
|
|
30
|
-
'acked',
|
|
31
|
-
'running',
|
|
32
|
-
'replied',
|
|
33
|
-
'closed',
|
|
34
|
-
'failed',
|
|
35
|
-
'cancelled',
|
|
36
|
-
'expired',
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
const STATUS_COLORS: Record<string, string> = {
|
|
40
|
-
created: 'bg-gray-500/10 text-gray-400 border-gray-500/20',
|
|
41
|
-
delivered: 'bg-blue-500/10 text-blue-300 border-blue-500/20',
|
|
42
|
-
acked: 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20',
|
|
43
|
-
running: 'bg-green-500/10 text-green-300 border-green-500/20',
|
|
44
|
-
replied: 'bg-purple-500/10 text-purple-300 border-purple-500/20',
|
|
45
|
-
closed: 'bg-gray-500/10 text-gray-500 border-gray-500/20',
|
|
46
|
-
failed: 'bg-red-500/10 text-red-300 border-red-500/20',
|
|
47
|
-
cancelled: 'bg-yellow-500/10 text-yellow-300 border-yellow-500/20',
|
|
48
|
-
expired: 'bg-orange-500/10 text-orange-300 border-orange-500/20',
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/** Small colored dot per status — visible on every tab (active + inactive)
|
|
52
|
-
* so users can scan the row at a glance. Hex inlined to avoid Tailwind
|
|
53
|
-
* purging dynamic `bg-${family}-400` class names. */
|
|
54
|
-
const STATUS_DOTS: Record<string, string> = {
|
|
55
|
-
created: '#9ca3af',
|
|
56
|
-
delivered: '#60a5fa',
|
|
57
|
-
acked: '#22d3ee',
|
|
58
|
-
running: '#4ade80',
|
|
59
|
-
replied: '#a78bfa',
|
|
60
|
-
closed: '#6b7280',
|
|
61
|
-
failed: '#f87171',
|
|
62
|
-
cancelled: '#facc15',
|
|
63
|
-
expired: '#fb923c',
|
|
64
|
-
};
|
|
65
|
-
|
|
66
28
|
function statusBadge(status: string) {
|
|
67
|
-
const color =
|
|
29
|
+
const color = STATUS_CHIP_CLASS[status] || 'bg-gray-500/10 text-gray-400 border-gray-500/20';
|
|
68
30
|
return `text-xs px-2 py-0.5 rounded-md border ${color}`;
|
|
69
31
|
}
|
|
70
32
|
|
|
@@ -149,6 +111,13 @@ function TasksContent() {
|
|
|
149
111
|
</span>
|
|
150
112
|
</div>
|
|
151
113
|
|
|
114
|
+
{/* Round 75: hide the status tab strip + From/To filter row when
|
|
115
|
+
there are no tasks at all. Each tab would show "0" and the
|
|
116
|
+
filter inputs would have nothing to act on — pure noise above
|
|
117
|
+
the empty-state CTA. When at least one task exists, the chrome
|
|
118
|
+
comes back even if the current filter happens to hide
|
|
119
|
+
everything (so users can clear filters). */}
|
|
120
|
+
{tasks.length > 0 && <>
|
|
152
121
|
{/* Status Tabs — color-coded dots per status family so users can
|
|
153
122
|
scan the row at a glance. Active tab gets full chip styling
|
|
154
123
|
(bg + border + text color), inactive tabs only carry the dot
|
|
@@ -158,64 +127,96 @@ function TasksContent() {
|
|
|
158
127
|
elements) hinting more content. */}
|
|
159
128
|
<div className="anet-tabstrip-wrap mb-4">
|
|
160
129
|
<div className="anet-tabstrip flex sm:flex-wrap gap-1 bg-[#111128] rounded-lg border border-[#2a2a4a] p-1 overflow-x-auto sm:overflow-x-visible scrollbar-thin">
|
|
161
|
-
{
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
130
|
+
{(() => {
|
|
131
|
+
// Round 56: compute per-status counts so the tab strip doubles as a
|
|
132
|
+
// status distribution dashboard. Matches Audit Log (r43) + Overview
|
|
133
|
+
// agent filter (r34) chip-with-count pattern.
|
|
134
|
+
const counts: Record<string, number> = {};
|
|
135
|
+
tasks.forEach(t => { counts[t.status] = (counts[t.status] || 0) + 1; });
|
|
136
|
+
return ['', ...TASK_STATUSES].map(s => {
|
|
137
|
+
const count = s === '' ? tasks.length : counts[s] || 0;
|
|
138
|
+
const isActive = filterStatus === s;
|
|
139
|
+
return (
|
|
140
|
+
<button
|
|
141
|
+
key={s}
|
|
142
|
+
onClick={() => setFilterStatus(s)}
|
|
143
|
+
disabled={count === 0 && s !== '' && !isActive}
|
|
144
|
+
className={`px-3 py-1.5 rounded-md text-xs transition-colors flex items-center gap-1.5 shrink-0 whitespace-nowrap disabled:opacity-30 disabled:cursor-not-allowed ${
|
|
145
|
+
isActive
|
|
146
|
+
? `${STATUS_CHIP_CLASS[s] || 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'} border`
|
|
147
|
+
: 'text-gray-500 hover:text-gray-300 hover:bg-[#1a1a2a]/40'
|
|
148
|
+
}`}
|
|
149
|
+
>
|
|
150
|
+
{s && (
|
|
151
|
+
<span
|
|
152
|
+
aria-hidden
|
|
153
|
+
className="inline-block w-1.5 h-1.5 rounded-full shrink-0"
|
|
154
|
+
style={{ backgroundColor: STATUS_DOT_HEX[s] || '#6b7280' }}
|
|
155
|
+
/>
|
|
156
|
+
)}
|
|
157
|
+
<span>{s || 'All'}</span>
|
|
158
|
+
<span className={`text-[10px] tabular-nums ${isActive ? 'opacity-80' : 'text-gray-600'}`}>{count}</span>
|
|
159
|
+
</button>
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
})()}
|
|
178
163
|
</div>
|
|
179
164
|
</div>
|
|
180
165
|
|
|
181
|
-
{/* From/To Filters */}
|
|
182
|
-
<div className="flex flex-wrap gap-
|
|
183
|
-
<
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
166
|
+
{/* From/To Filters — same visual block as the status tabs above. */}
|
|
167
|
+
<div className="flex flex-wrap items-center gap-2 mb-6">
|
|
168
|
+
<div className="flex items-center gap-1.5 rounded-lg border border-[#2a2a4a] bg-[#111128] px-2.5 py-1.5 focus-within:border-blue-500/40">
|
|
169
|
+
<span className="text-[10px] uppercase tracking-wide text-gray-600">From</span>
|
|
170
|
+
<input
|
|
171
|
+
type="text"
|
|
172
|
+
value={filterFrom}
|
|
173
|
+
onChange={e => setFilterFrom(e.target.value)}
|
|
174
|
+
placeholder="any node"
|
|
175
|
+
className="w-28 bg-transparent text-sm text-white placeholder-gray-700 focus:outline-none"
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
<div className="flex items-center gap-1.5 rounded-lg border border-[#2a2a4a] bg-[#111128] px-2.5 py-1.5 focus-within:border-blue-500/40">
|
|
179
|
+
<span className="text-[10px] uppercase tracking-wide text-gray-600">To</span>
|
|
180
|
+
<input
|
|
181
|
+
type="text"
|
|
182
|
+
value={filterTo}
|
|
183
|
+
onChange={e => setFilterTo(e.target.value)}
|
|
184
|
+
placeholder="any node"
|
|
185
|
+
className="w-28 bg-transparent text-sm text-white placeholder-gray-700 focus:outline-none"
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
{(filterStatus || filterFrom || filterTo) && (
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
onClick={() => { setFilterStatus(''); setFilterFrom(''); setFilterTo(''); }}
|
|
192
|
+
className="rounded-lg border border-gray-700 px-2.5 py-1.5 text-[11px] text-gray-500 hover:text-gray-200 hover:border-gray-600"
|
|
193
|
+
>
|
|
194
|
+
Clear filters
|
|
195
|
+
</button>
|
|
196
|
+
)}
|
|
197
197
|
</div>
|
|
198
|
+
</>}
|
|
198
199
|
|
|
199
200
|
{/* Status distribution */}
|
|
200
201
|
{tasks.length > 0 && !filterStatus && (() => {
|
|
201
202
|
const stats: Record<string, number> = {};
|
|
202
203
|
tasks.forEach(t => { stats[t.status] = (stats[t.status] || 0) + 1; });
|
|
203
204
|
const total = tasks.length || 1;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
{ key
|
|
209
|
-
|
|
210
|
-
].filter(b => stats[b.key]);
|
|
205
|
+
// Round 67: derive from shared TASK_STATUSES + STATUS_BAR_CLASS in
|
|
206
|
+
// app/lib/status.ts. Adding a new lifecycle status updates badge,
|
|
207
|
+
// tab dots, and bar in one edit.
|
|
208
|
+
const bars = TASK_STATUSES
|
|
209
|
+
.map(key => ({ key, color: STATUS_BAR_CLASS[key] }))
|
|
210
|
+
.filter(b => stats[b.key]);
|
|
211
211
|
if (!bars.length) return null;
|
|
212
|
+
// Round 63: removed the per-status legend row beneath the bar —
|
|
213
|
+
// it duplicated the counts already shown in the tab chip strip
|
|
214
|
+
// since r56. The proportional bar stays because it adds a visual
|
|
215
|
+
// "shape of workload" that the chip numbers don't convey.
|
|
212
216
|
return (
|
|
213
217
|
<div className="mb-6">
|
|
214
218
|
<div className="flex h-2 rounded-full overflow-hidden bg-gray-800">
|
|
215
|
-
{bars.map(b => <div key={b.key} className={b.color} style={{ width: `${(stats[b.key]/total)*100}%` }} />)}
|
|
216
|
-
</div>
|
|
217
|
-
<div className="flex flex-wrap gap-3 mt-1.5 text-[10px] text-gray-500">
|
|
218
|
-
{bars.map(b => <span key={b.key}><span className={`inline-block w-2 h-2 rounded-full ${b.color} mr-1`} />{stats[b.key]} {b.key}</span>)}
|
|
219
|
+
{bars.map(b => <div key={b.key} className={b.color} style={{ width: `${(stats[b.key]/total)*100}%` }} title={`${b.key}: ${stats[b.key]}`} />)}
|
|
219
220
|
</div>
|
|
220
221
|
</div>
|
|
221
222
|
);
|
|
@@ -276,11 +277,13 @@ function TasksContent() {
|
|
|
276
277
|
<div className="col-span-1">
|
|
277
278
|
<span className={statusBadge(t.status)}>{t.status}</span>
|
|
278
279
|
</div>
|
|
279
|
-
<div className="col-span-2
|
|
280
|
-
{t.from_name
|
|
280
|
+
<div className="col-span-2 flex items-center gap-1.5 min-w-0" title={t.from_name}>
|
|
281
|
+
{t.from_name && <AliasAvatar alias={t.from_name} size={18} />}
|
|
282
|
+
<span className="truncate text-sm text-gray-200">{t.from_name || '--'}</span>
|
|
281
283
|
</div>
|
|
282
|
-
<div className="col-span-2
|
|
283
|
-
{t.to_name
|
|
284
|
+
<div className="col-span-2 flex items-center gap-1.5 min-w-0" title={t.to_name}>
|
|
285
|
+
{t.to_name && <AliasAvatar alias={t.to_name} size={18} />}
|
|
286
|
+
<span className="truncate text-sm text-gray-200">{t.to_name || '--'}</span>
|
|
284
287
|
</div>
|
|
285
288
|
<div className="col-span-4 text-xs text-gray-400 truncate" title={t.content}>
|
|
286
289
|
{t.content || '--'}
|
|
@@ -315,10 +318,12 @@ function TasksContent() {
|
|
|
315
318
|
</svg>
|
|
316
319
|
</div>
|
|
317
320
|
</div>
|
|
318
|
-
<div className="text-xs text-gray-
|
|
319
|
-
<
|
|
320
|
-
<span className="
|
|
321
|
-
<span className="text-
|
|
321
|
+
<div className="flex items-center gap-1.5 text-xs text-gray-300 min-w-0">
|
|
322
|
+
{t.from_name && <AliasAvatar alias={t.from_name} size={16} />}
|
|
323
|
+
<span className="truncate max-w-[40%]">{t.from_name || '--'}</span>
|
|
324
|
+
<span className="text-gray-600">→</span>
|
|
325
|
+
{t.to_name && <AliasAvatar alias={t.to_name} size={16} />}
|
|
326
|
+
<span className="truncate max-w-[40%]">{t.to_name || '--'}</span>
|
|
322
327
|
</div>
|
|
323
328
|
<div className="text-xs text-gray-400 line-clamp-1">{t.content || '--'}</div>
|
|
324
329
|
<div className="text-xs text-gray-600">{timeAgo(t.created_at)}</div>
|
package/package.json
CHANGED
|
@@ -0,0 +1,224 @@
|
|
|
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
|
+
await page.screenshot({ path: file, fullPage: true });
|
|
87
|
+
|
|
88
|
+
const checks = await page.evaluate(() => {
|
|
89
|
+
const doc = document.documentElement;
|
|
90
|
+
const theme = doc.getAttribute('data-theme');
|
|
91
|
+
const body = document.body;
|
|
92
|
+
const bodyBg = getComputedStyle(body).backgroundColor;
|
|
93
|
+
const bodyFg = getComputedStyle(body).color;
|
|
94
|
+
|
|
95
|
+
const viewportH = window.innerHeight;
|
|
96
|
+
const viewportW = window.innerWidth;
|
|
97
|
+
|
|
98
|
+
// --- CTA-above-fold ---
|
|
99
|
+
// We treat any of these texts as the page's primary CTA:
|
|
100
|
+
// "Spin up your first agent" — empty Overview onboarding
|
|
101
|
+
// "Get started" — generic
|
|
102
|
+
// "Dispatch" (when not empty) — populated state primary action
|
|
103
|
+
const ctaTextRe = /Spin up your first agent|Get started/i;
|
|
104
|
+
const ctaCandidates = [...document.querySelectorAll('h1, h2, h3, p, div, button, a')]
|
|
105
|
+
.filter(el => ctaTextRe.test(el.textContent || ''));
|
|
106
|
+
let ctaTop = null;
|
|
107
|
+
if (ctaCandidates.length) {
|
|
108
|
+
// Pick the smallest (innermost) element matching to avoid catching a wrapping section
|
|
109
|
+
const sorted = ctaCandidates.sort((a, b) => (a.textContent?.length || 0) - (b.textContent?.length || 0));
|
|
110
|
+
const r = sorted[0].getBoundingClientRect();
|
|
111
|
+
ctaTop = r.top;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// --- Zero-card density ---
|
|
115
|
+
// Count visible elements whose trimmed text === "0". These are usually
|
|
116
|
+
// big stat-card values dominating prime real estate.
|
|
117
|
+
const zeroCards = [...document.querySelectorAll('div, span')]
|
|
118
|
+
.filter(el => {
|
|
119
|
+
const t = el.textContent?.trim();
|
|
120
|
+
if (t !== '0' && t !== '0/0') return false;
|
|
121
|
+
const r = el.getBoundingClientRect();
|
|
122
|
+
return r.top >= 0 && r.top < viewportH && r.width > 20 && r.height > 20;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// --- Whitespace gap ---
|
|
126
|
+
// Look for sibling-to-sibling top gaps >200px in the page content
|
|
127
|
+
// container. AppShell wraps page children in
|
|
128
|
+
// <main><HealthBanner/><div><div class="min-h-screen ...">{page}</div></div></main>,
|
|
129
|
+
// so we walk into that inner page wrapper to read real section gaps.
|
|
130
|
+
const main = document.querySelector('main');
|
|
131
|
+
const inner = main?.querySelector(':scope > div:last-of-type > div') || main || document.body;
|
|
132
|
+
const directKids = [...inner.children].filter(el => {
|
|
133
|
+
const r = el.getBoundingClientRect();
|
|
134
|
+
return r.height > 0 && r.width > 0;
|
|
135
|
+
});
|
|
136
|
+
let maxGap = 0;
|
|
137
|
+
for (let i = 0; i + 1 < directKids.length; i++) {
|
|
138
|
+
const a = directKids[i].getBoundingClientRect();
|
|
139
|
+
const b = directKids[i + 1].getBoundingClientRect();
|
|
140
|
+
if (a.bottom < 1600 && b.top - a.bottom > maxGap) {
|
|
141
|
+
maxGap = Math.round(b.top - a.bottom);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// --- Misleading copy guard ---
|
|
146
|
+
// "All systems go" must not appear anywhere when nodes.total === 0.
|
|
147
|
+
// We can't read state directly, but if the page also shows
|
|
148
|
+
// "Waiting for first agent" then we know fleet is empty.
|
|
149
|
+
const bodyText = document.body.innerText;
|
|
150
|
+
const fleetEmpty = /Waiting for first agent/i.test(bodyText);
|
|
151
|
+
const sayingAllSystemsGo = /All systems go/i.test(bodyText);
|
|
152
|
+
const misleadingCopy = fleetEmpty && sayingAllSystemsGo;
|
|
153
|
+
|
|
154
|
+
// --- Carryover ---
|
|
155
|
+
const docWidth = doc.scrollWidth;
|
|
156
|
+
const horizontalOverflow = docWidth > viewportW + 2;
|
|
157
|
+
let glowyCount = 0;
|
|
158
|
+
for (const el of document.querySelectorAll('body *')) {
|
|
159
|
+
const cs = getComputedStyle(el);
|
|
160
|
+
if (cs.filter && cs.filter.includes('blur(') && !cs.filter.includes('blur(0px)')) glowyCount++;
|
|
161
|
+
}
|
|
162
|
+
const sidebar = document.querySelector('aside, [data-sidebar], nav');
|
|
163
|
+
const sidebarRect = sidebar?.getBoundingClientRect();
|
|
164
|
+
const sidebarVisible = !!(sidebarRect && sidebarRect.width > 0 && sidebarRect.height > 0);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
theme, bodyBg, bodyFg,
|
|
168
|
+
viewportH, viewportW,
|
|
169
|
+
ctaTop, ctaPresent: ctaCandidates.length > 0,
|
|
170
|
+
zeroCardCount: zeroCards.length,
|
|
171
|
+
maxGap,
|
|
172
|
+
fleetEmpty, sayingAllSystemsGo, misleadingCopy,
|
|
173
|
+
docWidth, horizontalOverflow,
|
|
174
|
+
glowyCount, sidebarVisible,
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
let score = 10;
|
|
179
|
+
const notes = [];
|
|
180
|
+
if (checks.theme !== m.theme) { score -= 3; notes.push(`theme=${checks.theme}`); }
|
|
181
|
+
if (checks.horizontalOverflow) { score -= 2; notes.push(`horiz overflow ${checks.docWidth}/${checks.viewportW}`); }
|
|
182
|
+
if (m.tag === 'desktop' && !checks.sidebarVisible) { score -= 1; notes.push('sidebar missing on desktop'); }
|
|
183
|
+
if (checks.glowyCount > 8) { score -= 1; notes.push(`${checks.glowyCount} blur-filter elements (glow tell)`); }
|
|
184
|
+
|
|
185
|
+
// New checks — the ones that would have caught r70's bug
|
|
186
|
+
if (checks.ctaPresent && checks.ctaTop !== null) {
|
|
187
|
+
if (checks.ctaTop > checks.viewportH) {
|
|
188
|
+
score -= 3;
|
|
189
|
+
notes.push(`CTA below fold (top=${Math.round(checks.ctaTop)} > vh=${checks.viewportH})`);
|
|
190
|
+
} else if (checks.ctaTop > checks.viewportH * 0.7) {
|
|
191
|
+
score -= 1;
|
|
192
|
+
notes.push(`CTA low in fold (top=${Math.round(checks.ctaTop)})`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (checks.zeroCardCount > 4) {
|
|
196
|
+
const extra = Math.min(checks.zeroCardCount - 4, 3);
|
|
197
|
+
score -= extra;
|
|
198
|
+
notes.push(`${checks.zeroCardCount} zero-value cards above fold`);
|
|
199
|
+
}
|
|
200
|
+
if (checks.maxGap > 200) {
|
|
201
|
+
score -= 1;
|
|
202
|
+
notes.push(`whitespace gap ${checks.maxGap}px`);
|
|
203
|
+
}
|
|
204
|
+
if (checks.misleadingCopy) {
|
|
205
|
+
score -= 2;
|
|
206
|
+
notes.push(`'All systems go' shown while fleet empty`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
report.push({ ...m, file, checks, score: Math.max(score, 0), notes });
|
|
210
|
+
|
|
211
|
+
await page.close();
|
|
212
|
+
await ctx.close();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
await browser.close();
|
|
216
|
+
|
|
217
|
+
console.log('\n=== Loop self-review (harness r73) ===\n');
|
|
218
|
+
for (const r of report) {
|
|
219
|
+
console.log(`${r.pageName.padEnd(8)} ${r.theme}/${r.tag.padEnd(7)} score=${r.score}/10`);
|
|
220
|
+
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}`);
|
|
221
|
+
if (r.notes.length) console.log(` notes: ${r.notes.join(' · ')}`);
|
|
222
|
+
}
|
|
223
|
+
const avg = report.reduce((s, r) => s + r.score, 0) / report.length;
|
|
224
|
+
console.log(`\noverall avg = ${avg.toFixed(1)}/10 across ${report.length} captures`);
|