/** * dashboard-client/src/tabs/ReposTab.tsx — Repos tab (C2 fleshed out). * * Renders: SummaryTiles (4 tiles), All Repositories table (RepoTable), * Active Repos table (ActiveReposTable), Savings by Model table * (SavingsByModelTable), and per-repo detail modal (RepoDetailModal). * * Data sources: fetchIndex() for summary + all repos + savings-by-model; * fetchServers() for active repos. Both polled every 10s. */ import type React from "react"; import { useCallback, useState } from "react"; import { useApi } from "../hooks/useApi"; import { fetchIndex, fetchServers } from "../api/client"; import type { IndexesIndexRow, ServersResponse } from "@contracts"; import { SummaryTiles } from "../components/SummaryTiles"; import { RepoTable } from "../components/RepoTable"; import { RepoDetailModal } from "../components/RepoDetailModal"; import { ActiveReposTable } from "../components/ActiveReposTable"; import { SavingsByModelTable } from "../components/SavingsByModelTable"; /** * Actual runtime shape of /api/index. The contract's IndexesSummaryResponse * puts summary fields at the top level, but the real server nests them under * `summary` (matching IndexFallbackResponse shape with non-null summary). */ interface DashboardIndexSummary { totalRepos: number; totalCheckpoints: number; totalTokensSaved: number; totalCompressedOriginalBytes: number; } interface DashboardIndexResponse { updatedAt: string | null; summary: DashboardIndexSummary | null; repos: IndexesIndexRow[]; } /** Fetch /api/index and cast to the actual runtime shape. */ async function fetchIndexTyped(): Promise { return (await fetchIndex()) as unknown as DashboardIndexResponse; } export default function ReposTab(): React.ReactElement { const [selected, setSelected] = useState(null); const { data: indexData, error: indexErr } = useApi( useCallback(() => fetchIndexTyped(), []), { pollInterval: 10_000 }, ); const { data: serversData } = useApi( useCallback(() => fetchServers(), []), { pollInterval: 10_000 }, ); if (indexErr && !indexData) { return (
Error loading repos: {indexErr.message}
); } if (!indexData) { return
Loading repos…
; } const summary = indexData.summary; const repos = indexData.repos; const servers = serversData?.servers ?? []; if (repos.length === 0) { return (

Repositories / Memory

Repo memory appears after the first compaction. Compaction fires when context crosses the tier threshold (~50-70% of the window). Run a longer session or lower the compaction tier to see it sooner.

); } return (

All Repositories

Active Repos — Live Cache Hits & Compactions

Savings by Model

{selected && ( setSelected(null)} /> )}
); }