@@ -6176,6 +6259,275 @@ function PublishPanel({ run, authToken }: { run: RunResponse | null; authToken:
);
}
+type OverviewNamespace = {
+ namespace: string;
+ displayName: string;
+ target: string;
+ entities: number;
+ relationships: number;
+ topics: number;
+ questions: number;
+ proof?: { blobId: string; certified: boolean } | null;
+};
+
+// Short relative token ("5m" / "2h" / "3d"). Floors sub-minute to "1m" so it reads
+// cleanly inside an "{x} ago" template. Returns "—" for an unparseable timestamp.
+function ovRelativeTime(value: string): string {
+ const then = new Date(value).getTime();
+ if (Number.isNaN(then)) return "—";
+ const minutes = Math.max(1, Math.floor((Date.now() - then) / 60000));
+ if (minutes < 60) return `${minutes}m`;
+ const hours = Math.floor(minutes / 60);
+ if (hours < 24) return `${hours}h`;
+ const days = Math.floor(hours / 24);
+ if (days < 7) return `${days}d`;
+ const weeks = Math.floor(days / 7);
+ if (weeks < 5) return `${weeks}w`;
+ return `${Math.floor(days / 30)}mo`;
+}
+
+function ovRunStatus(item: RunHistoryItem): { label: string; tone: "done" | "running" | "failed" } {
+ const status = (item.status ?? "").toLowerCase();
+ if ((item.errors?.length ?? 0) > 0 || status === "failed" || status === "error") {
+ return { label: "failed", tone: "failed" };
+ }
+ if (status === "running" || status === "queued" || status === "processing" || status === "pending") {
+ return { label: "running", tone: "running" };
+ }
+ return { label: "done", tone: "done" };
+}
+
+// In-app Overview / Home dashboard — mirrors the "ContextMeM Dashboard" comp's
+// Overview section. Honest data only: namespaces from the public facts catalog
+// (same fetch + shape as NamespacesSimplePage), runs/alerts from the in-memory run
+// history. Public (rendered with allowAnon) like the Namespaces gallery.
+function OverviewAppPage({ history }: { history: RunHistoryItem[] }) {
+ const navigate = useNavigate();
+ const [namespaces, setNamespaces] = useState([]);
+ const [busy, setBusy] = useState(true);
+ const [copiedNs, setCopiedNs] = useState(null);
+
+ useEffect(() => {
+ let cancelled = false;
+ void (async () => {
+ try {
+ const response = await fetch(`${API_BASE}/api/memwal/facts`);
+ const body = (await response.json()) as { namespaces?: OverviewNamespace[] };
+ if (!cancelled) setNamespaces(body.namespaces ?? []);
+ } catch {
+ /* leave empty */
+ } finally {
+ if (!cancelled) setBusy(false);
+ }
+ })();
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ const totalArtifacts = namespaces.reduce((sum, ns) => sum + (ns.entities ?? 0), 0);
+ const certifiedCount = namespaces.filter((ns) => ns.proof?.certified).length;
+ const allCertified = namespaces.length > 0 && certifiedCount === namespaces.length;
+ const recentRuns = history.slice(0, 4);
+ const mostRecent = history.length
+ ? history.reduce((a, b) => (new Date(b.updatedAt).getTime() > new Date(a.updatedAt).getTime() ? b : a))
+ : null;
+ const alertRuns = history.filter(
+ (item) => (item.errors?.length ?? 0) > 0 || (item.status ?? "").toLowerCase() === "failed"
+ );
+ const openAlerts = alertRuns.length;
+
+ function copyMcp(namespace: string) {
+ // Same construction the app uses elsewhere: MCP must hit the worker gateway, so
+ // build from API_BASE on hosted, fall back to window.origin only on localhost.
+ const mcpBase = isLocalApiBase(API_BASE) ? window.location.origin : API_BASE;
+ const mcpUrl = `${mcpBase}/mcp?namespace=${encodeURIComponent(namespace)}`;
+ void navigator.clipboard.writeText(mcpUrl);
+ setCopiedNs(namespace);
+ window.setTimeout(() => setCopiedNs((current) => (current === namespace ? null : current)), 1600);
+ }
+
+ return (
+
+ {/* The shell topbar (AppShell) already renders the eyebrow + h1 "Overview" +
+ subtitle for this route, so the page body only adds the Build action — same
+ convention as every other app page (Runs, Artifacts, …). */}
+