From e6c352dea9d75444996004e31d057cb35a3b5eb8 Mon Sep 17 00:00:00 2001 From: Christopher Date: Tue, 19 May 2026 17:28:45 +1000 Subject: [PATCH 1/2] fix(studio): restore multi-project dashboard UX Restore the project dashboard parity with single-project mode by bringing back the full experiments table, clickable experiment rows, tab icons, and the active-run log section. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- apps/studio/src/components/ExperimentsTab.tsx | 13 ++- .../studio/src/routes/projects/$projectId.tsx | 93 ++++++++----------- 2 files changed, 51 insertions(+), 55 deletions(-) diff --git a/apps/studio/src/components/ExperimentsTab.tsx b/apps/studio/src/components/ExperimentsTab.tsx index 90fab6534..54588be29 100644 --- a/apps/studio/src/components/ExperimentsTab.tsx +++ b/apps/studio/src/components/ExperimentsTab.tsx @@ -5,15 +5,22 @@ * last run timestamp. Each row links to the experiment detail page. */ +import { useQuery } from '@tanstack/react-query'; import { Link } from '@tanstack/react-router'; -import { useExperiments } from '~/lib/api'; +import { projectExperimentsOptions, useExperiments } from '~/lib/api'; import type { ExperimentSummary } from '~/lib/types'; import { PassRatePill } from './PassRatePill'; -export function ExperimentsTab() { - const { data, isLoading } = useExperiments(); +interface ExperimentsTabProps { + projectId?: string; +} + +export function ExperimentsTab({ projectId }: ExperimentsTabProps) { + const { data, isLoading } = projectId + ? useQuery(projectExperimentsOptions(projectId)) + : useExperiments(); if (isLoading) { return ; diff --git a/apps/studio/src/routes/projects/$projectId.tsx b/apps/studio/src/routes/projects/$projectId.tsx index a70697b88..28c242c54 100644 --- a/apps/studio/src/routes/projects/$projectId.tsx +++ b/apps/studio/src/routes/projects/$projectId.tsx @@ -4,32 +4,32 @@ * Mirrors the single-project home page but fetches from project-scoped API endpoints. */ -import { createFileRoute, useNavigate, useRouterState } from '@tanstack/react-router'; +import { Link, createFileRoute, useNavigate, useRouterState } from '@tanstack/react-router'; import { useState } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { AnalyticsTab } from '~/components/AnalyticsTab'; +import { ExperimentsTab } from '~/components/ExperimentsTab'; import { RunEvalModal } from '~/components/RunEvalModal'; import { RunList } from '~/components/RunList'; import { type RunSourceFilter, RunSourceToolbar } from '~/components/RunSourceToolbar'; import { TargetsTab } from '~/components/TargetsTab'; import { projectCompareOptions, - projectExperimentsOptions, syncRemoteResultsApi, + useEvalRuns, useProjectRunList, useRemoteStatus, useStudioConfig, } from '~/lib/api'; -import type { ExperimentsResponse } from '~/lib/types'; type TabId = 'runs' | 'experiments' | 'analytics' | 'targets'; const tabs: { id: TabId; label: string }[] = [ - { id: 'runs', label: 'Recent Runs' }, - { id: 'experiments', label: 'Experiments' }, - { id: 'analytics', label: 'Analytics' }, - { id: 'targets', label: 'Targets' }, + { id: 'runs', label: '๐Ÿƒ Recent Runs' }, + { id: 'experiments', label: '๐Ÿงช Experiments' }, + { id: 'analytics', label: '๐Ÿ“Š Analytics' }, + { id: 'targets', label: '๐Ÿค– Targets' }, ]; export const Route = createFileRoute('/projects/$projectId')({ @@ -90,7 +90,7 @@ function ProjectHomePage() { {activeTab === 'runs' && } - {activeTab === 'experiments' && } + {activeTab === 'experiments' && } {activeTab === 'analytics' && ( )} @@ -110,9 +110,13 @@ function ProjectHomePage() { function ProjectRunsTab({ projectId }: { projectId: string }) { const queryClient = useQueryClient(); const { data, isLoading, error } = useProjectRunList(projectId); + const { data: activeRunsData } = useEvalRuns(projectId); const { data: remoteStatus } = useRemoteStatus(projectId); const [sourceFilter, setSourceFilter] = useState('all'); const [syncInFlight, setSyncInFlight] = useState(false); + const activeRuns = (activeRunsData?.runs ?? []).filter( + (run) => run.status === 'starting' || run.status === 'running', + ); const filteredRuns = sourceFilter === 'all' @@ -155,6 +159,35 @@ function ProjectRunsTab({ projectId }: { projectId: string }) { return (
+ {activeRuns.length > 0 && ( +
+
+ + Active + +
+
    + {activeRuns.map((run) => ( +
  • +
    + + {run.id} + + {new Date(run.started_at).toLocaleTimeString()} + +
    + + View Log โ†’ + +
  • + ))} +
+
+ )} - {['s1', 's2', 's3'].map((id) => ( -
- ))} -
- ); - } - - if (experiments.length === 0) { - return ( -
-

No experiments found

-
- ); - } - - return ( -
- {experiments.map((exp) => ( -
-
-

{exp.name}

-

- {exp.run_count} run{exp.run_count !== 1 ? 's' : ''} -

-
- - {Math.round(exp.pass_rate * 100)}% - -
- ))} -
- ); -} - function ProjectAnalyticsTab({ projectId, readOnly, From ccf7f5682df39876535179da17a1a8418702f278 Mon Sep 17 00:00:00 2001 From: Christopher Date: Tue, 19 May 2026 17:35:41 +1000 Subject: [PATCH 2/2] test(results): isolate project registry in serve tests Stub os.homedir() in the all-runs registry test so it uses a temporary projects.yaml and no longer sees unrelated local projects. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- apps/cli/test/commands/results/serve.test.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/cli/test/commands/results/serve.test.ts b/apps/cli/test/commands/results/serve.test.ts index bb5aa1cf9..7d82771f9 100644 --- a/apps/cli/test/commands/results/serve.test.ts +++ b/apps/cli/test/commands/results/serve.test.ts @@ -1,5 +1,6 @@ -import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; +import { afterEach, beforeEach, describe, expect, it, spyOn } from 'bun:test'; import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import os from 'node:os'; import { tmpdir } from 'node:os'; import path from 'node:path'; @@ -544,8 +545,7 @@ describe('serve app', () => { describe('GET /api/projects/all-runs', () => { it('infers experiment names for live benchmark runs before records persist them', async () => { - const previousHome = process.env.AGENTV_HOME; - process.env.AGENTV_HOME = path.join(tempDir, 'agentv-home'); + const homedirSpy = spyOn(os, 'homedir').mockReturnValue(path.join(tempDir, 'home')); try { const benchmarkDir = path.join(tempDir, 'bench-one'); @@ -575,11 +575,7 @@ describe('serve app', () => { target: 'gpt-4o', }); } finally { - if (previousHome === undefined) { - process.env.AGENTV_HOME = undefined; - } else { - process.env.AGENTV_HOME = previousHome; - } + homedirSpy.mockRestore(); } }); });