diff --git a/apps/cli/src/commands/results/serve.ts b/apps/cli/src/commands/results/serve.ts index d631c022e..555a3a440 100644 --- a/apps/cli/src/commands/results/serve.ts +++ b/apps/cli/src/commands/results/serve.ts @@ -725,6 +725,61 @@ export function createApp( } }); + /** Aggregate runs from all registered projects, sorted by timestamp descending. */ + app.get('/api/projects/all-runs', (c) => { + const registry = loadProjectRegistry(); + const allRuns: Array<{ + filename: string; + path: string; + timestamp: string; + test_count: number; + pass_rate: number; + avg_score: number; + size_bytes: number; + target?: string; + experiment?: string; + project_id: string; + project_name: string; + }> = []; + + for (const p of registry.projects) { + try { + const metas = listResultFiles(p.path); + for (const m of metas) { + let target: string | undefined; + let experiment: string | undefined; + try { + const records = loadLightweightResults(m.path); + if (records.length > 0) { + target = records[0].target; + experiment = records[0].experiment; + } + } catch { + // ignore enrichment errors + } + allRuns.push({ + filename: m.filename, + path: m.path, + timestamp: m.timestamp, + test_count: m.testCount, + pass_rate: m.passRate, + avg_score: m.avgScore, + size_bytes: m.sizeBytes, + ...(target && { target }), + ...(experiment && { experiment }), + project_id: p.id, + project_name: p.name, + }); + } + } catch { + // skip inaccessible projects + } + } + + allRuns.sort((a, b) => b.timestamp.localeCompare(a.timestamp)); + return c.json({ runs: allRuns }); + }); + // ── Data routes (unscoped) ──────────────────────────────────────────── app.get('/api/config', (c) => handleConfig(c, defaultCtx)); diff --git a/apps/studio/src/components/Sidebar.tsx b/apps/studio/src/components/Sidebar.tsx index 6758ac33a..eed55444a 100644 --- a/apps/studio/src/components/Sidebar.tsx +++ b/apps/studio/src/components/Sidebar.tsx @@ -12,8 +12,10 @@ import { Link, useMatchRoute } from '@tanstack/react-router'; import { isPassing, + useAllProjectRuns, useCategoryDatasets, useExperiments, + useProjectList, useProjectRunDetail, useProjectRunList, useRunDetail, @@ -104,11 +106,19 @@ export function Sidebar() { function RunSidebar() { const matchRoute = useMatchRoute(); - const { data } = useRunList(); + const { data: projectData } = useProjectList(); + const hasProjects = (projectData?.projects.length ?? 0) > 0; const isHome = matchRoute({ to: '/' }); const runMatch = matchRoute({ to: '/runs/$runId', fuzzy: true }); + // On the projects landing page, show aggregated runs from all projects + const useAggregated = hasProjects && isHome !== false; + + const { data: localData } = useRunList(); + const { data: aggregatedData } = useAllProjectRuns(); + const data = useAggregated ? aggregatedData : localData; + return (