From 9f37777776eb231e621ad64227c3f12569ce163c Mon Sep 17 00:00:00 2001
From: Cursor Agent
diff --git a/apps/web/app/evidence/page.tsx b/apps/web/app/evidence/page.tsx
index c947e18..b7a9f59 100644
--- a/apps/web/app/evidence/page.tsx
+++ b/apps/web/app/evidence/page.tsx
@@ -7,6 +7,7 @@ import {
ResponsiveTable,
TruncateText,
} from "../../components/ResponsiveTable";
+import { StaleIndexBanner } from "../../components/StaleIndexBanner";
import { loadAnalysisIndex, loadRunArtifacts } from "../../lib/data";
import {
aggregateEvidenceStrings,
@@ -88,6 +89,7 @@ export default async function EvidenceExplorerPage({
return (
diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css
index d2d7506..bce90ee 100644
--- a/apps/web/app/globals.css
+++ b/apps/web/app/globals.css
@@ -1990,6 +1990,22 @@ pre {
line-height: 1.35;
}
+.experiment-matrix-cell-wrap {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+}
+
+.experiment-matrix-compare {
+ color: var(--color-accent);
+ font-size: 0.75rem;
+ text-decoration: none;
+}
+
+.experiment-matrix-compare:hover {
+ text-decoration: underline;
+}
+
/* Heatmap improvements */
.benchmark-heatmap-wrap {
overflow-x: auto;
diff --git a/apps/web/app/leaderboard/compare/page.tsx b/apps/web/app/leaderboard/compare/page.tsx
index 187aa75..43fc4a9 100644
--- a/apps/web/app/leaderboard/compare/page.tsx
+++ b/apps/web/app/leaderboard/compare/page.tsx
@@ -1,11 +1,18 @@
import type { Metadata } from "next";
import Link from "next/link";
+import { ActiveIndexFiltersNotice } from "../../../components/ActiveIndexFiltersNotice";
import { CompareDeltaChart } from "../../../components/charts/CompareDeltaChart";
import { CompareExportLink } from "../../../components/CompareExportLink";
+import { CompareFormFilterFields } from "../../../components/CompareFormFilterFields";
import { CompareSwapLink } from "../../../components/CompareSwapLink";
import { MetricCard } from "../../../components/MetricCard";
import { ModelFilterSelect } from "../../../components/ModelFilterSelect";
import { loadAnalysisIndex } from "../../../lib/data";
+import {
+ indexFilterExtraParams,
+ pickIndexFilterParams,
+} from "../../../lib/compareFilterParams";
+import { applyIndexFilters } from "../../../lib/indexFilters";
import { buildModelLeaderboard } from "../../../lib/modelLeaderboard";
import { buildModelComparePayload } from "../../../lib/modelCompare";
import { buildLeaderboardCompareSuggestions } from "../../../lib/leaderboardCompareSuggestions";
@@ -17,6 +24,12 @@ export const metadata: Metadata = {
type ModelCompareSearchParams = {
left?: string;
right?: string;
+ q?: string;
+ model?: string;
+ preset?: string;
+ fast?: string;
+ from?: string;
+ to?: string;
};
function formatMetric(value: number | null, digits = 2) {
@@ -35,9 +48,9 @@ export default async function ModelComparePage({
searchParams: PromiseCounterfactual failure modes
Evidence planning
Compare models
@@ -52,20 +65,30 @@ export default async function ModelComparePage({
);
}
- const models = buildModelLeaderboard(index).map((row) => row.model);
+ const filterParams = pickIndexFilterParams(params);
+ const filterExtraParams = indexFilterExtraParams(filterParams);
+ const index = applyIndexFilters(rawIndex, filterParams);
+ const models = buildModelLeaderboard(index, {
+ linkFilters: filterParams,
+ }).map((row) => row.model);
const leftModel = (params.left ?? "").trim();
const rightModel = (params.right ?? "").trim();
const compare =
leftModel && rightModel
- ? buildModelComparePayload(index, leftModel, rightModel)
+ ? buildModelComparePayload(index, leftModel, rightModel, {
+ linkFilters: filterParams,
+ })
: null;
const suggestions = buildLeaderboardCompareSuggestions(
- buildModelLeaderboard(index).map((row) => ({
- key: row.model,
- runCount: row.runCount,
- })),
+ buildModelLeaderboard(index, { linkFilters: filterParams }).map(
+ (row) => ({
+ key: row.model,
+ runCount: row.runCount,
+ }),
+ ),
{ left: leftModel, right: rightModel },
"/leaderboard/compare",
+ filterExtraParams,
);
return (
@@ -89,7 +112,15 @@ export default async function ModelComparePage({
@@ -78,7 +81,10 @@ export default async function PresetLeaderboardPage({ Experiment catalog - + Compare presets
Model × preset coverage for this question — cell counts show runs and benchmarks. Click a cell to open the - latest trace. + latest trace, or compare when two or more runs exist in + a cell.
|
{href ? (
-
- {label}
-
+
+
+ {label}
+
+ {compareHref ? (
+
+ Compare
+
+ ) : null}
+
) : (
{label}
diff --git a/apps/web/components/ActiveIndexFiltersNotice.tsx b/apps/web/components/ActiveIndexFiltersNotice.tsx
new file mode 100644
index 0000000..0d12f3b
--- /dev/null
+++ b/apps/web/components/ActiveIndexFiltersNotice.tsx
@@ -0,0 +1,32 @@
+import Link from "next/link";
+import type { ArtifactFilterParams } from "../lib/data";
+import { hasActiveIndexFilters } from "../lib/indexFilters";
+
+type ActiveIndexFiltersNoticeProps = {
+ filters: ArtifactFilterParams;
+ filteredRunCount: number;
+ totalRunCount: number;
+ clearHref: string;
+};
+
+export function ActiveIndexFiltersNotice({
+ filters,
+ filteredRunCount,
+ totalRunCount,
+ clearHref,
+}: ActiveIndexFiltersNoticeProps) {
+ if (!hasActiveIndexFilters(filters)) {
+ return null;
+ }
+
+ return (
+
+
+ );
+}
diff --git a/apps/web/components/CompareFormFilterFields.tsx b/apps/web/components/CompareFormFilterFields.tsx
new file mode 100644
index 0000000..5ccb00d
--- /dev/null
+++ b/apps/web/components/CompareFormFilterFields.tsx
@@ -0,0 +1,24 @@
+import type { ArtifactFilterParams } from "../lib/data";
+
+type CompareFormFilterFieldsProps = {
+ filters: ArtifactFilterParams;
+};
+
+export function CompareFormFilterFields({
+ filters,
+}: CompareFormFilterFieldsProps) {
+ const entries = Object.entries(filters).filter(
+ (entry): entry is [string, string] =>
+ typeof entry[1] === "string" && entry[1].length > 0,
+ );
+
+ if (entries.length === 0) return null;
+
+ return (
+ <>
+ {entries.map(([name, value]) => (
+
+ ))}
+ >
+ );
+}
diff --git a/apps/web/lib/compareFilterParams.test.ts b/apps/web/lib/compareFilterParams.test.ts
new file mode 100644
index 0000000..7108657
--- /dev/null
+++ b/apps/web/lib/compareFilterParams.test.ts
@@ -0,0 +1,52 @@
+import { describe, expect, it } from "vitest";
+import {
+ buildLeaderboardSideCompareHref,
+ indexFilterExtraParams,
+ pickIndexFilterParams,
+} from "./compareFilterParams";
+
+describe("compareFilterParams", () => {
+ it("picks index filter fields from search params", () => {
+ expect(
+ pickIndexFilterParams({
+ left: "gpt-4",
+ q: "threat",
+ model: "gpt",
+ preset: "standard",
+ fast: "true",
+ from: "2026-01-01",
+ }),
+ ).toEqual({
+ q: "threat",
+ model: "gpt",
+ preset: "standard",
+ fast: "true",
+ from: "2026-01-01",
+ to: undefined,
+ });
+ });
+
+ it("builds compare href with filters and side", () => {
+ expect(
+ buildLeaderboardSideCompareHref(
+ "/leaderboard/compare",
+ "left",
+ "gpt-a",
+ { q: "risk", fast: "false" },
+ ),
+ ).toBe("/leaderboard/compare?q=risk&fast=false&left=gpt-a");
+ });
+
+ it("omits empty filter values from extra params", () => {
+ expect(
+ indexFilterExtraParams({
+ q: "ai",
+ model: "",
+ preset: "standard",
+ }),
+ ).toEqual({
+ q: "ai",
+ preset: "standard",
+ });
+ });
+});
diff --git a/apps/web/lib/compareFilterParams.ts b/apps/web/lib/compareFilterParams.ts
new file mode 100644
index 0000000..ed07fc2
--- /dev/null
+++ b/apps/web/lib/compareFilterParams.ts
@@ -0,0 +1,49 @@
+import type { ArtifactFilterParams } from "./data";
+import { hasActiveIndexFilters } from "./indexFilters";
+import { buildQueryString } from "./listPagination";
+
+export function pickIndexFilterParams(
+ params: Record+ Comparing within a filtered index — {filteredRunCount} of{" "} + {totalRunCount} indexed run{totalRunCount === 1 ? "" : "s"}. + Metrics reflect only runs matching the active filters.{" "} + Clear filters + + |