Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions evalboard/app/_components/__tests__/harness-badge.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, expect, test } from "vitest";
import { render, screen } from "@testing-library/react";
import { KNOWN_HARNESSES } from "@/lib/harness";
import { HarnessBadge, harnessShortLabel } from "../harness-badge";

describe("harnessShortLabel", () => {
test("every known harness has a human label", () => {
// A missing entry falls through to the raw id, which would put
// "delegate-sdk" in a legend next to "Codex" and "Antigravity".
for (const h of KNOWN_HARNESSES) {
expect(harnessShortLabel(h)).not.toBe(h);
}
});

test("the UiPath harness reads as Delegate", () => {
// The run data's id stays `delegate-sdk` (it's the registered
// `agent.type`); only the label people read is the short one.
expect(harnessShortLabel("delegate-sdk")).toBe("Delegate");
});

test("an unknown harness falls back to its id rather than a wrong name", () => {
expect(harnessShortLabel("some-new-agent")).toBe("some-new-agent");
});
});

describe("HarnessBadge", () => {
test("names the vendor in the alt text, not just the product", () => {
render(<HarnessBadge harness="delegate-sdk" />);
expect(screen.getByAltText("Delegate · UiPath")).toBeInTheDocument();
});

test("renders the id as text when there is no logo for it", () => {
// Better a raw id than another vendor's mark on someone else's run.
render(<HarnessBadge harness="some-new-agent" />);
expect(screen.getByText("some-new-agent")).toBeInTheDocument();
});

test("takes a size so the chart legend can sit inside 11px text", () => {
render(<HarnessBadge harness="codex" size={14} />);
expect(screen.getByAltText("Codex · OpenAI")).toHaveAttribute(
"width",
"14",
);
});
});
24 changes: 21 additions & 3 deletions evalboard/app/_components/harness-badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ const HARNESS_LOGO: Record<string, { src: string; label: string; short: string }
label: "Antigravity · Google Gemini",
short: "Antigravity",
},
// The Delegate harness is UiPath's own (the coder_eval_uipath plugin), so
// its vendor mark is the UiPath logo already served for the site header
// rather than a per-harness file under /harness. The key stays `delegate-sdk`
// because that is the `agent.type` the plugin registers and therefore what
// run.json carries; only the label people read is shortened.
"delegate-sdk": {
src: "/uipath.png",
label: "Delegate · UiPath",
short: "Delegate",
},
};

// Short human label for a harness id ("Claude Code"), for selectors and prose.
Expand All @@ -30,7 +40,15 @@ export function harnessShortLabel(harness: string): string {
return HARNESS_LOGO[harness]?.short ?? harness;
}

export function HarnessBadge({ harness }: { harness?: string | null }) {
export function HarnessBadge({
harness,
// Square edge in px. Defaults to the runs-table size; the chart legend asks
// for a smaller mark so the logo sits inside a line of 11px text.
size = 20,
}: {
harness?: string | null;
size?: number;
}) {
const key = harness ?? "claude-code";
const logo = HARNESS_LOGO[key];
// Unknown harness: show the raw id rather than a misleading logo.
Expand All @@ -42,8 +60,8 @@ export function HarnessBadge({ harness }: { harness?: string | null }) {
src={logo.src}
alt={logo.label}
title={logo.label}
width={20}
height={20}
width={size}
height={size}
className="rounded-sm"
/>
);
Expand Down
82 changes: 67 additions & 15 deletions evalboard/app/_components/harness-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,96 @@
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { HarnessBadge, harnessShortLabel } from "./harness-badge";

// Segmented control for the trends page's harness scope. Sets `?h=<harness>`
// while preserving the active q/tag params, mirroring WindowSelector. Each
// segment shows the vendor logo + short label so it reads like the per-run
// harness badge on the runs tables.
// Segmented control for a page's harness scope. Sets `?h=<harness>` while
// preserving the active q/tag params, mirroring WindowSelector. Each segment
// shows the vendor logo + short label so it reads like the per-run harness badge
// on the runs tables.
//
// `current` is null only on pages that support the all-harness view (the
// overview, whose charts draw a line per harness and whose tiles and run list
// then cover every harness). Pages that need exactly one harness — trends
// collapses per-task history across runs, which only means something inside one
// harness — pass a concrete harness and leave `includeAll` off.
export function HarnessSelector({
current,
harnesses,
includeAll = false,
}: {
current: string;
current: string | null;
harnesses: readonly string[];
includeAll?: boolean;
}) {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const set = (h: string) => {
const set = (h: string | null) => {
const p = new URLSearchParams(searchParams.toString());
p.set("h", h);
router.replace(`${pathname}?${p.toString()}`, { scroll: false });
if (h == null) {
// The unscoped view is the default, so it's the absence of the param
// rather than `h=all` — keeps the canonical URL clean.
p.delete("h");
} else {
p.set("h", h);
}
// Changing scope changes which runs are in play, so the paged-out row
// counts no longer describe the new set; drop them back to page one.
p.delete("limit");
p.delete("alimit");
const qs = p.toString();
router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
};
// Always show the active harness, even if it has aged out of the recent
// window (so a deep-linked `?h=` still reads as selected rather than absent).
const opts = harnesses.includes(current)
? harnesses
: [current, ...harnesses];
const opts =
current == null || harnesses.includes(current)
? harnesses
: [current, ...harnesses];
// shrink-0 + whitespace-nowrap: a segment must keep its label on one line
// and never compress, so on a narrow screen the control scrolls (see the
// wrapper) instead of wrapping "Claude Code" onto two lines and stretching
// the whole page past the viewport.
const segment = (active: boolean) =>
`flex shrink-0 items-center gap-1.5 whitespace-nowrap px-2.5 py-1 sm:px-3 ${
active
? "bg-studio-blue text-white"
: "bg-white text-gray-700 hover:bg-gray-50"
}`;
// Below `sm` the vendor logo carries the identity on its own (each button
// keeps the full name as its accessible label and tooltip) — five spelled-out
// segments don't fit on a phone, and the chart legend below names every line.
const label = (text: string) => (
<span className="hidden sm:inline">{text}</span>
);
return (
<div className="inline-flex border border-gray-200 rounded-md overflow-hidden text-sm">
{opts.map((h) => {
<div className="flex max-w-full overflow-x-auto rounded-md border border-gray-200 text-sm">
{includeAll && (
<button
type="button"
onClick={() => set(null)}
aria-pressed={current == null}
className={`${segment(current == null)} rounded-l-md`}
title="Every harness, one line each"
>
All
</button>
)}
{opts.map((h, i) => {
const active = h === current;
const name = harnessShortLabel(h);
return (
<button
key={h}
type="button"
onClick={() => set(h)}
aria-pressed={active}
className={`flex items-center gap-1.5 px-3 py-1 ${active ? "bg-studio-blue text-white" : "bg-white text-gray-700 hover:bg-gray-50"}`}
aria-label={name}
title={name}
className={`${segment(active)} ${
!includeAll && i === 0 ? "rounded-l-md" : ""
} ${i === opts.length - 1 ? "rounded-r-md" : ""}`}
>
<HarnessBadge harness={h} />
{harnessShortLabel(h)}
{label(name)}
</button>
);
})}
Expand Down
33 changes: 0 additions & 33 deletions evalboard/app/_components/window-selector.tsx

This file was deleted.

130 changes: 130 additions & 0 deletions evalboard/app/_overview/__tests__/harness-legend.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { describe, expect, test } from "vitest";
import { render, screen } from "@testing-library/react";
import { harnessColor } from "@/lib/harness";
import { HarnessLegend, HarnessTooltip } from "../harness-legend";
import { seriesKey } from "../harness-series";

function series(...harnesses: string[]) {
return harnesses.map((harness, i) => ({
harness,
dataKey: seriesKey(harness, i),
color: harnessColor(harness),
}));
}

describe("HarnessLegend", () => {
test("names every series, so identity is never color-alone", () => {
render(<HarnessLegend series={series("claude-code", "codex")} />);
expect(screen.getByText("Claude Code")).toBeInTheDocument();
expect(screen.getByText("Codex")).toBeInTheDocument();
});

test("renders nothing for a single series", () => {
// A one-line chart is already named by its own heading; a legend box
// there is noise.
const { container } = render(
<HarnessLegend series={series("claude-code")} />,
);
expect(container).toBeEmptyDOMElement();
});

test("renders nothing when there is no data at all", () => {
const { container } = render(<HarnessLegend series={[]} />);
expect(container).toBeEmptyDOMElement();
});
});

describe("HarnessTooltip", () => {
const s = series("claude-code", "codex");

test("shows only the harnesses that actually ran at the hovered x", () => {
// Recharts hands over every series, including the ones with no value
// here. Listing those would invent runs that never happened.
render(
<HarnessTooltip
active
label={1_700_000_000_000}
series={s}
suffix="success"
emptyText="no tasks"
payload={[
{ dataKey: s[0].dataKey, value: 92.5 },
{ dataKey: s[1].dataKey, value: undefined },
]}
/>,
);
expect(screen.getByText("Claude Code")).toBeInTheDocument();
expect(screen.getByText("92.5% success")).toBeInTheDocument();
expect(screen.queryByText("Codex")).not.toBeInTheDocument();
});

test("keeps a real zero instead of filtering it out as falsy", () => {
render(
<HarnessTooltip
active
label={1_700_000_000_000}
series={s}
suffix="success"
emptyText="no tasks"
payload={[{ dataKey: s[0].dataKey, value: 0 }]}
/>,
);
expect(screen.getByText("0.0% success")).toBeInTheDocument();
});

test("falls back to the empty text when no series has a value", () => {
render(
<HarnessTooltip
active
label={1_700_000_000_000}
series={s}
suffix="within turn budget"
emptyText="no tasks with a turn budget"
payload={[{ dataKey: s[0].dataKey, value: undefined }]}
/>,
);
expect(
screen.getByText("no tasks with a turn budget"),
).toBeInTheDocument();
});

test("ignores payload entries for unknown series keys", () => {
render(
<HarnessTooltip
active
label={1_700_000_000_000}
series={s}
suffix="success"
emptyText="no tasks"
payload={[{ dataKey: "h_stale_key", value: 50 }]}
/>,
);
expect(screen.getByText("no tasks")).toBeInTheDocument();
});

test("renders nothing while inactive", () => {
const { container } = render(
<HarnessTooltip
series={s}
suffix="success"
emptyText="no tasks"
payload={[{ dataKey: s[0].dataKey, value: 1 }]}
/>,
);
expect(container).toBeEmptyDOMElement();
});

test("renders the UTC timestamp header from the x label", () => {
render(
<HarnessTooltip
active
label={Date.UTC(2026, 6, 30, 4, 27)}
series={s}
suffix="success"
emptyText="no tasks"
payload={[{ dataKey: s[0].dataKey, value: 80 }]}
/>,
);
expect(screen.getByText("2026-07-30 04:27 UTC")).toBeInTheDocument();
});
});
Loading
Loading