feat(compare): radar scale toggle, hideable usage axes, row renames - #1765
tawnymanticore wants to merge 16 commits into
Conversation
Mocks echarts, renders the component with fixed props, and asserts on the option object that reaches setOption. No component change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…odule Moves score normalisation, axis scaling, usage inversion, shared-key filtering, label resolution and tooltip trimming out of compare_radar_chart.svelte into lib/utils/radar_chart_data.ts. The component computes chart data once per update. The option object that reaches echarts is byte-identical for the six golden fixtures. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…age and chart The compare page built the usage section from string literals and the radar chart re-typed the same keys, so renaming a row key would have dropped an axis with no type error. compare_metric_keys.ts now holds the section id, the five usage row keys with their table and axis names, and the usage-key predicate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…re_view score_type_max took a string, so a rating type added to the backend would have fallen through to null and quietly disabled Full Scale for that axis. It now takes TaskOutputRatingType and ends in assertNever. It and the axis-max builder move to compare_view.ts as pure functions, with tests for the key format. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Score names, run config names, model, prompt and tool names reached the radar tooltip's HTML unescaped, and echarts renders a tooltip formatter's string as innerHTML. A score name, or a metric_labels entry carried in a shared compare link, could therefore inject markup. chat_markdown's escape helper moves to lib/utils/escape_html.ts and both call sites use it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Series and tooltip lookups keyed on the display name, so two run configs with the same name collapsed into one legend entry showing the first config's model, cost and scores. Chart data now carries the run config behind each series name, and a name two configs share is numbered so echarts can tell the series apart. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The note under the chart title counts every candidate axis dropped for want of a result, usage rows included, so calling them all scores misnamed the cost, latency and token rows it was counting. The count itself is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The note under the title counts axes. The empty state for fewer than three shared axes said "scores". Both now say axes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Hiding rows until fewer than three axes remained unmounted the whole card - title, scale toggle and all - so the chart vanished with nothing to explain it. The card now goes only when the table has no rows at all; one or two rows keep it, and the empty state names the three ways back to a third axis. The "Create and run evals" message is reserved for a table with no eval section, so it no longer greets a user whose eval rows are merely hidden, and the omitted-axis note no longer repeats what the empty state already says. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On a card much wider than it is tall the plot's left edge sat about 0.32W - 0.425H from the left, which is less than the name gap plus the axis name box on a 1280px screen, so the left-hand names ran off the canvas. The wide layout's centre moves right and its radius comes in, and the name box narrows to a width that still fits "Output Token Efficiency" on two lines. The legend stays where it was. A new case computes the left margin from the option itself, so the next change to either number has to keep the names on the card. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The only whole-option case asserted that the keys of an object built from a list of names were that list of names, which is true of any object built that way. It proved nothing about the option, while reading as if the refactors were gated. Each fixture now gets its own case: the option carries exactly the four keys the chart sets, every series datum has one value per axis, all three formatters return something for every series name, and the option holds no undefined while its serialised form holds no function. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…logy The ✎ and ✕ buttons carry the glyph as text, so their title never becomes the accessible name and a screen reader reads out the character. Each now has an aria-label naming the row or section it acts on, matching the convention in tag_input.svelte, and the rename field has one of its own. The rename field also takes a 60 character cap: the name is JSON-stringified into the URL, and 60 is as much as a row heading can show anyway. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
hideEval now hides the cost section too and showAllHiddenEvals clears hidden rows as well, so the names had stopped describing the work. They become hideSection / showSection / hiddenSectionIds / showAllHidden / hiddenMenuItems, and the dropdown header reads "Show Section" rather than offering an eval named "Average Usage, Cost & Latency". The URL parameter names are unchanged so old links keep working. The section shape had three near-identical declarations; it now has one, in compare_metric_keys.ts, which both charts, the page's view helpers and their tests import. has_default_eval_config went with the copies - nothing on the chart side ever read it. The chart also took the user's display names by two routes at once. The page now filters first and labels afterwards, so the table gets the labelled rows and the chart gets the rows plus the metricLabels prop, which is the one route its own naming rule uses. Also: a dozen symbols nothing imports lose their export, an unreachable `|| []` goes, `|| 1` becomes `?? 1` so a legitimate zero survives, an `?.` on a non-nullable Eval goes, and a harness case is renamed to match what it checks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The redraw guard read `prompts || prompts === null`, which is false for an undefined prop, so a caller that ever set prompts to undefined froze the chart with no error. Naming prompts in the statement is what makes the redraw depend on it, but the comparison was never what did that work. chartData and prompts are now arguments to updateChart and are threaded into the legend and tooltip builders, so the dependency is the call itself and each formatter carries the prompt list of the draw that made it. A new case walks a missing prompt list through arriving and going away again, and fails on the old guard at the last step. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The subtitle promised only evaluation scores while the chart had grown cost, speed and token axes, which is the point of the feature. It now names all four kinds of axis. In the tooltip the same list of numbers was headed "Values" or "Lowest Scores" depending on how many there were; both now say "Scores", so the two states read as one list. escapeHtml also escapes an apostrophe. Every sink today is element text or a double-quoted attribute, so nothing rendered changes, but the function promises general-purpose escaping and a single-quoted attribute would have been open. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (13)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. WalkthroughThe compare page now supports hidden sections and metrics, custom metric labels, shared comparison types, configurable radar scaling, usage metrics, safer tooltips, and extracted radar chart data utilities. New tests cover helper functions, chart rendering, scoring, formatting, and empty states. ChangesComparison chart controls and radar data
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~60 minutes Change: Feature Merge Risk: ⚪ Minimal · up to The comparison-page changes are covered by focused utility and component tests, with no actionable merge-blocking risk identified. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Description checkResolution Complete the Contributor License Agreement section with the author’s GitHub username and confirmation that the agreement was read and accepted. Optionally mark the /lib test checklist item as checked with the stated not-applicable justification. Full details: Docstring CoverageExplanation Docstring coverage is 61.76% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 34 functions across 8 files. (5 skipped: 5 unsupported.)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. A rabbit hops through axes bright Comment |
📊 Coverage ReportOverall Coverage: 92% Diff: origin/main...HEADNo lines with coverage information in this diff.
|
scosman
left a comment
There was a problem hiding this comment.
Not sure I like "a dozen pencils" UI, or an edit UI that doesn't save the edit. We should chat through the UI design.
What does this PR do?
TLDR: The compare radar chart said nothing with one or two run configs, because every axis was scaled to the selection and a missing score was drawn as zero. This PR adds a Relative / Full Scale toggle, plots only the scores every plotted config has, turns the cost, latency and token rows into hideable axes, and lets the user hide or rename any row. It replaces #1628 with the same feature on top of the current
main, moves the chart maths into tested modules, and fixes three defects the new tests exposed. Targetsmain. There are no companion branches.Before this PR, the radar chart on the compare page scaled each axis to the highest value among the selected run configs. One run config sat on the outer ring of every axis. A score with no result was plotted at the centre, which reads the same as a score of zero. Only eval sections had a hide control. After this PR, the chart has a Relative / Full Scale toggle, plots only the scores every plotted run config has a result for and reports the omitted count under the title, charts cost, latency and tokens as relative axes that the user can hide from the table, and lets the user rename any row for display. The chart logic lives in pure modules with unit tests, and the option object that reaches ECharts is asserted by a test.
Implementation
hidden_metricsandmetric_labelsthat persist them.lib/utils/radar_chart_data.tsholds score normalisation, axis scaling, usage-axis inversion, the shared-key filter, label resolution and the tooltip trim. The component computes chart data once per update instead of twice. Its script drops from 629 lines to 374. The whole file is 430 lines; onmaintoday it is 417 lines without the feature.lib/utils/compare_metric_keys.tsholds the cost section id and the five usage keys. The page and the chart import them instead of repeating string literals.TaskOutputRatingTypeand ends withassertNever, so a new rating type fails the type check instead of returningnull. The lookup and the axis-max builder move intocompare_view.ts.setOption. 32 unit tests cover the data module.compare_view.test.tsgrows from 20 to 26 cases. This is the first unit test of an ECharts component in the repo.metric_labelsURL parameter, so a shared link could inject markup.undefinedafter it loaded. Before, the guard in the reactive statement stopped the redraw.compare_metric_keys.ts, and the chart'scomparisonFeaturesprop uses it: same shape, one field fewer.escape_htmlalso escapes the single quote.updateURL()passesnoScrollandkeepFocustogoto(), so a state change no longer jumps the page to the top. The first table column grows from 200 px to 240 px to fit the rename control.Before you merge
lower_is_betterorinformationalscores from the radar.EvalOutputScorehas nodirectionfield onmain.How the refactor was checked
The first commit reproduces the #1628 branch tip
ba08b006eonmain; the five changed files are byte-identical (git diffbetween the two is empty for those paths). A test harness was committed on top of it before any refactor. The refactor commits were then gated three ways:diff -ris empty).e9020ebab, after the three refactor commits and the first four fixes, all six states are pixel-identical to the baseline (0 pixels differ). At the final commit the differences are confined to the radar plot area in the three-config states (the resize for the labels) and to one subtitle line in the one- and two-config states, measured by the bounding box of the differing pixels.compare_viewtests pass unchanged through the refactor commits. The three fix commits each change exactly the assertions they say they change.Commits, in order:
d199675e9feat(compare): radar chart scale toggle, hideable usage axes, no false zeros, renamable rows — Compare radar chart: relative/full scale toggle, removable usage axes, no false zeros, rename axes #1628 squashed, byte-identical in its five files0271edcbftest(compare): characterise the compare radar chart option at baseline — 25 cases, no component change1559d98a2refactor(compare): extract the radar chart data layer into a tested module — golden identical611c863afrefactor(compare): share the cost section id and usage keys between page and chart — golden identical7d3351b6brefactor(compare): type the score range lookup and move it into compare_view — golden identical5c278e1b9fix(compare): escape score labels in the radar tooltip — golden identical (no fixture carries markup characters); one harness assertion changedf12bc6163fix(compare): keep run configs that share a name apart on the radar — golden changes only in the duplicate-names fixture; one harness assertion changede3712603ffix(compare): count omitted axes, not scores, under the radar title — DOM text onlye9020ebabfix(compare): name axes consistently in the radar empty state — DOM text only45b5bfbbbfix(compare): keep the radar card when hidden rows leave too few axes — DOM only, golden identicaldb31ec158fix(compare): make room for the radar axis labels — golden changes only inradar.center,radar.radius,axisName.width3ece384c1test(compare): assert the radar option shape instead of a tautology — tests only4bb7c8945fix(compare): label the hide and rename controls for assistive technology — golden identicala4498758drefactor(compare): name the hide state for what it hides — golden identicalbc245af22fix(compare): redraw the radar when the prompt list loads — golden identical; one new harness case96ac35cfafix(compare): update the radar copy for usage axes — golden changes only in tooltip header textScreenshots
Hosted on the
mike/compare-radar-legibility-v2-assetsbranch. Never merge that branch. The team can delete it after this PR closes.Three run configs, relative scale. The table has hide and rename controls on every row; the radar plots one eval axis and five usage axes:
Full scale. The eval axis uses its own 0 to 1 range, and the axis labels fit:
The same view on the #1628 branch, for comparison. The left labels are clipped:
A row renamed to "Escalation" through the rename control. The name changes on the table, the axis, and the tooltip:
Two usage rows hidden from the table. They leave the radar, and the "Show Section" menu lists them:
A single run config defaults to full scale with an area fill:
Related Issues
Replaces #1628. No tracked issue.
Contributor License Agreement
Left for the PR author to complete.
Checklists
uv run ./checks.sh --agent-modeexits 0 in a clean worktree;npm run checkreports 0 errors and the same 38 warnings asmain;misspellis not installed locally, so CI covers that check)🤖 Generated with Claude Code