feat(evalboard): compare harnesses on the overview, and identify each run - #69
feat(evalboard): compare harnesses on the overview, and identify each run#69bai-uipath wants to merge 5 commits into
Conversation
… whole page to one The overview opened scoped to claude-code and re-scoped only its analytics block: the summary tiles and the run list kept covering every harness, so picking codex changed the chart while the tiles above it still reported all-harness cost and pass rate. Nothing on the page said which set a number described. Now one scope drives the page, and its default is every harness: - Both charts draw one line per harness in the window instead of a single line zigzagging across incomparable harnesses. RunPoint carries its run's harness and getOverview reports which harnesses are present, so the series list is data-driven and a harness with no runs contributes no empty line. - Line color is bound to the harness, not to its index, so narrowing the view never repaints the survivors. The four reserved colors are slots from the validated categorical palette and clear every hard gate as an ordered set on a light surface (worst adjacent CVD dE 8.4, normal-vision dE 27.1, all above 3:1 contrast); unknown harnesses share one neutral rather than getting a generated hue. Each chart carries a legend pairing the swatch with the vendor badge and label, so identity is never color-alone. - getRunListing takes the same scope, so the tiles (which read its pre-limit rollup) and the table narrow with the charts. - delegate-sdk joins KNOWN_HARNESSES and renders the UiPath mark, the vendor logo it had been missing. Both tables now page one screen at a time and no longer offer "show all": every extra row is another run.json read behind the window load, so an unbounded jump was the one click that could stall the page. A hand-typed ?limit= clamps at 500 rather than being honored. Trends and path-to-ga still require a single harness (collapsing per-task history across runs only means something inside one), so the All segment is opt-in via includeAll.
…n used The run page identified a run only by its timestamped id, so telling a codex run from a claude-code one meant going back to the runs table or opening a task — even though harness and model are exactly what decide whether this page's numbers are comparable to another run's. RunSummary now carries both (harness from the RunConfig stamp, model as the dominant per-task model_used) and the header renders them as a badge strip. An A/B run that fans variants across models reports the spread instead of silently claiming the winner. The coder_eval chip also showed a git SHA linking to a tree, but the framework is pinned and consumed by released version, and env_info records that version alongside the SHA. The chip now reads the version and links to its release. The SHA survives as a fallback for the two cases a version can't cover: an editable checkout between releases, and legacy runs written before the version was captured. This also gives in-container runs a chip at all — their git SHA resolves to "unknown" and was dropped, while the version resolves fine.
Shortens the label from "Delegate SDK" to "Delegate" everywhere it is read: the runs-table badge, the chart legend, the harness selector, and the run page's identity strip. The registered `agent.type` stays `delegate-sdk` (that is what run.json carries and what `?h=` accepts), so only the display name changes. Adds coverage for the badge, which had none: every known harness has a human label, an unknown one falls back to its raw id rather than borrowing another vendor's mark, and the size prop the chart legend depends on is honored. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ages through all history Three changes that all pull in the same direction: make a number mean the same thing wherever it appears, and stop making people choose a time window to find a run. Pass-rate colors now come from one module (`lib/pass-rate.ts`) carrying the same cutoffs as the nightly Slack rollup's traffic-light dots (>=95% green, <85% red, amber between). Four call sites had drifted apart: the front-page table and tiles were green at >=80, Trends was green only at a perfect 100% (a task that passed 9 of its last 10 runs looked as broken as one that never passed), the watchlist had a third set, and the run page's meter was unconditionally green, so a 77% run still read as healthy at a glance. A run that pings the channel red and then reads green on the page it links to is the failure this consolidates away. The time-window control (1d/7d/14d/30d) is gone. Charts and summary tiles cover a fixed 30 days; the run table is no longer bounded by that window at all and pages back through the whole store 20 rows at a time, which is what a shorter window was being used for. Rows load lazily, since a run.json is multi-MB and counting every match in history on each render would not be affordable: `hasMore` comes from over-fetching a single row rather than from a total, and the one free count (every pipeline run in the store) is reported alongside it. `getWindowRollup` splits the tiles' 30-day rollup away from the table's paging, and `projectRunRow` becomes the single definition of "does this run count, and with which tasks" that both go through, so the tiles can no longer describe a different set of runs than the table under them. Path to GA defaults to every harness, like the overview: a line per harness in one chart instead of four page loads. Its task table pools appearances across harnesses when unscoped, which mixes regimes that are not strictly comparable, so it says so and points at the selector. Also fixes the overview at phone width, where the two segmented controls could not fit on one row and pushed the page past the viewport: the control row wraps, harness segments keep their labels on one line and drop to logo-only below `sm` (each keeps its name as tooltip and accessible label, and the chart legend names every line), and the run timestamp stops breaking across three lines. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cope Self-review turned up three places where the page could state something untrue, plus dead code with comments justifying it. Scoped counts were wrong. `isFiltered` only looked at tag/q, so selecting a harness left the run table reading "20 of 122" where 122 counted every harness, and left the tiles labelled as if nothing were narrowed. Split into `isFiltered` (tasks within a run) and `isNarrowed` (the run set, harness included), and every sub-label now names the window even when scoped so a narrowed view can't read as all-time. The charts and the tiles could describe different runs. getOverview and getWindowRollup each had their own copy of "does this run count, over which tasks", and they disagreed on a `q` that matched only a run id. Extracted `scopeRunTasks` as the single seam and folded the rollup into getOverview's existing pass, so the two are now the same loop rather than two loops that agree by convention. Drops getWindowRollup, WindowRollup, and a Promise.all slot. Pagination could strand history. `hasMore` came from over-fetching one row, but collectPipelineRuns gives up at a multiple of the limit, so once the store passes ~170 runs a filtered page one would report "no more" and hide every older match behind a link that vanished. The listing now probes every candidate. Also: the coder_eval chip no longer links pre-0.8.2 runs at a release tag that was never cut; chart dataKeys are index-prefixed so two harness ids that sanitize alike can't collide onto one line; tag chips stop emitting a `?window=` nobody reads; and the turn-budget caption says that runs with no budgeted task are omitted rather than plotted at 0. Removed as filler: a dead ALL_HARNESSES re-export, the redundant `hasData` parameter on all four pass-rate helpers (null already says it), a one-line forwarder in trends, two one-line limit-parser wrappers, and a second copy of the harness ordering logic. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @bai-uipath's task in 1m 7s —— View job Code Review in Progress
|
uipreliga
left a comment
There was a problem hiding this comment.
Review: coder_eval — pr:69 (29 files) axis:1,2,3,4,5,6,7,8
Scope: pr:69 (29 files) axis:1,2,3,4,5,6,7,8 · branch bai/evalboard-harness-scope (PR #69 head; local review branch feat/evalboard-multi-harness-charts) · 404fccd · 2026-07-30T23:22Z · workflow variant
Change class: complex — reshapes the overview's run-scoping/pagination semantics (projectRunRow/scopeRunTasks, probeAll, hasMore replacing matchedCount/totals), adds multi-series chart derivation and a new pass-rate threshold module; correctness requires reasoning about which runs/tasks land in each metric
A clean, well-factored multi-harness PR (9.2/10) with strong security and style hygiene, whose real risks are all in what the numbers mean and whether anything guards them: the Path-to-GA headline quietly became a cadence-weighted cross-harness average, scoped cost silently understates partial slices, and the new pagination and harness-scope mechanics ship with no tests (Test Health 7.8) — none of it blocking, but the semantic and coverage items above should land before this is trusted as a GA-readiness signal.
Summary
| Axis | Score | 🔴 | 🟠 | 🟡 | 🔵 | Top Issue |
|---|---|---|---|---|---|---|
| 1. Code Quality & Style | 9.8 / 10 | 0 | 0 | 0 | 2 | The harness-id charset regex is duplicated between parseHarnessParam and parseHarnessScope, so ?h= validation has two sources of truth |
| 2. Type Safety | 9.3 / 10 | 0 | 0 | 1 | 2 | Hand-rolled TooltipEntry (harness-legend.tsx:37-41) restates recharts' Payload at an injection site recharts types as a bare ReactElement, so nothing type-checks the tooltip's payload contract |
| 3. Test Health | 7.8 / 10 | 0 | 1 | 2 | 2 | getRunListing has zero tests: the new limit + 1 over-fetch ↔ rows.length > limit pairing and totalCandidates: ids.length are unverified (the scan primitive it composes is well covered) |
| 4. Security | 9.9 / 10 | 0 | 0 | 0 | 1 | Run-artifact-controlled version string interpolated unescaped into a rendered GitHub href |
| 5. Architecture & Design | 9.3 / 10 | 0 | 0 | 1 | 2 | The two overview charts still duplicate ~95 of 110 lines of chart shell (incl. shortLabel and a now-contradictory connectNulls comment) after the harness pivot/legend extraction |
| 6. Error Handling & Resilience | 8.8 / 10 | 0 | 0 | 2 | 2 | Turn-budget chart no longer breaks at runs with no turn budget: the pivot drops the null point (harness-series.ts:55), and lib/overview.ts:36-37 still documents the gap main used to draw |
| 7. API Surface & Maintainability | 9.8 / 10 | 0 | 0 | 0 | 2 | WindowSelector/?window= removal left-overs: no redirect or alias for bookmarked ?window= links, a dead WINDOWS export, and comments pointing at the deleted component |
| 8. Evaluation Harness Quality | 8.8 / 10 | 0 | 1 | 0 | 2 | Path-to-GA headline pass rate becomes a run-weighted mean pooled across harnesses, diverging from the task-weighted rate the front page shows from the same getOverview payload |
Overall Score: 9.2 / 10 · Weakest Axis: Test Health at 7.8 / 10
Totals: 🔴 0 · 🟠 2 · 🟡 6 · 🔵 15 across 8 axes.
Blockers
- [Axis 3] getRunListing has zero tests: the new
limit + 1over-fetch ↔rows.length > limitpairing andtotalCandidates: ids.lengthare unverified (the scan primitive it composes is well covered) (evalboard/lib/overview.ts:838) — This PR replacedtotalInWindow/matchedCount/totalswithtotalCandidates: ids.length,(overview.ts:837) andhasMore: rows.length > limit,(838), and switched the scan tocollectPipelineRuns(ids, limit + 1, cachedLoadPerRun, isMatch, true)(824-830).getRunListingappears in no test file.hasMoreis the only thing that renders the "Show more" link (page.tsx:144const hasMore = listing.hasMore && shownCount < MAX_LIMIT;, footer at page.tsx:347), so wrong-in-the-false-direction makes every older matching run unreachable through the UI and wrong-in-the-true-direction offers a page with no new rows.collectPipelineRunsis well tested in isolation (overview.test.ts:230-366) but nothing tests thelimit + 1over-fetch →rows.length > limitderivation, which is where the off-by-one lives. FAILURE SCENARIO: a later cleanup changeshasMore: rows.length > limitto>=, or callscollectPipelineRunswithlimitinstead oflimit + 1; with exactly 20 matching runs atlimit = 20, page.tsx:347 renders "Show 20 more" and the next page returns the same 20 rows — or, with the inverse slip,hasMoreis false at 21 matches and everything older than the first screenful is permanently unreachable. Both ship green. Note separately thattotalCandidatesis an id count (ids.lengthafter theparseRunIdDate(id) != nullfilter at line 811), not a match count, yet page.tsx:147 renders it as${shownCount} of ${listing.totalCandidates}; only theisNarrowedbranch avoids that reading and nothing asserts the branch choice. FIX: add adescribe("getRunListing")block (mock../runs'slistRunIds/readRunOverview/readRunMeta, andnext/cache'sunstable_cacheto identity) covering exactly three boundaries —limit = 3with 3 matching runs →hasMore === false,rows.length === 3; 4 matching runs →hasMore === true,rows.length === 3; 0 matching runs →rows === [],hasMore === false,totalCandidatesstill the date-shaped id count — plus one test that the promised second page (limit = 6) actually contains the extra run. - [Axis 8] Path-to-GA headline pass rate becomes a run-weighted mean pooled across harnesses, diverging from the task-weighted rate the front page shows from the same getOverview payload (
evalboard/app/path-to-ga/page.tsx:45) —app/path-to-ga/page.tsx:36switches toconst harness = parseHarnessScope(params.h);, which returns null (= every harness) when?h=is absent (lib/harness.ts:49-57); it replacedparseHarnessParam, which defaulted toDEFAULT_HARNESS(lib/harness.ts:34). The diff also deletes main's own justification for the old default: "// Scope to one harness — readiness of a task is per-harness, and a blended chart/pass-rate mixes incomparable regimes." The GA headline atpath-to-ga/page.tsx:45-49—overview.runs.reduce((sum, r) => sum + (r.successRate ?? 0), 0) / runsInWindow— is now an unweighted mean over per-run rates across every harness, still labelled only "avg pass rate over the last 30d" (line 84). Two concrete consequences for identical stored data: (a) the number changes meaning without the reader doing anything, and the same 3xl figure now answers a different question than it did last week; (b) because the mean is per-RUN, the daily harness (codex, ~30 runs in 30d) dominates the weekly ones (claude-code/antigravity, ~4 runs each), and codex is precisely the harness whose rate is inflated by mature-skip carry-forward (see the related finding), so the GA readiness headline converges on the most optimistic harness. The "pooled across harnesses" caveat that was added (lines 127-132) is attached only to the Tasks table heading, not to this headline or to the "runs with a path-to-ga task" / "distinct tasks" tiles beside it. Fix: either keep a single-harness default on this page (readiness is per-harness, as the deleted comment said), or compute the headline as a task-weighted rate and label it explicitly, e.g.avg pass rate · pooled across N harnesses · last 30d, with a per-harness breakdown.
Non-blocking, but please consider before merge
-
[Axis 2] Hand-rolled
TooltipEntry(harness-legend.tsx:37-41) restates recharts'Payloadat an injection site recharts types as a bareReactElement, so nothing type-checks the tooltip's payload contract (evalboard/app/_overview/harness-legend.tsx:37) —harness-legend.tsx:37-41declares its ownexport interface TooltipEntry { dataKey?: string | number; value?: number | string | null; color?: string; }for props that recharts injects, and recharts types the injection site as a bareReactElement(ContentType<TValue,TName> = ReactElement | ((props: TooltipProps<TValue,TName>) => ReactNode),node_modules/recharts/types/component/Tooltip.d.ts:8). Sodaily-chart.tsx:70-77andturn-budget-chart.tsx:71-78passingcontent={<HarnessTooltip series={series} … />}get ZERO checking thatHarnessTooltip's payload shape matches what recharts actually hands it. The unit tests build payloads from the same hand-rolled type (app/_overview/__tests__/harness-legend.test.tsx:50-53:{ dataKey: s[0].dataKey, value: 92.5 }), so they would keep passing after a recharts upgrade renamed or re-typed the field — the failure mode is a tooltip that silently rendersemptyText("no tasks") on every hover, becausepayload.filter((e) => typeof e.value === "number" && byKey.has(String(e.dataKey)))(lines 73-75) then matches nothing. This is Review Criteria #17 ("mocks match the real SDK shape") in TypeScript form. Derive the type from the installed package instead of restating it:import type { TooltipProps } from "recharts";thentype TooltipEntry = NonNullable<TooltipProps<number, string>["payload"]>[number];— recharts re-exportsTooltipPropsfrom its root (node_modules/recharts/types/index.d.ts:10), so this one-line change turns any upstream shape change into a compile error. While there, remove the two assertions the hand-rolled type forces —const s = byKey.get(String(e.dataKey))!;(line 88) and{(e.value as number).toFixed(1)}% {suffix}(line 103) — by making line 73'sfiltera type predicate ((e): e is TooltipEntry & { value: number } => …) and resolvingsinside it, so neither!noras numberis needed. -
[Axis 3] Unknown harness renders its id twice in all three composed consumers (legend, run identity, selector); no composed test covers the no-logo path (
evalboard/app/_overview/harness-legend.tsx:27) —HarnessBadgefalls back to text when a harness has no logo — harness-badge.tsx:55-57if (!logo) { return <span className="text-xs text-gray-700">{key}</span>; }— and every consumer printsharnessShortLabel()immediately after it, which for an unknown id returns the id (harness-badge.tsx:40return HARNESS_LOGO[harness]?.short ?? harness;). Affected sites: harness-legend.tsx:26-27 (<HarnessBadge harness={s.harness} size={14} />then{harnessShortLabel(s.harness)}), run-identity.tsx:31-32, harness-selector.tsx:94-95. VERIFIED EMPIRICALLY at PR HEAD by rendering both components in a throwaway vitest file:HarnessLegendwith series["gemini-cli", "codex"]produced textContent"gemini-cligemini-cliCodex", andRunIdentity harness="gemini-cli"produced"gemini-cligemini-cli". FAILURE SCENARIO: this is exactly the self-extending case the module advertises (harness.ts:7-9 "the switcher is data-driven … so a new harness surfaces automatically without editing this list") — i.e. the first render of any newly-added nightly harness, before an entry lands inHARNESS_LOGO. The suite misses it because coverage is split at the seam: harness-badge.test.tsx:32-36 asserts the raw-id fallback in isolation ("renders the id as text when there is no logo for it"), locking the fallback in, while harness-legend.test.tsx only ever passesseries("claude-code", "codex")(line 38) and run-identity.test.tsx onlycodex/delegate-sdk/claude-code. FIX: add one case each to harness-legend.test.tsx and run-identity.test.tsx rendering an unknown harness and asserting the id appears exactly once (expect(container.textContent).toBe("gemini-cli")); the production fix is to drop the text fallback fromHarnessBadgeso "no logo → no image" lives in one place and naming the harness is unambiguously the caller's job. -
[Axis 3] HarnessSelector's all-harness scope, "All" chip and limit/alimit pagination reset ship with no test file; run-view's
totalN > 0tone guard is unasserted (evalboard/app/_components/harness-selector.tsx:39) — Theme-grouped: two in-scope components gained new behaviour with no behavioural assertion. (a)harness-selector.tsx—currentwidened fromstringtostring | null; newincludeAll = falseprop (line 19); the "All" button whose handler isonClick={() => set(null)}(line 71); the newp.delete("h")branch (line 33) withrouter.replace(qs ?${pathname}?${qs}: pathname, ...)(line 42); and the newp.delete("limit"); p.delete("alimit");reset (lines 39-40, whose comment reads "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"). Noapp/_components/__tests__/harness-selector.test.tsxexists at PR HEAD; the component is only rendered incidentally inside trends-view.test.tsx and watchlist-view.test.tsx, whose own comments say they stubnext/navigationmerely so the view renders. This is the mechanism behind the PR's headline feature ("the overview home page defaults to ALL harnesses") and it is entirely unasserted. FAILURE SCENARIO: a later edit dropsp.delete("limit")at line 39 (or reorders it afterrouter.replace); switching from All to codex while paged out to?limit=200re-renders the table at limit 200 against a set that no longer has 200 members, and page.tsx:147's${shownCount} of ${listing.totalCandidates}label describes a scope the user never asked for — nothing fails. FIX: copy the mock pattern already in the repo at app/_components/tests/search-box.test.tsx:10 (vi.mock("next/navigation", ...)with a capturedreplacespy) and assert: "All" callsrouter.replacewith nohparam; a harness segment setsh=<id>; both droplimit/alimitwhile preservingtag/q;includeAllomitted renders no "All" segment; acurrentoutsideharnessesis still rendered (the[current, ...harnesses]branch at lines 46-49). (b)app/runs/[id]/run-view.tsx:410—const tone = totalN > 0 ? pct : null;feeding the newpassClass(tone)(line 415) andpassBarClass(tone)(line 439, replacing a hardcodedbg-green-500). ThetotalN > 0guard is the branch separating an empty run (neutral) from a measured 0% (red) — the "gate that turns a gap into a signal" shape — and it is untested even though run-view.render.test.tsx already mounts this exact tile (tests at lines 43 and 69). Add one class assertion there per band plus the empty-run case. -
[Axis 5] The two overview charts still duplicate ~95 of 110 lines of chart shell (incl. shortLabel and a now-contradictory connectNulls comment) after the harness pivot/legend extraction (
evalboard/app/_overview/turn-budget-chart.tsx:93) —diffof the two PR-HEAD files reports only comment blocks plus three literals:"successRate"→"turnBudgetRate"(daily-chart.tsx:38 / turn-budget-chart.tsx:39),suffix="success"→suffix="within turn budget", andemptyText. Lines 17-22 (shortLabel) and 40-107 (the wholeResponsiveContainer/CartesianGrid/XAxis/YAxis/Tooltip/series.map(<Line/>)shell) are character-for-character identical to daily-chart.tsx:17-22 and 39-104. The PR extracted the pivot (harness-series.ts) and the tooltip/legend (harness-legend.tsx) and its commit e2628e0 is titled "one multi-series chart behind both overview charts", but the chart shell was never extracted — so the claim does not hold. The cost is already visible: turn-budget-chart.tsx:93 says// Unlike the success chart this must bridge, forwhile daily-chart.tsx:96 also hasconnectNulls={true}, i.e. the comment describes a difference that does not exist. Fix: add oneHarnessLineChart({data, harnesses, metric, suffix, emptyText, windowStart, windowEnd})inapp/_overview/and reduce both files to a ~10-line call, deleting the duplicatedshortLabeland the stale comment. -
[Axis 6] Turn-budget chart no longer breaks at runs with no turn budget: the pivot drops the null point (harness-series.ts:55), and lib/overview.ts:36-37 still documents the gap main used to draw (
evalboard/app/_overview/turn-budget-chart.tsx:100) — origin/main's TurnBudgetChart drewconnectNulls={false}with the comment "Don't bridge runs that have no budgeted tasks — a gap is honest about missing data; a line is not." At PR HEAD it isconnectNulls={true}(turn-budget-chart.tsx:100), and the pivot drops such runs from the data entirely —if (p[metric] == null) continue;(app/_overview/harness-series.ts:54), asserted by the new test "omits a null metric rather than plotting it as zero" (app/_overview/tests/harness-series.test.ts:107-117, expectsrowsto be[]). So a run with no budgeted task no longer produces a visible break; the line is drawn straight across it and reads as continuous measurement. The RunPoint doc comment is now false about its own consumer: "null when no task in scope carries a budget at all (the chart shows a gap rather than a failure-driven 0%)" (lib/overview.ts:36-37). FAILURE SCENARIO: a 30-day window holds three claude-code nightly runs and the middle one ran a suite with noexpected_turnsbudget, soturnBudgetRateForTasksreturns null; on main the line broke at that date, now the point is dropped and run 1 is joined to run 3, showing an unbroken "within expected turns" trend across a date where nothing was measured (hovering it shows no row at all). FIX: the bridging is genuinely required for interleaved multi-harness rows, so don't just revert the flag — emit an explicit row carryingnullfor every in-window run of the series' own harness (instead of dropping the point) so recharts breaks at real gaps while still bridging other harnesses' rows, or render bridged spans dashed. At minimum correct the stale comment at overview.ts:36-37. -
[Axis 6] Run table's empty state reads isFiltered while the count label reads isNarrowed, so a harness-only scope with no matches prints "no runs yet" above/below "0 matching" (
evalboard/app/page.tsx:446) — The page deliberately keeps two flags —const isFiltered = activeTag != null || q != null;(page.tsx:118) andconst isNarrowed = isFiltered || harness != null;(page.tsx:121, commented "The set of RUNS is narrowed, harness scope included: drives every 'n of N' count"). The count label uses the harness-aware flag (const tableCountLabel = isNarrowed ? ... : ..., page.tsx:145-147) but the empty state does not:{isFiltered ? "no runs match the current filter" : "no runs yet"}(page.tsx:446-448). This PR is what first makes the table harness-scoped (getRunListing(activeTag, q, limit, harness), page.tsx:130), so a harness scope with zero matching runs now prints "no runs yet" over a store full of runs, and the "clear filter" link — also gated onisFiltered(page.tsx:333) — is hidden. FAILURE SCENARIO: a bookmarked or hand-typed/?h=Claude-Code(wrong case, but it passesparseHarnessScope's/^[\w.-]{1,64}$/at lib/harness.ts:56) or a?h=for a retired harness — both explicitly supported paths, since HarnessSelector keeps an unknown/aged-outcurrentselected on purpose (app/_components/harness-selector.tsx:44-49) — renders empty charts, empty tiles, and "no runs yet", telling the reader the evalboard has no runs at all. FIX: useisNarrowedat line 446 (and for the clear link at 333), or word it "no runs match this harness".
Nits
- [Axis 1] The harness-id charset regex is duplicated between parseHarnessParam and parseHarnessScope, so
?h=validation has two sources of truth (evalboard/lib/harness.ts:56) — The two parsers differ only in their fallback, yet each carries its own copy of the trim/array handling and of the charset bound:
// line 36 (parseHarnessParam)
return /^[\w.-]{1,64}$/.test(trimmed) ? trimmed : DEFAULT_HARNESS;
// line 56 (parseHarnessScope)
return /^[\w.-]{1,64}$/.test(trimmed) ? trimmed : null;Tightening or widening the accepted charset in one and forgetting the other silently changes which runs a page counts (the test at lib/__tests__/harness.test.ts:98 asserts they "differ exactly on the empty case", which is true today but is not enforced on the regex). Extract one sanitizeHarnessId(raw): string | null holding the array/trim/regex logic, then parseHarnessParam = raw => sanitizeHarnessId(raw) ?? DEFAULT_HARNESS and parseHarnessScope = sanitizeHarnessId — which also removes the need for the ALL_HARNESSES special-case at line 55, since "all" can be handled once alongside the empty case.
2. [Axis 1] RunPoint.runId is dead — the new pivot discards it and no consumer reads it (evalboard/lib/overview.ts:22) — RunPoint still declares runId: string; (line 22, immediately above the harness field this PR added), and getOverview populates it at line 592 (runId: id,), but pivotByHarness keys rows solely on timestamp (harness-series.ts:53-66) and neither chart nor HarnessTooltip ever reads it — grep -rn "runId" app lib outside trends/ returns no consumer. The tooltip is therefore unable to name or link the run a point belongs to even though the data is present. Either use it (add the run id / a /runs/<id> link to HarnessTooltip, which needs the id carried through the pivot) or drop the field so the interface stops advertising data nothing reads.
3. [Axis 2] KnownHarness union is exported but unused; both harness lookup tables stay Record<string, …> (evalboard/lib/harness.ts:17) — harness.ts:11-17 builds the const tuple and export type KnownHarness = (typeof KNOWN_HARNESSES)[number];, and lines 9-10 assert what membership buys: "Membership here does buy two things: a stable position in the switcher, and a reserved series color (see HARNESS_COLORS)." Nothing types that contract: grep -rn KnownHarness app lib returns only the declaration (harness.ts:17) and a pass-through re-export (harness-badge.tsx:6) — no function or table consumes it. Both lookup tables are open-keyed: harness.ts:69 const HARNESS_COLORS: Record<string, string> = { and harness-badge.tsx:13 const HARNESS_LOGO: Record<string, { src: string; label: string; short: string }> = {. So adding a fifth entry to KNOWN_HARNESSES — or renaming one, a live risk given harness.ts:29 names "delegate" as a plausible id while both tables key on "delegate-sdk" — compiles clean while that harness silently falls to HARNESS_COLOR_FALLBACK (#6b7280) and to its raw id as a label; two such harnesses then draw as two indistinguishable grey lines, breaking the colour-is-identity invariant this PR is built on. Today the drift is caught only at test time (lib/__tests__/harness.test.ts:108-109 asserts new Set(KNOWN_HARNESSES.map(harnessColor)).size === KNOWN_HARNESSES.length; app/_components/__tests__/harness-badge.test.tsx:10-12 asserts each known id has a label). Push it left into the compiler by appending satisfies Record<KnownHarness, string> to the HARNESS_COLORS literal and satisfies Record<KnownHarness, { src: string; label: string; short: string }> to HARNESS_LOGO — satisfies preserves the wide string index signature that harnessColor(harness: string) and harnessShortLabel(harness: string) need, while making a missing known key a build error.
4. [Axis 2] RunListingRow.harness optional only for test factories, giving the field three meanings (evalboard/lib/overview.ts:115) — overview.ts:112-115 declares harness?: string | null; with the reason stated inline: "// Optional so existing test factories stay valid." Every production producer always sets it — rowFromScoped (line 776) writes harness: harness ?? null and is the only path into a pipeline row — so the optionality models nothing real while creating a three-state field: absent (only buildAdhocRows, whose line 885 comment reads "// Harness is a main-table-only (internal) column; ad-hoc rows omit it."), null (legacy run with no stamp), or an id. Because AdhocRunRow extends RunListingRow (line 842) inherits the optional field and HarnessBadge collapses both empty cases with const key = harness ?? "claude-code"; (harness-badge.tsx:52), the moment anyone adds a Harness column to the ad-hoc table every ad-hoc run is silently attributed to Claude Code with no compile error — the exact mis-attribution this PR's harness comparison exists to prevent. Make the production contract honest: declare harness: string | null (required) on RunListingRow, and Omit<> it out of the ad-hoc row (export interface AdhocRunRow extends Omit<RunListingRow, "harness">) so an ad-hoc row cannot even be asked for a harness; fix the test factories rather than loosening the shipped type for them (lib/__tests__/overview.test.ts already has a row({}) helper used at line 96, which is the natural place to default it).
5. [Axis 3] overview.test.ts guard comment names a nonexistent getWindowRollup and misdescribes the refactor (evalboard/lib/__tests__/overview.test.ts:495) — The block comment above describe("projectRunRow") reads: "projectRunRow is the single definition of 'does this run count, and with which tasks' — the summary tiles (getWindowRollup) and the paged run table (getRunListing) both go through it. They used to be one loop; if they ever disagreed, the tiles would describe a different set of runs than the table below them with nothing failing." Three things in it are wrong at PR HEAD, and it is the only place a reader is told what this suite guards. (1) getWindowRollup does not exist — grep -rn getWindowRollup evalboard/ --include="*.ts" --include="*.tsx" matches this comment and nothing else. (2) The summary tiles do NOT go through projectRunRow: getOverview calls rowFromScoped(id, scoped, overview.harness) directly (overview.ts:586); only getRunListing uses projectRunRow (overview.ts:832). The genuinely shared seam is scopeRunTasks/rowFromScoped, which is what the nested describe("scopeRunTasks, the seam both share") at line 611 actually exercises. (3) "They used to be one loop" is backwards — on origin/main they were two separate implementations (getOverview had its own inline matching filter; getRunListing had its own scoped-cost block) and this PR merges them into one. FAILURE SCENARIO: a maintainer reads lines 494-498, greps for getWindowRollup to find the tiles' producer, finds nothing, and concludes the tile path is covered by this block — when it runs through rowFromScoped and has no test at all (Finding #1). The comment actively misdirects the exact reader trying to close that gap. FIX: rewrite it to name scopeRunTasks as the shared seam and getOverview as the tiles' producer.
6. [Axis 3] tallyModels' tie-break is unasserted, so the run header's "dominant model" choice is undocumented behaviour (evalboard/lib/__tests__/runs.test.ts:400) — tallyModels (runs.ts:721-739) picks the winner with a strict if (n > bestN) over Map insertion order, so on an exact tie the model appearing first in run.json's task_results wins. That is deterministic for a given run.json, but nothing says so: the new describe("tallyModels") block (runs.test.ts:400-433) covers 2-vs-1, single-model, null rows and empty input, but never an even split — which is the normal shape of the A/B experiment the block's own header comment invokes ("tallyModels is what keeps that claim honest when an A/B experiment fans variants across models inside one run"). FAILURE SCENARIO: an A/B run fans 10 tasks across two models 5/5; tallyModels returns whichever appears first and the header claims it as the run's model with "+1 more" beside it (run-identity.tsx:46-50). If a later refactor switches to >=, the last-seen model silently becomes the header's claim for every tied run and no test notices. FIX: add expect(tallyModels([row("a"), row("b")])).toEqual({ dominant: "a", distinct: 2 }) so first-seen-wins is a pinned contract rather than an artifact of Map iteration order.
7. [Axis 4] Run-artifact-controlled version string interpolated unescaped into a rendered GitHub href (evalboard/lib/runs.ts:501) — New in this PR (runs.ts:498-501, reached by the new else-branch at runs.ts:550):
versionUrl: (v) =>\n v === "0.1.0"\n ? null\n : https://github.com/UiPath/coder_eval/releases/tag/v${v}`,`
url = comp.versionUrl?.(value) ?? comp.nonShaUrl;
value is environment_info.coder_eval read verbatim from a run's run.json — the only validation applied is typeof v === "string" && v && v.toLowerCase() !== "unknown" (runs.ts:538). Unlike the SHA branch (guarded by SHA_RE.test(value), runs.ts:545) and the sibling plugin branch (encodeURIComponent(name), runs.ts:567), this path neither validates nor encodes. The result is rendered as href={url} in app/_components/version-list.tsx:23-26, reachable from the in-scope app/runs/[id]/page.tsx:116 (<VersionList versions={summary.componentShas} />).
No scheme injection is possible (the fixed https://github.com/... prefix), and React escapes the attribute, so this is not XSS. The real (small) impact is link spoofing: the evalboard accepts manual ad-hoc run uploads, and a crafted environment_info.coder_eval containing ../ segments is normalized by the browser at navigation time, so a chip labelled with a plausible version can resolve to an arbitrary github.com/<attacker>/<repo> page — a phishing surface on a page reviewers trust to identify what produced a run.
Fix: validate before linking — gate versionUrl on a semver-shaped regex (e.g. /^\\d+\\.\\d+\\.\\d+([-.\\w]*)$/) and return null otherwise, and/or wrap with encodeURIComponent(v) to match the tool_plugins branch. Also set rel="noopener noreferrer" on version-list.tsx:26 (currently only rel="noreferrer") for consistency. Candidate JS-side guard: an ESLint no-restricted-syntax rule (or a vitest assertion in lib/__tests__/runs.test.ts) forbidding a template literal that interpolates an unvalidated value into a https:// URL that becomes an href, since tests/lint/rules/ is a Python-AST runner and cannot see .ts. CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:N/I:L/A:N
8. [Axis 5] Harness identity still lives in three tables across two layers, and HarnessBadge re-hardcodes the default harness instead of importing DEFAULT_HARNESS (evalboard/app/_components/harness-badge.tsx:52) — The PR's commit e4eb23b claims "one HARNESSES table for id, label, logo and colour", but adding a harness still means editing three tables in two files: KNOWN_HARNESSES (lib/harness.ts:11) and HARNESS_COLORS (lib/harness.ts:69) plus HARNESS_LOGO (harness-badge.tsx:13). Key coverage is guarded by tests (harness-badge.test.tsx:10-12; harness.test.ts:107-109), so the residual defect is the unguarded default: harness-badge.tsx:52 reads const key = harness ?? "claude-code"; while lib/harness.ts:22-25 owns DEFAULT_HARNESS = "claude-code" and its comment even says "Mirrors HarnessBadge's default" — a mirror, not a source. If legacy-run attribution is ever changed, normalizeHarness (used by the charts and every scope filter) and the badge will disagree and the UI will label a run with the wrong vendor mark. Fix: import { DEFAULT_HARNESS } from "@/lib/harness" and use harness ?? DEFAULT_HARNESS; optionally fold HARNESS_LOGO's label/short into the lib table (leaving only the <Image> in app/) so one record per harness carries id/label/logo/colour as claimed.
9. [Axis 5] New tallyModels duplicates mostCommonAgentType's argmax-over-tally in the same file (evalboard/lib/runs.ts:720) — tallyModels (lines 720-738) and mostCommonAgentType (lines 924-940) are the same 15-line algorithm over the same RawTaskResult[], differing only in the field read (r.model_used vs r.agent_config?.type) and in tallyModels also returning counts.size. The new function's own comment concedes it at line 717: "Mirrors mostCommonAgentType's 'the thing these tasks ran on' vote". Collapse to one tallyBy(rows, pick: (r) => string | undefined): {dominant, distinct} and have mostCommonAgentType return tallyBy(rows, r => r.agent_config?.type).dominant; both are already exercised by lib/tests/runs.test.ts so the refactor is test-covered.
10. [Axis 6] Scoped cost sum silently understates a partial slice while the sibling duration sum nulls out, and the Cost tile's caveat cannot fire (evalboard/lib/overview.ts:759) — scopeRunTasks treats the two aggregates asymmetrically: totalCostUsd: costHasAny ? costSum : null, taskDurationSeconds: durAllPresent ? durSum : null, (overview.ts:757-761). Duration is all-or-nothing (covered by the new test "nulls a scoped duration when any matching task lacks one", lib/tests/overview.test.ts), but cost is emitted as soon as one matching task recorded a value. summarizeListing then reports costPartial: false (overview.ts:156), so the Total cost tile shows the smaller number without its "some runs missing cost" caveat (app/_overview/window-summary.tsx:70-77). FAILURE SCENARIO: filter to a tag whose slice has 5 matching tasks, 2 of which errored without a recorded total_cost_usd — the tile shows the 3-task sum labelled "matching tasks · 30d" with no caveat, understating spend while the Tasks tile counts all 5. The rule is inherited from main, but this PR makes it the single definition shared by the tiles, both charts, and the table, and it added a test for the duration half only. FIX: carry a task-granular costPartial out of ScopedRun and surface it on the tile, or apply the all-or-nothing rule to cost; add the mirror test.
11. [Axis 6] coder_eval version chip links any non-"0.1.0" version to a release tag, so dev/editable versions link at a nonexistent tag and the documented git_commit fallback can never fire (evalboard/lib/runs.ts:498) — versionUrl: (v) => v === "0.1.0" ? null : https://github.com/UiPath/coder_eval/releases/tag/v${v}`,` (runs.ts:498-501) is a denylist of exactly one known-bad value; every other value of env_info.coder_eval — which is importlib.metadata.version("coder_eval") (src/coder_eval/utils.py:463) — is assumed to name an existing release tag, and it is consumed at runs.ts:550 (url = comp.versionUrl?.(value) ?? comp.nonShaUrl;). The documented git_commit fallback (runs.ts:504-507) never engages because the version key wins the keys loop (runs.ts:508). FAILURE SCENARIO: a run produced from a checkout whose pyproject version was bumped to 0.9.2 but not yet tagged by the Release workflow renders a coder_eval 0.9.2 chip linking to .../releases/tag/v0.9.2, which 404s — the reader cannot tell an unreleased build from a released one. FIX: allowlist instead — link only when the value matches a strict release shape (e.g. /^\d+\.\d+\.\d+$/) and render anything else as plain text, as the "unknown" sentinel already is (runs.ts:534).
12. [Axis 7] WindowSelector/?window= removal left-overs: no redirect or alias for bookmarked ?window= links, a dead WINDOWS export, and comments pointing at the deleted component (evalboard/app/path-to-ga/page.tsx:29) — searchParams: Promise<{ h?: string }> (path-to-ga/page.tsx:29) no longer accepts window, and const WINDOW: Window = "30d"; (line 24) replaces the former parseWindow(params.window); app/page.tsx made the same change (page.tsx:32 const WINDOW: Window = "30d";, and its searchParams type at page.tsx:100-106 lists only tag/q/h/limit/alimit). evalboard/app/_components/window-selector.tsx is deleted. Consequences worth cleaning up in the same PR: (a) an existing /path-to-ga?window=7d or /?window=1d link now silently renders 30 days — acceptable only because every label says "last 30d", so at minimum say so in the PR description; (b) WINDOWS in lib/reviews-types.ts:26 and its re-export at lib/reviews.ts:18 now have zero consumers (grep for WINDOWS at HEAD returns only those two definition sites) — dead export; (c) app/_components/harness-selector.tsx:7 still reads // preserving the active q/tag params, mirroring WindowSelector., referencing a component that no longer exists. Since no ESLint config exists under evalboard/, propose @typescript-eslint + eslint-plugin-unused-imports with import/no-unused-modules (or a knip/ts-prune step in CI) as the mechanical guard for unreferenced exports — tsc --noEmit cannot see them.
13. [Axis 7] "Which harnesses exist" has two independent definitions (selector list from recent runs vs chart series from the window), so a legend line can name a harness the selector cannot isolate (evalboard/app/page.tsx:270) — page.tsx:270 passes harnesses={harnesses} from listRecentHarnesses() (page.tsx:132), which is built from loadRecentRuns(HARNESS_DISCOVERY_COUNT) with HARNESS_DISCOVERY_COUNT = 12 (lib/overview.ts:305-308), while page.tsx:277 and page.tsx:294 pass harnesses={overview.harnesses}, which is orderHarnesses(seenHarnesses) over the 30-day window (lib/overview.ts:612). The two sets are unrelated: a harness that ran 20 days ago but not in the last 12 runs gets a line and a legend entry (harness-legend.tsx:16-29) with no selector segment to scope to it, and conversely a stale store whose newest 12 runs predate the window lists selector segments that all resolve to an empty chart. Derive the selector's options from the same window the charts plot (union them with the active ?h= value, which harness-selector.tsx:46-49 already handles) so one definition drives both.
14. [Axis 8] A stamp-less legacy run is badged "Claude Code" in the table and plotted as claude-code, but its own run header claims no harness (evalboard/app/runs/[id]/run-identity.tsx:23) — app/runs/[id]/run-identity.tsx:23 — if (!harness && !model) return null; — renders nothing for a run whose summary.harness is null, while the same run in the runs table gets a Claude Code logo, because app/_components/harness-badge.tsx:52 does const key = harness ?? "claude-code";, and lib/overview.ts:589 plots it as a claude-code chart point via normalizeHarness. So the same fact about the same run is presented three ways across surfaces this PR touches. The fold is correct per the producer (coder_eval_uipath/eval_runner/src/eval_runner/run_config.py:22-27: every stamp-less legacy run was the claude-code nightly), so the detail header is the outlier. Note also that harness-badge.tsx re-exports from @/lib/harness on line 6 yet re-implements the default inline instead of calling normalizeHarness, which is what let the two drift. Fix: have RunIdentity take the normalized harness (or call normalizeHarness) so the header agrees with the table and the chart, and replace harness ?? "claude-code" in HarnessBadge with normalizeHarness(harness).
15. [Axis 8] A fifth or later harness draws an indistinguishable grey line and an indistinguishable grey legend swatch (evalboard/lib/harness.ts:79) — lib/harness.ts:79 defines a single HARNESS_COLOR_FALLBACK = "#6b7280" returned by harnessColor for any harness without a reserved slot (line 82), and the comment at lines 76-79 argues this is safe because "the legend still names each line". That holds for one unknown harness but not two: orderHarnesses (lines 88-95) appends every newcomer, pivotByHarness gives each its own series (app/_overview/harness-series.ts:43-47), and HarnessLegend renders the swatch from s.color (app/_overview/harness-legend.tsx:21-25) — so two newcomers produce two identically-grey lines with two identically-grey legend swatches, and the legend can no longer tell the reader which line is which. HARNESS_COLORS currently has exactly the four KNOWN_HARNESSES entries, so this is not reachable today; it becomes reachable the moment a fifth harness's runs land, which is the scenario the data-driven switcher exists to support. Fix: either dash/dot-style the fallback series so shape disambiguates where colour cannot, or add the validated fifth palette slot the comment at lines 66-68 already describes the process for.
What's Missing
Tests:
- 🟠 No CI job runs the evalboard at all:
.github/workflows/pr-checks.ymlmentionsevalboard/only in an osv-scanner comment, there is nosetup-node/pnpm/viteststep, and no Makefile target or pre-commit hook invokespnpm verify— so the 5 new test files added here (app/_overview/__tests__/harness-series.test.ts,harness-legend.test.tsx,run-identity.test.tsx,lib/__tests__/pass-rate.test.ts,harness-badge.test.tsx) plustsc --noEmitandnext buildare gated only by the author remembering to run them locally, while the hosted app deploys fromcoder_eval@main. _(trigger: evalboard/app/overview/tests/harness-series.test.ts) - 🟡 The
v === "0.1.0"legacy-sentinel branch — the only reasonversionUrlis a callback rather than a template — has zero tests:lib/__tests__/runs.test.ts:353-380covers0.9.1,0.10.0, thegit_commitfallback and the"unknown"sentinel, but never asserts that0.1.0renders as plain text, so the one value the denylist exists to suppress is unguarded. (trigger: evalboard/lib/runs.ts) (restates: Axis 6: coder_eval version chip links any non-"0.1.0" version to a release tag) - 🟡
getOverviewgained three new outputs this PR —harnesses(drives every chart series + the legend),totalsandrunCount(now feedWindowSummaryinstead of the listing) — and has no test of its own; only its helpers (scopeRunTasks,summarizeListing,pivotByHarness) are covered, so nothing asserts that the tiles and the charts are folded from the same loop, which is the invariant the new code was written to establish. (trigger: evalboard/lib/overview.ts) - 🟡
tag-rail.tsxchanged the harness-preservation rule inhrefForTag(wasif (harness && harness !== DEFAULT_HARNESS), nowif (harness)) and dropped thewindowparam, yet there is noapp/_overview/__tests__/tag-rail.test.tsxat all — a regression here silently widens a codex-scoped view back to all harnesses on the next tag click, with the counts and charts changing under the reader. _(trigger: evalboard/app/overview/tag-rail.tsx) - 🔵
readRunSummary's three new fields (harness,model,modelCountatlib/runs.ts:710-712) are untested; only thetallyModelshelper is, so nothing pins that the run header's identity claim is wired toenvironment_info.run_configwith themostCommonAgentTypefallback rather than to some other key. (trigger: evalboard/lib/runs.ts) (restates: Axis 3: tallyModels' tie-break is unasserted) - 🔵
WindowSummarywas rewired from the run listing togetOverviewand its copy changed (matching · last 30d,matching tasks · 30d) precisely so the tiles don't read as all-time now that the table pages past the window — and there is nowindow-summarytest asserting either the newrunCount/totalswiring or that every sub-label names the window. _(trigger: evalboard/app/overview/window-summary.tsx)
Parallel paths:
- 🟡
?h=now means two different things depending on the page and nothing carries the scope between them: overview + path-to-ga useparseHarnessScope(absent = all harnesses,includeAllchip rendered), while/trends,/watchlistandtrends/actions.tsstill useparseHarnessParam(absent = claude-code, no All chip) — and the header nav links inapp/layout.tsxpass noh, so clicking Trends from an all-harness overview silently narrows to claude-code with no cue and no way to widen. (trigger: evalboard/lib/harness.ts) - 🟡 The sibling ad-hoc listing was left on the old contract:
getAdhocRunListing/buildAdhocRowsstill return a true pre-limittotaldrivingShow {adhocNextPageSize} moreand{shown} of {total}(page.tsx:456-490), while the main table switched tohasMore/totalCandidates— so one page now carries two paging idioms whose "N of M" labels mean different things (matched runs vs. date-shaped candidate ids), and the two will drift on the next paging change. (trigger: evalboard/lib/overview.ts) - 🟡
RunIdentityrenders the harness badge unconditionally atapp/runs/[id]/page.tsx:106-111, while every other harness surface is edition-gated — the runs-table Harness column (page.tsx:409,colSpan={isInternal ? 6 : 5}at 443), the whole analytics block + selector (page.tsx:211) — andharness-badge.tsx:12states "This is an internal-only column — the caller gates it behind isInternal"; the OSS edition now shows harness/model identity that the same build hides one click away. (trigger: evalboard/app/runs/[id]/run-identity.tsx) - 🟡
probeAllopts the main run table out of the scan cap without updating the cap's own rationale (lib/overview.ts:337-347still says the cap "keeps a pathological stretch of unusable runs … from walking the entire container"): with a?h=/tag/q filter that matches fewer thanlimit + 1runs — including a stale or mistyped harness scope —getRunListingloads every date-shaped run.json in the store, and refill rounds are floored atMIN_PROBE_BATCH = 5, so the tail is sequential rounds of 5 blob reads per render. No bound, no worst-case test, and the hosted deploy reads these multi-MB blobs over the network. (trigger: evalboard/lib/overview.ts)
Downstream consumers:
- 🟡 The 95/85 cutoffs are now duplicated across two repos with nothing but prose keeping them together:
lib/pass-rate.ts:9-10says "Change these only alongside that script" and points atcoder_eval_uipath/eval_runner/scripts/ci/slack_summary.py(GREEN_PCT = 95.0/RED_PCT = 85.0— verified identical today), but no test, generated constant or CI check fails if either side moves, which is exactly the channel-vs-board disagreement the file was created to prevent. (trigger: evalboard/lib/pass-rate.ts) - 🟡 Mature-skip carry-forward is invisible in the new cross-harness comparison:
matureSkippedrows count as SUCCESS ingetOverview'ssuccessRate, and only the codex daily is maturity-gated (eval_runner/src/eval_runner/run_config.py:29-33), yet the legend, tooltip and tiles carry no marker — while/trends,run-view.tsx:78andtask-grid.tsx:189all annotate it. The default all-harness chart therefore plots a carry-forward-inflated line against full-suite lines as if they were like-for-like. _(trigger: evalboard/app/overview/harness-legend.tsx) - 🔵 Two adjacent pages now compute "pass rate" with different math from the same
getOverviewpayload:path-to-ga/page.tsx:45-49averages per-run rates whilewindow-summary.tsx:56-59uses task-weightedtasksSucceeded / tasksRun;overview.totalsis already on the payload the page awaits, so the divergence is unused-availability rather than cost. (trigger: evalboard/app/path-to-ga/page.tsx) (restates: Axis 8: Path-to-GA headline pass rate becomes a run-weighted mean pooled across harnesses)
Display & mapping dicts:
- 🔵 Adding a harness still means editing three hand-maintained tables with no compile-time link between them (
KNOWN_HARNESSESandHARNESS_COLORSinlib/harness.ts,HARNESS_LOGOinharness-badge.tsx), and the newdelegate-sdkentry has no producer counterpart yet —eval_runner/run_config.pydefines onlyclaude-code/codex/antigravity, so the fourth chip, colour and logo ship ahead of any run that stamps them, while a harness that does appear without a table entry falls through to grey#6b7280plus its raw id. (trigger: evalboard/lib/harness.ts) (restates: Axis 2: KnownHarness union is exported but unused; both harness lookup tables stay Record<string, …>)
Daily/nightly:
- 🟡 The PR states no hosted-deploy blast radius even though the evalboard is the internal dashboard over the nightly's blob store: it is built and deployed from
UiPath/coder_eval@mainbycoder_eval_uipath/.github/workflows/deploy-evalboard.yml(manualworkflow_dispatch, default refmain, no test/typecheck step), so these changes reach production only on someone dispatching that workflow — and nothing says who redeploys, that?window=bookmarks silently become 30d after it, or that the full-historyprobeAllscan now runs against blob on every filtered render. (trigger: evalboard/app/page.tsx) - 🔵 The default scope was changed on the overview only, so the dashboard now disagrees with itself about what you see first:
/trendsand/watchliststill default toDEFAULT_HARNESS = "claude-code", which pereval_runner/run_config.py:29-33is a weekly harness, while codex is the daily driver — the per-task history pages therefore open on the sparsest series while the home page opens on everything. (trigger: evalboard/app/page.tsx)
Harness & Lint Improvements
Static checks (lint / type):
- [ce-lint] CE032 — "harness identity literals live only in lib/harness.ts". Forbid any KNOWN_HARNESSES id string literal ("claude-code", "codex", "antigravity", "delegate-sdk") in evalboard/app/** and evalboard/lib/** outside lib/harness.ts, except as keys of a table annotated
satisfies Record<KnownHarness, ...>. PLACEMENT NOTE (applies to every ce-lint item below): tests/lint/rules/ is a Python-AST runner and cannot see .ts, so these land either as a pytest lint class over evalboard sources (the CE027–CE031 pattern, which already reasons over non-.py surfaces and therefore gates in the existing Python CI job today) or as source-text invariant tests in the established style of evalboard/lib/tests/module-boundaries.test.ts (idiomatic, but does not gate until harness improvement #1 lands). Measured noise at PR HEAD: exactly 3 non-comment hits (harness-badge.tsx:14, :30 table keys, and :52), so the rule is affordable as written. Prevents: Finding 14 (harness-badge.tsx:52const key = harness ?? "claude-code"re-hardcodes the default instead of importing DEFAULT_HARNESS — the drift vector between the badge and normalizeHarness) and Finding 20/21 (a stamp-less legacy run badged Claude Code in the table, plotted as claude-code, and claiming no harness in its own run header). - [pyright] tsc-side tightenings (pyright's TypeScript counterpart) in evalboard: (a) append
satisfies Record<KnownHarness, string>to HARNESS_COLORS (lib/harness.ts:69) andsatisfies Record<KnownHarness, { src: string; label: string; short: string }>to HARNESS_LOGO (harness-badge.tsx:13) so the exported KnownHarness union becomes load-bearing whilesatisfiespreserves the widestringindex signature harnessColor()/harnessShortLabel() need; (b) enablenoUncheckedIndexedAccessin evalboard/tsconfig.json; (c) enableexactOptionalPropertyTypes— honest limit: it does not ban a three-state field, it only forces absent-vs-null to be written explicitly, so Finding 5's real fix (requiredharness: string | null+Omit<>on AdhocRunRow) stays a code change. Prevents: Finding 4 (KnownHarness exported with zero consumers; both lookup tablesRecord<string, …>, so adding or renaming a harness compiles clean and silently falls back to grey + raw id — today caught only at test time) and the build-time half of Finding 23. Partially surfaces Finding 5. - [pyright] Derive injected third-party prop types from the dependency instead of restating them: replace the hand-rolled
interface TooltipEntry(harness-legend.tsx:37-41) withtype TooltipEntry = NonNullable<TooltipProps<number, string>["payload"]>[number](recharts re-exports TooltipProps from its root, node_modules/recharts/types/index.d.ts:10), and make the payload filter a type predicate so the!at line 88 and theas numberat line 103 disappear — keeping thetypeof e.value === "number"guard, since this app's data genuinely produces nulls that the derived type does not admit. Mechanical companion: a ce-lint/eslint check that a module rendering a rechartscontent={…}component declares no local interface carryingpayload/dataKey/active. Prevents: Finding 3 (recharts types thecontentinjection site as a bare ReactElement, so daily-chart.tsx:70-77 and turn-budget-chart.tsx:71-78 get zero checking that HarnessTooltip's payload shape matches what recharts injects; the tests build payloads from the same hand-rolled type, so an upstream rename ships green and every hover silently renders "no tasks"). - [ruff] Bootstrap ESLint in evalboard/ — ruff's TypeScript counterpart is entirely absent today (no ESLint config exists anywhere under evalboard/). Add
next/core-web-vitals+@typescript-eslinttype-checked withno-non-null-assertion,no-unnecessary-type-assertion,no-unnecessary-condition,consistent-type-imports,react/jsx-no-target-blank, and ano-restricted-syntaxentry for template literals interpolating into ahttps://string; wirepnpm lintintopnpm verify. Measured cost: only ~8 production (non-test) sites use!/as number/as string, so the assertion bans are landable in one pass. Prevents: Finding 3's two assertions (harness-legend.tsx:88, :103), the missingrel="noopener noreferrer"in Finding 11 (version-list.tsx:26 currentlyrel="noreferrer"only — jsx-no-target-blank covers exactly this), and future recurrences tree-wide. - [ce-lint] CE033 — "a URL built from run-artifact data must be allowlist-validated or encoded". Forbid two shapes in evalboard/lib: (a) a template literal interpolating a value into a
https://…string where the interpolated expression is neitherencodeURIComponent(...)nor guarded by a.test(...)regex in the same function; (b) the inverse-denylist link guardvalue === "<literal>" ? null : <link>. Require a positive shape test (e.g./^\d+\.\d+\.\d+$/) and render non-matching values as plain text, as the "unknown" sentinel already is. This is CE018 (no_final_status_name_denylist, a denylist membership test against a set) transplanted to the TS consumer side. Prevents: Finding 11 (runs.ts:498-501 interpolatesenvironment_info.coder_evalverbatim into a rendered GitHub href with neither validation nor encoding — link spoofing via../on manually uploaded ad-hoc runs, while the sibling tool_plugins branch at runs.ts:567 already uses encodeURIComponent) and Finding 18 (the same conditional is a denylist of exactly one value, so an untagged 0.9.2 build links to a 404 release tag and the documented git_commit fallback at runs.ts:504-507 can never fire). - [bandit-codeql] Add
javascript-typescriptto the CodeQL matrix — .github/workflows/codeql.yml:33 islanguages: pythononly, so the entire evalboard tree (the surface this review covers) has never been scanned. With the existingqueries: security-and-qualitythe js/ts pack contributes URL-construction / href-sink queries plus unused-export and dead-code queries. Paths-filter toevalboard/**to keep the run cheap. Prevents: Finding 11 as an automated security alert rather than a reviewer catch, plus independent coverage of the dead-export half of Findings 4 and 22(b). - [ce-lint] CE034 — "no dangling symbol references in comments". A comment in evalboard/** naming an identifier in an unambiguous code form (backticked,
foo(), or a CamelCase component name) must resolve to a symbol declared somewhere in the tree. Verified feasibility at PR HEAD:getWindowRollupoccurs ONLY at lib/tests/overview.test.ts:495 (inside the comment) andWindowSelectorONLY at app/_components/harness-selector.tsx:7 (inside the comment) — both grep to zero declarations, so the rule fires on exactly the two real defects with no other hits. Same family as CE028's doc-drift gate; worth extending to src/ docstrings. Prevents: Finding 9 (the only comment telling a reader what overview.test.ts guards names a nonexistent producer and misdescribes the refactor, actively misdirecting the reader trying to close Finding 6's gap) and Finding 22(c) (comment still mirrors the deleted WindowSelector). - [ce-lint] CE035 — "pipeline entry points and URL-writing components must be exercised". (a) Every exported function in lib/overview.ts and lib/runs.ts must be IMPORTED by a file under lib/tests (critical detail:
getRunListingpasses a naive word-match only because it appears in a comment at overview.test.ts:496), or carry anEXEMPT: <reason>comment in the CE030 style. Measured at PR HEAD: 6 of 14 lib/overview.ts exports would flag — getRunListing, listRecentHarnesses, getAdhocRunListing, getTagTaskBreakdown, aggregateTaskTagCounts, taskMatchesTag. (b) Any client component callingrouter.replace/router.push/useSearchParamsmust have a co-located__tests__/<name>.test.tsx; harness-selector.tsx has none, andalimitappears in zero test files tree-wide. Prevents: Finding 6 (high — getRunListing's newlimit + 1over-fetch paired withrows.length > limit, plustotalCandidates: ids.length, have zero tests despite being the sole driver of the "Show more" affordance) and Finding 8(a) (HarnessSelector's all-harness scope, All chip and limit/alimit pagination reset — the mechanism behind the PR's headline feature — ship with no test file). - [ce-lint] CE036 — CE031 ("dead config field") transplanted to the evalboard row/point types: a field declared on the exported pipeline interfaces (RunPoint, RunListingRow, ListingSummary, ScopedRun) must (a) be read by name somewhere outside its own declaration and producer, and (b) not be assigned only a constant literal at its single producer. Extend the same reader-existence check to exported consts/types in lib/** so dead exports are covered by one rule. Prevents: Finding 2 (RunPoint.runId is written at overview.ts:592 and read nowhere — the pivot discards it, so the tooltip cannot name or link the run even though the data is present), Finding 4 (KnownHarness), Finding 22(b) (WINDOWS at reviews-types.ts:26 plus its re-export at reviews.ts:18 have zero consumers), and the load-bearing half of Finding 17 (
costPartial: falseis constant at its only producer, so the Total-cost tile's "some runs missing cost" caveat can never fire). - [ce-lint] CE037 — "pass-rate arithmetic lives in lib/pass-rate.ts". Forbid ad-hoc pass-rate math (
(x / y) * 100,sum(successRate) / n) anywhere in evalboard/app/** or lib/** outside the shared helper module, which must expose two explicitly named helpers (taskWeightedPassRate,runWeightedPassRate) so every page is forced to pick and name a weighting. Measured at PR HEAD: 5 sites would flag — app/page.tsx:390, app/page.tsx:528, app/path-to-ga/page.tsx:47, app/_overview/window-summary.tsx:58, lib/overview.ts:595. Prevents: Finding 19 (high — path-to-ga's GA-readiness headline is an unweighted mean over per-RUN rates pooled across harnesses, while the front page's headline over the same getOverview payload is task-weightedtotals.tasksSucceeded / totals.tasksRun; two adjacent pages report "pass rate" with different math, and the run-weighted one is dominated by whichever harness runs most often). - [ce-lint] CE038 — "near-duplicate sibling modules / clones". Flag (a) any pair of files in the same directory whose non-comment lines are >70% byte-identical, (b) intra-file clones of ≥15 lines (jscpd, or a normalized-line-hash pass to avoid a new npm dep), and (c) the same non-trivial regex literal appearing twice in one module. Measured at PR HEAD: daily-chart.tsx vs turn-budget-chart.tsx = 95 of 110 lines byte-identical (11 of the 15 differing lines are comments); tallyModels (runs.ts:720-738) vs mostCommonAgentType (runs.ts:924-940) is the same 15-line argmax;
/^[\w.-]{1,64}$/appears at lib/harness.ts:36 and :56. Prevents: Finding 15 (the two overview charts still duplicate the entire chart shell — including shortLabel and a connectNulls comment describing a difference that no longer exists — despite commit e2628e0 claiming "one multi-series chart behind both"), Finding 13 (tallyModels duplicates mostCommonAgentType's argmax in the same file), and Finding 1 (the harness-id charset regex has two sources of truth, so?h=validation can drift between parseHarnessParam and parseHarnessScope). - [ce-lint] CE039 (small, deliberately narrow) — "no test-driven widening of a shipped type". Flag an optional (
?:) field on an exported interface in evalboard/lib whose adjacent comment mentions tests/fixtures/factories (e.g. overview.ts:114 "// Optional so existing test factories stay valid."): fix the factories, not the production contract. It is comment-keyed on purpose — the general "this optionality models nothing real" judgment is not statically decidable, which is why the tsconfig flips in item 2 only partially cover it. Prevents: Finding 5 (RunListingRow.harness is optional purely for test factories, giving the field three meanings — absent / null / id — and because AdhocRunRow extends it, adding a Harness column to the ad-hoc table would silently attribute every ad-hoc run to Claude Code with no compile error, the exact mis-attribution this PR exists to prevent).
Harness improvements (not statically reachable):
- Give evalboard a CI gate at all: add a
make evalboard-verifytarget (pnpm -C evalboard install --frozen-lockfile && pnpm -C evalboard verify) plus anevalboardjob in .github/workflows/pr-checks.yml (setup-node 20 + pnpm, paths-filtered onevalboard/**). Todaypnpm verify(tsc --noEmit + vitest run + next build) exists in evalboard/package.json but no workflow invokes it — the stringevalboardappears in pr-checks.yml exactly once, inside an osv-scanner exclusion comment — and the root Makefile has no evalboard target, so ~30 test files and the type checker never run on a PR. Why not static: It is a missing execution surface, not a code pattern — no linter can flag a CI job that was never written. It is also the prerequisite that makes every static check above real: a vitest-implemented invariant that no CI job runs is documentation, not enforcement (hence the pytest-lint-class placement offered as the interim path, since that already runs in the Python quality gate). Prevents: Structurally every finding in this review; concretely Findings 6 and 8, whose missing tests cost nothing today because the suite is never executed on a PR. - Add a
coverageblock with thresholds to evalboard/vitest.config.ts (mirroring the Python 80% gate), scoped to lib/** and app/_overview/**.@vitest/coverage-v8is already a devDependency but the config declares no coverage section at all, so an entirely untested pipeline entry point is invisible. Why not static: Coverage is a property of an executed suite: CE035 can see that a symbol is never imported by a test, but not that a branch inside an otherwise-tested function is never taken. Prevents: Finding 6 (getRunListing's limit+1 → hasMore derivation), Finding 8(b) (run-view'stotalN > 0tone guard and passClass/passBarClass wiring — the bands themselves are covered by the new lib/tests/pass-rate.test.ts, the empty-run branch and the wiring are not), Finding 10 (tallyModels' tie branch). - Add one table-driven render-contract test for the unknown-harness path: mount every consumer of a
harnessprop (HarnessBadge, HarnessLegend, RunIdentity, HarnessSelector) with an id absent from HARNESS_LOGO and assert the id appears exactly once (e.g.expect(container.textContent).toBe("gemini-cli")), deriving the consumer list by greppingharness=under app/ so a new consumer must be enrolled or the test fails. Extend with a two-unknown-harness chart case asserting the two series differ in stroke colour OR dasharray. Why not static: The defect exists only in composed rendered output — HarnessBadge's text fallback and harnessShortLabel are each individually correct and individually tested; the duplication ("gemini-cligemini-cliCodex") appears only when both run in the same subtree. The two-grey-lines case depends on harness ids that arrive from run data at runtime, which no build-time check can enumerate. Prevents: Finding 7 (unknown harness renders its id twice in all three composed consumers; coverage is split at the seam, with harness-badge.test.tsx locking the raw-id fallback in) and Finding 23 (a fifth harness draws an indistinguishable grey line and grey legend swatch). - Add a URL-state round-trip test layer for the harness scope, reusing the mock pattern already in-repo at app/_components/tests/search-box.test.tsx:10 (vi.mock("next/navigation") with a captured
replacespy): assert All clearsh, a segment setsh=<id>, both droplimit/alimitwhile preservingtag/q,includeAllomitted renders no All segment, and acurrentoutsideharnessesstill renders. Pair with a page-level assertion that a zero-match scope renders scope-aware empty copy — confined to the empty state, since extending isNarrowed to the clear-filter link would render a link that clears nothing (clearAllHref deliberately preserves the harness scope). Why not static: The invariant relates a user action to the resulting query string and to copy that a different module renders from a different derived flag (page.tsx's isFiltered/isNarrowed pair) — it only exists at runtime, and there is no e2e layer in evalboard at all. Prevents: Finding 8(a) (pagination reset and All chip entirely unasserted;alimitappears in zero tests tree-wide) and Finding 16 (a harness-only scope with no matches prints "no runs yet" directly under a header reading "0 matching"). - Add a headline-parity test: push one fixture getOverview payload through both the front-page WindowSummary computation and the path-to-ga headline, and assert either that they agree (both task-weighted from
overview.totals, which the same payload already carries at zero extra cost) or that their labels differ explicitly (e.g. "avg pass rate · pooled across N harnesses · last 30d"). Why not static: CE037 can force both call sites through one module, but only an executed fixture demonstrates that two weightings over identical stored data yield materially different headlines (28 codex runs at 100% + 2 claude-code runs at 0% → a 93% GA-readiness figure while claude-code readiness is 0%). Prevents: Finding 19 (run-weighted vs task-weighted divergence between two adjacent pages now that both default to all harnesses). - Add a chart-data honesty fixture test: a window in which one in-window run of a harness has a null metric, asserting the rendered series either visibly breaks or carries an explicit unmeasured marker, and that RunPoint's doc comment matches its consumer. Implementation correction for whoever takes it: the lost gap is caused by the point being dropped at app/_overview/harness-series.ts:55, not by the connectNulls flag — with the row absent from
datarecharts joins across the date even at connectNulls={false}, and emitting an explicit null row does not help while the flag is true, so the working remedies are a dashed bridged span, explicit segment splitting, or a visible marker. At minimum correct the now-false comment at lib/overview.ts:36-37. Why not static: It is a rendered-output property of pivoted data crossing into a third-party chart library; both modules read as correct in isolation, and CE034 catches only comments naming nonexistent symbols, not a comment whose claim about its consumer silently became false. Prevents: Finding 12 (the turn-budget chart no longer breaks at runs with no turn budget, so an unmeasured date reads as continuous measurement; also surfaces the now-unreachable emptyText on that chart). - Adopt a boundary-case convention for aggregate/vote helpers: every argmax or aggregate helper in evalboard/lib must pin (i) its tie-break and (ii) its missing-data rule in tests. Apply to tallyModels (
expect(tallyModels([row("a"), row("b")])).toEqual({ dominant: "a", distinct: 2 })) and to scopeRunTasks (add the cost mirror of the existing "nulls a scoped duration when any matching task lacks one" test). Why not static: A linter sees the>vs>=and the differing presence predicates but cannot know which answer is intended — first-seen-wins, and all-or-nothing vs any-present, are product decisions only a pinned assertion can record. Prevents: Finding 10 (tallyModels' first-seen-wins tie-break is an unasserted artifact of Map iteration order, and a 5/5 A/B fan-out is its normal shape) and Finding 17 (cost is emitted as soon as one matching task has a value while duration is all-or-nothing, so a partial slice understates spend with no caveat). - Add a producer/consumer contract fixture between coder_eval and evalboard: check in one current stamped
run.jsonand one legacy stamp-less one, and assert the evalboard's readers agree with the producer's contract — harness stamp normalization, theenvironment_info.coder_evalvalue shape, and the legacy no-stamp attribution rule — with a pointer to the producer (coder_eval_uipath eval_runner run_config.py) as the authority. Why not static: The contract spans two languages and a serialized artifact: nothing in the TS tree can assert what the Python producer actually emits, and the version-shape assumption in particular is only falsifiable against real output. Prevents: Finding 18 (version-shape assumption behind the release-tag link), Finding 20/21 (three different presentations of the same stamp-less run across table, chart and run header), and future silent drift in the run.json consumer contract. - Add a lightweight route test / checklist step for removed query parameters: when a search param is dropped (here
?window=), either add a redirect/alias or assert-and-document that bookmarked links silently fall back, and say so in the PR description. A one-line route test on/?window=7dand/path-to-ga?window=7dpins whichever choice is made. Why not static: It needs request-level behaviour (what a real bookmarked URL does today), which no source-level rule can evaluate; the deadWINDOWSexport half of the same cleanup IS statically reachable and is covered by CE036. Prevents: Finding 22(a) (an existing?window=7dlink now silently renders 30 days with no redirect or alias).
Top 5 Priority Actions
- Fix the Path-to-GA headline first —
app/path-to-ga/page.tsx:45-49still averages per-RUNsuccessRatebut now pools every harness (:36switched toparseHarnessScope, dropping main's single-harness default), so for byte-identical stored runs the 3xl "avg pass rate" silently became a cadence-weighted cross-harness number that disagrees with the task-weighted rate the home page computes from the same payload (app/_overview/window-summary.tsx:56-59); useoverview.totalsand label the tile with the pooled scope. - Make the scoped cost aggregate honest —
lib/overview.ts:757-761nulls a partial duration (durAllPresent) but emits a cost sum as soon as ONE matching task recorded a value (costHasAny), andsummarizeListinghardcodescostPartial: false(lib/overview.ts:156), so the Total-cost tile understates spend with no caveat while the Tasks tile counts every task; carry a task-granularcostPartialout ofScopedRun(or apply the all-or-nothing rule to cost) and add the mirror test the duration half already has. - Close the two untested new behaviours on the weakest axis —
getRunListing(lib/overview.ts:800-838) has zero tests despite this PR replacing window counts with thelimit + 1over-fetch ↔hasMore: rows.length > limitderivation that solely gates the "Show more" link (app/page.tsx:144,347), andharness-selector.tsx(the mechanism behind the PR's headline all-harness default, incl. thep.delete("limit"); p.delete("alimit")reset at:39-40) has no test file at all; add the threehasMoreboundary cases plus a selector test using the existingvi.mock("next/navigation")idiom fromapp/_components/__tests__/search-box.test.tsx:10, and one class assertion for run-view's untestedtotalN > 0tone guard (app/runs/[id]/run-view.tsx:410). - Reconcile the two narrowing flags in the run table — the count label uses the harness-aware
isNarrowed(app/page.tsx:145-147) while the empty state still readsisFiltered(app/page.tsx:446-448), so now that the table is harness-scoped (:130) a deep-linked?h=Claude-Codeor retired harness id renders "no runs yet" directly under a header saying "0 matching" over a store full of runs; branch the copy onisNarrowed("no runs match this harness") but leave the clear-filter link at:333alone, sinceclearAllHrefintentionally preserves the harness scope. - Finish the extraction the commit message claims and fix the data-honesty it left behind —
turn-budget-chart.tsxis 95/110 lines byte-identical todaily-chart.tsx(incl. a duplicatedshortLabeland aconnectNullscomment at:93asserting a difference that no longer exists), and the pivot'sif (p[metric] == null) continue;(app/_overview/harness-series.ts:55) now drops unmeasured runs so the line is drawn straight across dates with no turn budget whilelib/overview.ts:36-37still promises "the chart shows a gap"; add oneHarnessLineChart, give bridged spans a visible marker/dash, correct the stale comment, and while in the harness layer dropHarnessBadge's text fallback (app/_components/harness-badge.tsx:55-57, which makes an unknown harness render its id twice) and replaceharness ?? "claude-code"(:52) withnormalizeHarness/DEFAULT_HARNESS.
Stats: 0 🔴 · 2 🟠 · 6 🟡 · 15 🔵 across 8 axes reviewed.

The overview opened scoped to claude-code and only ever re-scoped its charts: the summary tiles and the run list kept covering every harness, so picking codex moved the chart while the tiles above it still reported all-harness cost and pass rate. Nothing on the page said which set of runs a given number described, and the run page itself never said which harness or model produced it.
What changes
The overview compares harnesses by default. Both charts draw one line per harness in the window instead of one line zigzagging across incomparable harnesses, and the default scope is every harness rather than claude-code. Color is bound to the harness, so narrowing the view never repaints the survivors, and each chart carries a legend pairing the swatch with the vendor badge and label so a line is never identified by color alone.
One scope drives the whole page. Selecting a harness recomputes the summary tiles, redraws both charts, and narrows the run list together. Every count says whether it is describing everything or a narrowed set, and the tiles always name the window they cover, so a scoped view can never read as all-time. Path to GA gets the same treatment and the same all-harness default; its task table pools appearances across harnesses when unscoped, which mixes regimes that are not strictly comparable, so it says so and points at the selector.
The run page says what produced it. Harness and model appear in the header as a badge strip. A run that fans variants across models reports the spread instead of silently claiming the dominant one.
Pass-rate colors come from one place, on the nightly Slack rollup's cutoffs (>=95% green, <85% red, amber between). Four call sites had drifted: the front-page table and tiles were green at >=80, Trends was green only at a perfect 100% so a task that passed 9 of its last 10 runs looked as broken as one that never passed, the watchlist had a third set, and the run page's meter was unconditionally green so a 77% run still read as healthy at a glance. A run that pings the channel red and then reads green on the page it links to is the failure this consolidates away.
The time-window control is gone and the run table pages through all history. Charts and tiles cover a fixed 30 days; the table is no longer bounded by that window and grows 20 rows at a time back to the oldest run, which is what a shorter window was actually being used for. Neither table offers a "show all" jump, since every extra row is another multi-MB
run.jsonread.The coder_eval version chip links to its release. It showed a git SHA pointing at a tree, but the framework is pinned and consumed by released version. The SHA stays as a fallback for editable checkouts and legacy runs. As a side effect, in-container runs get a chip at all: their git SHA resolves to
unknownand was dropped, while the version resolves fine.The UiPath harness gets its vendor mark and a shorter name. It reads "Delegate" rather than "Delegate SDK"; the registered
agent.typestaysdelegate-sdk, so only the label changes.The overview works at phone width. The two segmented controls could not fit on one row and pushed the whole page past the viewport. The control row wraps, harness segments keep their labels on one line and drop to logo-only below
sm(each keeps its name as tooltip and accessible label, and the legend below names every line), and the run timestamp stops breaking across three lines.All of the above is internal-edition surface except the run list, the summary tiles, and the run header's harness/model strip. The OSS edition still hides the charts, the harness selector, the tag rail, the Harness column, and the component version chips.
🤖 Generated with Claude Code
Screenshots
Desktop
Mobile
Model ID badge