+ {model.emptyMessage ? (
+
+
{model.status}
+
{model.emptyMessage}
+
+ ) : null}
+
+
+ ({
+ key: row.key,
+ value: rowValue(row.value),
+ mono: true,
+ }))}
+ />
+ ({
+ key: row.key,
+ value: rowValue(row.value),
+ mono: true,
+ }))}
+ />
+
+
+ {model.chainRows.length > 0 ? (
+ ({
+ key: row.key,
+ value: rowValue(row.value),
+ mono: true,
+ }))}
+ />
+ ) : null}
+
+
+ {model.detailSections.map((section) => (
+
+ ))}
+
+
+
+
+ Source spans
+
+ {model.sourceSpanRows.length > 0 ? (
+ ({
+ key: row.key,
+ value: rowValue(row.value),
+ mono: true,
+ }))}
+ />
+ ) : (
+
+ No source spans recorded.
+
+ )}
+
+
+
+
+ reproducer
+
+
+ {model.reproducer}
+
+
+
+ {model.showRaw ? (
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/workbench-ui/src/app/trace/practiceEvidenceEndpoint.test.ts b/workbench-ui/src/app/trace/practiceEvidenceEndpoint.test.ts
new file mode 100644
index 00000000..4b8d3105
--- /dev/null
+++ b/workbench-ui/src/app/trace/practiceEvidenceEndpoint.test.ts
@@ -0,0 +1,40 @@
+import { describe, expect, it, vi } from "vitest";
+import type { PracticeEvidence } from "../../types/practiceEvidence";
+import {
+ fetchTracePracticeWith,
+ tracePracticePath,
+ tracePracticeReproducer,
+} from "./practiceEvidenceEndpoint";
+
+const evidence: PracticeEvidence = {
+ schema_version: "practice_evidence_v1",
+ turn_id: 7,
+ status: "missing_evidence",
+ missing_reason: "sealed practice evidence was not persisted for this turn",
+ record_kind: null,
+ practice_disposition: null,
+ chain: [],
+ sealed_trace: null,
+ trace_refusal: null,
+ diagnostic_only: true,
+ serving_allowed: false,
+ mutation_allowed: false,
+ replay_execution_allowed: false,
+ replay_executed_by_workbench: false,
+};
+
+describe("practice evidence endpoint helpers", () => {
+ it("builds encoded trace practice paths", () => {
+ expect(tracePracticePath(7)).toBe("/trace/7/practice");
+ expect(tracePracticeReproducer(7)).toBe("curl /trace/7/practice");
+ });
+
+ it("fetches practice evidence through an injected fetcher", async () => {
+ const fetcher = vi.fn(