diff --git a/ergon-dashboard/RUN_VISIBILITY_PLAN.md b/ergon-dashboard/RUN_VISIBILITY_PLAN.md
index 01a0c5afd..7bfc32e0a 100644
--- a/ergon-dashboard/RUN_VISIBILITY_PLAN.md
+++ b/ergon-dashboard/RUN_VISIBILITY_PLAN.md
@@ -34,7 +34,7 @@ These land first because every workstream depends on them.
type RunEvent =
| { kind: "workflow.started"; at: string; runId: string; ... }
| { kind: "task.status_changed"; at: string; taskId: string; from: TaskStatus; to: TaskStatus; trigger: TaskTrigger; ... }
- | { kind: "generation.turn"; at: string; taskExecutionId: string; turnIndex: number; ... }
+ | { kind: "generation.turn"; at: string; taskAttemptId: string; turnIndex: number; ... }
| { kind: "sandbox.command"; ... }
| { kind: "thread.message"; ... }
| { kind: "task.evaluation"; ... }
diff --git a/ergon-dashboard/scripts/generate-event-contracts.mjs b/ergon-dashboard/scripts/generate-event-contracts.mjs
index 607f4fa4c..ec32502c0 100644
--- a/ergon-dashboard/scripts/generate-event-contracts.mjs
+++ b/ergon-dashboard/scripts/generate-event-contracts.mjs
@@ -41,15 +41,6 @@ function patchZod4Compat(source) {
}
function customEventContractSource(entry) {
- if (entry.modelName === "DashboardGraphMutationEvent") {
- return `import { z } from "zod";
-import { GraphMutationDtoSchema } from "@/features/graph/contracts/graphMutations";
-
-export const DashboardGraphMutationEventSchema = z.object({
- mutation: GraphMutationDtoSchema,
-}).catchall(z.any());
-`;
- }
if (entry.modelName === "DashboardSampleRuntimeEvent") {
return `import { z } from "zod";
import { SampleRuntimeEventViewSchema } from "@/lib/contracts/rest";
diff --git a/ergon-dashboard/scripts/generate-rest-contracts.mjs b/ergon-dashboard/scripts/generate-rest-contracts.mjs
index 3e27f52ea..6fb74ff38 100644
--- a/ergon-dashboard/scripts/generate-rest-contracts.mjs
+++ b/ergon-dashboard/scripts/generate-rest-contracts.mjs
@@ -28,6 +28,13 @@ let source = readFileSync(contractsPath, "utf8")
);
const sampleRuntimeEventDiscriminators = {
+ // openapi-zod-client currently loses literal discriminators for this Pydantic
+ // discriminated union. The dashboard relies on eventType narrowing, so this
+ // post-process keeps SampleRuntimeEventView type-safe until the generator can
+ // emit the correct z.discriminatedUnion shape directly.
+ //
+ // When adding a new backend SampleRuntimeEventView union member, add the
+ // schema name and eventType here as well, then regenerate contracts.
SampleStatusChangedEventView: "sample.status_changed",
SampleTaskAddedEventView: "task.added",
SampleTaskRemovedEventView: "task.removed",
diff --git a/ergon-dashboard/src/app/api/samples/[sampleId]/events/route.ts b/ergon-dashboard/src/app/api/samples/[sampleId]/events/route.ts
index 7ecf31d56..dd9d21d1c 100644
--- a/ergon-dashboard/src/app/api/samples/[sampleId]/events/route.ts
+++ b/ergon-dashboard/src/app/api/samples/[sampleId]/events/route.ts
@@ -1,6 +1,6 @@
import { NextResponse } from "next/server";
-import { fetchErgonApi } from "@/lib/serverApi";
+import { loadSampleEvents } from "@/lib/server-data/samples";
interface RouteContext {
params: Promise<{
@@ -10,18 +10,10 @@ interface RouteContext {
export async function GET(_request: Request, context: RouteContext) {
const { sampleId } = await context.params;
+ const result = await loadSampleEvents(sampleId);
- try {
- const response = await fetchErgonApi(`/samples/${sampleId}/events`);
- const body = await response.json();
- return NextResponse.json(body, { status: response.status });
- } catch (error) {
- return NextResponse.json(
- {
- detail: `Ergon API is unavailable while loading events for sample ${sampleId}.`,
- error: error instanceof Error ? error.message : "Unknown backend fetch failure",
- },
- { status: 503 },
- );
+ if (result.ok) {
+ return NextResponse.json(result.data, { status: result.status });
}
+ return NextResponse.json(result.body, { status: result.status });
}
diff --git a/ergon-dashboard/src/app/samples/[sampleId]/detail/page.tsx b/ergon-dashboard/src/app/samples/[sampleId]/detail/page.tsx
deleted file mode 100644
index 13ca16277..000000000
--- a/ergon-dashboard/src/app/samples/[sampleId]/detail/page.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { notFound } from "next/navigation";
-
-import { SampleDetail } from "@/components/samples/SampleDetail";
-import { loadSampleState } from "@/lib/server-data/samples";
-
-interface SampleDetailPageProps {
- params: Promise<{
- sampleId: string;
- }>;
-}
-
-export default async function SampleDetailPage({ params }: SampleDetailPageProps) {
- const { sampleId } = await params;
- const result = await loadSampleState(sampleId);
- if (!result.ok) {
- if (result.status === 404) notFound();
- throw new Error(`Failed to load sample ${sampleId}: ${result.status}`);
- }
-
- return ;
-}
diff --git a/ergon-dashboard/src/app/samples/[sampleId]/page.tsx b/ergon-dashboard/src/app/samples/[sampleId]/page.tsx
index 0b5e79c52..96769d641 100644
--- a/ergon-dashboard/src/app/samples/[sampleId]/page.tsx
+++ b/ergon-dashboard/src/app/samples/[sampleId]/page.tsx
@@ -1,4 +1,7 @@
-import { redirect } from "next/navigation";
+import { notFound } from "next/navigation";
+
+import { SampleWorkspacePage } from "@/components/sample/SampleWorkspacePage";
+import { loadSampleSnapshot } from "@/lib/server-data/samples";
interface SamplePageProps {
params: Promise<{
@@ -8,5 +11,11 @@ interface SamplePageProps {
export default async function SamplePage({ params }: SamplePageProps) {
const { sampleId } = await params;
- redirect(`/samples/${sampleId}/detail`);
+ const result = await loadSampleSnapshot(sampleId);
+ if (!result.ok) {
+ if (result.status === 404) notFound();
+ return ;
+ }
+
+ return ;
}
diff --git a/ergon-dashboard/src/app/samples/page.tsx b/ergon-dashboard/src/app/samples/page.tsx
index 42d8ce6ca..fac9d73f0 100644
--- a/ergon-dashboard/src/app/samples/page.tsx
+++ b/ergon-dashboard/src/app/samples/page.tsx
@@ -1,29 +1,33 @@
import { SampleIndexTable } from "@/components/indexes/SampleIndexTable";
import { loadSampleList, type SampleSummary } from "@/lib/server-data/samples";
-export default async function RunsPage() {
- let runs: SampleSummary[] = [];
+export default async function SamplesPage() {
+ let samples: SampleSummary[] = [];
let error: string | null = null;
const result = await loadSampleList({ limit: 100 });
if (result.ok) {
- runs = result.data;
+ samples = result.data;
} else {
const detail = (result.body as { detail?: string })?.detail;
error = detail ?? `API returned ${result.status}`;
}
- const runningCount = runs.filter((run) => ["executing", "evaluating"].includes(run.status)).length;
- const failedCount = runs.filter((run) => run.status === "failed" || run.failed_tasks > 0).length;
- const completedCount = runs.filter((run) => run.status === "completed").length;
+ const runningCount = samples.filter((sample) =>
+ ["executing", "evaluating"].includes(sample.status),
+ ).length;
+ const failedCount = samples.filter(
+ (sample) => sample.status === "failed" || sample.failed_tasks > 0,
+ ).length;
+ const completedCount = samples.filter((sample) => sample.status === "completed").length;
return (
- Run Index
+ Sample Index
-
Runs
+
Samples
{error ? (
@@ -47,7 +51,7 @@ export default async function RunsPage() {
-
+
);
}
diff --git a/ergon-dashboard/src/components/experiments/SampleTable.tsx b/ergon-dashboard/src/components/experiments/SampleTable.tsx
index fb0ca5ad2..b72c6fb79 100644
--- a/ergon-dashboard/src/components/experiments/SampleTable.tsx
+++ b/ergon-dashboard/src/components/experiments/SampleTable.tsx
@@ -26,7 +26,7 @@ export function SampleTable({ samples }: { samples: ExperimentSampleSummaryView[
>
{sample.sampleKey}
diff --git a/ergon-dashboard/src/components/experiments/sampleRunMetricExplorerModel.test.ts b/ergon-dashboard/src/components/experiments/sampleRunMetricExplorerModel.test.ts
index 4837b3f5f..1d7685e95 100644
--- a/ergon-dashboard/src/components/experiments/sampleRunMetricExplorerModel.test.ts
+++ b/ergon-dashboard/src/components/experiments/sampleRunMetricExplorerModel.test.ts
@@ -11,7 +11,7 @@ import {
type RunMetricPoint,
} from "./sampleRunMetricExplorerModel";
-const definitionId = "11111111-1111-4111-8111-111111111111";
+const experimentId = "11111111-1111-4111-8111-111111111111";
const defaultSampleId = "22222222-2222-4222-8222-222222222222";
function metrics(overrides: Partial = {}): ExperimentRunRow["metrics"] {
@@ -28,7 +28,7 @@ function metrics(overrides: Partial = {}): Experime
function runRow(overrides: Partial = {}): ExperimentRunRow {
return {
sample_id: defaultSampleId,
- definition_id: definitionId,
+ experiment_id: experimentId,
benchmark_type: "minif2f",
instance_key: "sample-a",
status: "completed",
diff --git a/ergon-dashboard/src/components/indexes/SampleIndexTable.tsx b/ergon-dashboard/src/components/indexes/SampleIndexTable.tsx
index ecd1fabe4..7d2ade3a1 100644
--- a/ergon-dashboard/src/components/indexes/SampleIndexTable.tsx
+++ b/ergon-dashboard/src/components/indexes/SampleIndexTable.tsx
@@ -23,7 +23,6 @@ export function SampleIndexTable({ runs }: { runs: SampleSummary[] }) {
const matchesStatus = status === "all" || run.status === status;
const text = [
run.name,
- run.definition_name,
run.experiment,
run.benchmark_type,
run.instance_key,
@@ -45,7 +44,7 @@ export function SampleIndexTable({ runs }: { runs: SampleSummary[] }) {
setQuery(event.target.value)}
- placeholder="Search runs"
+ placeholder="Search samples"
className="h-9 w-full max-w-sm rounded-md border border-[var(--line)] bg-[var(--card)] px-3 text-sm text-[var(--ink)] outline-none focus:border-[var(--ink-2)]"
/>
|