"use client"; import Link from "next/link"; import { ArrowRight } from "lucide-react"; import { useApi } from "@/lib/api"; import { ago, fmtNum, truncate } from "@/lib/format"; import { Bar, Empty, Kpi, Panel, Skeleton, StatusPill, Tag } from "@/components/ui"; import { ActivityChart, SurfaceBars, TypeDonut, YieldBars } from "@/components/charts"; import { LiveFeed } from "@/components/LiveFeed"; interface Overview { totals: { sessions: number; entities: number; media: number; schemas: number; actions: number; observations: number; relationships: number; patterns_learned: number; feed_items: number }; by_type: { platform: string; entity_type: string; n: number }[]; running: { session_id: string; platform: string; started_at: string }[]; recent_sessions: { session_id: string; platform: string; goal: string | null; mode: string | null; started_at: string; ended_at: string | null; health: string; actions: number; entities: number }[]; series: { bucket: string; entities: number; responses: number; actions: number; schemas: number }[]; surfaces: { dom: number; network: number; both: number; agreements: number; conflicts: number }; actions: { action_type: string; n: number; avg_gain: number | null; success_rate: number | null; avg_new_entities: number | null }[]; platforms: { platform: string; learned: boolean; adapter: boolean; confidence: number; page_types: number; entity_types: number; network_schemas: number; sessions: number; has_manifest: boolean }[]; } export default function Observatory() { const { data, loading } = useApi("/overview", { refreshMs: 8000 }); const t = data?.totals; const s = data?.surfaces; const agreement = s && s.agreements + s.conflicts > 0 ? s.agreements / (s.agreements + s.conflicts) : null; const spark = (k: keyof Overview["series"][number]) => (data?.series ?? []).slice(-30).map((r) => Number(r[k])); return (

Observatory

The crawler does not download pages. It watches social applications run inside an authenticated browser, fuses what the network and the DOM reveal, and learns the platform as it goes.

{data?.running.map((r) => ( {r.platform} {ago(r.started_at)} ))} New mission
{loading ? : data?.series.length ? : No activity yet — launch a mission.} {data?.by_type.length ? : —}
{s ? : }
field agreements {fmtNum(s?.agreements)} · conflicts {fmtNum(s?.conflicts)}
{data?.actions.length ? : —}
all →} pad={false}> {data?.recent_sessions.length ? (
    {data.recent_sessions.map((r) => (
  • {r.platform} {truncate(r.goal ?? "(no goal)", 70)} {r.actions} steps · {r.entities} ent · {ago(r.started_at)}
  • ))}
) : ( No sessions yet. )}
details →}>
{data?.platforms .filter((p) => p.adapter || p.learned) .map((p) => (
{p.platform} = 70 ? "#43e69a" : p.confidence >= 40 ? "#38e1ff" : "#ffb347"} className="flex-1" /> {p.confidence}%
{p.page_types} page types · {p.entity_types} entity types · {p.network_schemas} schemas · {p.sessions} sessions{p.has_manifest ? " · compiled" : ""}
))}
); }