Skip to content

Compare radar chart: relative/full scale toggle, removable usage axes, no false zeros - #1628

Open
tawnymanticore wants to merge 4 commits into
mainfrom
mike/radar-chart-legibility
Open

Compare radar chart: relative/full scale toggle, removable usage axes, no false zeros#1628
tawnymanticore wants to merge 4 commits into
mainfrom
mike/radar-chart-legibility

Conversation

@tawnymanticore

@tawnymanticore tawnymanticore commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Makes the compare screen's radar chart readable with a small number of run configs. Before: every axis was scaled to the max across the selected configs, so a single run config sat on the outer ring of every axis and the chart said nothing — and a score it had no result for was plotted at 0, indistinguishable on a radar from scoring 0.

  • Relative / Full Scale toggle. Full Scale scales each axis to the score type's own range instead — 1 for pass/fail, 5 for 5-star — from the eval definitions the page already caches. Scores with no known range keep the relative max, so nothing is clipped. Defaults to Full Scale when only one run config has results.
  • Missing results are no longer drawn as zero. echarts maps a missing radar value to the center of the chart by design (radarLayout's getValueMissingPoint), so a gap can't be drawn. The chart now plots only the scores every selected run config has a result for, and reports the count of the rest under the title.
  • Cost, latency and token axes. All five usage metrics are axes, scored relative to the selected configs (bigger means less cost, less time, fewer tokens), so they stay relative in Full Scale mode too. Each is hidden with the same the eval sections already have, on its row in the cost table — tracked in the URL as hidden_usage, restorable from the "Hidden (n)" dropdown.
  • Layout. Larger plot, legend below the chart for one or two configs, area fill for a single series, tooltip confined to the chart and trimmed to the ten weakest scores.
  • One page-level fix. updateURL() now passes noScroll/keepFocus to goto(). Every state change on the compare page used to jump back to the top and drop focus. Not specific to the chart.

Not included: skipping scores whose direction is lower_is_better or informational, since a radar reads "further out is better". That needs EvalOutputScore.direction, which isn't on main yet.

Screenshot 2026-07-28 at 12 00 00 PM Above: Note key things here. there is an X beside the usage/latency metrics. There is a full scale vs relative button. and things that are empty are omitted from the plot

Related Issues

None — this wasn't tracked in an issue.

Contributor License Agreement

I, @, confirm that I have read and agree to the Contributors License Agreement.

Checklists

  • Tests have been run locally and passed
  • New tests have been added to any work in /lib

Web UI only, so the /lib item doesn't apply. From app/web_ui: npm run check (0 errors), npm run lint, npm run format_check and npx vitest run (1163 tests) all pass.

🤖 Generated with Claude Code

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The comparison page adds hidden usage-metric filtering and score-axis metadata. The radar chart supports relative/full-scale modes, shared-score filtering, usage axes, enhanced tooltips and legends, and distinct empty states.

Changes

Comparison and radar chart flow

Layer / File(s) Summary
Hidden usage filtering and persistence
app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/compare/+page.svelte
Usage metrics can be hidden individually, persisted through hidden_usage, filtered from comparison features, and restored through the Hidden menu.
Score-axis metadata and chart wiring
app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/compare/+page.svelte
Supported score types are mapped to absolute maxima, evaluation data produces JSON-keyed axis metadata, and scoreAxisMaxes is passed to the radar chart.
Radar scaling, usage axes, and empty states
app/web_ui/src/lib/components/compare_radar_chart.svelte, app/web_ui/src/lib/components/chart_no_data.svelte
The chart computes shared score and usage axes, supports selectable scaling, updates tooltips and legends, and renders configurable messages when data is insufficient.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ComparePage
  participant URL
  participant CompareRadarChart
  participant ECharts
  participant ChartNoData
  ComparePage->>URL: read and persist hidden usage metrics
  ComparePage->>CompareRadarChart: pass visible features and scoreAxisMaxes
  CompareRadarChart->>CompareRadarChart: generate shared scaled radar data
  CompareRadarChart->>ECharts: render chart, legends, and tooltips
  CompareRadarChart->>ChartNoData: render contextual empty state when needed
Loading

Possibly related PRs

  • Kiln-AI/Kiln#1411: Both changes modify compare-page filtering and URL-driven hidden evaluation state affecting chart inputs.

Suggested reviewers: sfierro

Poem

A bunny found axes, wide and precise,
Scores hopped neatly through data like mice.
“Relative!” cried one, “Full scale!” cried two,
Tooltips grew whiskers and legends did too.
Hidden metrics vanished from view.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main compare radar chart changes and is concise and specific.
Description check ✅ Passed The description follows the template and includes the goal, related issues, CLA, and checklist items with test details.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mike/radar-chart-legibility

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

📊 Coverage Report

Overall Coverage: 92%

Diff: origin/main...HEAD

No lines with coverage information in this diff.


@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
app/web_ui/src/lib/components/compare_radar_chart.svelte (2)

472-482: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

scoreAxisMaxes && doesn't gate on readiness.

An empty {} is truthy, so this only serves as a reactive dependency, not a "maxes are loaded" guard. Effect: in Full Scale mode the chart can draw with 0–1 fallback axes while eval_data_cache is still populating, then rescale. Self-recovering, but if the intent was to wait, gate on Object.keys(scoreAxisMaxes).length > 0 (or drop the misleading comment).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web_ui/src/lib/components/compare_radar_chart.svelte` around lines 472 -
482, Update the reactive updateChart guard so scoreAxisMaxes is considered ready
only when it contains at least one key, using Object.keys(scoreAxisMaxes).length
> 0. Preserve scoreAxisMaxes as a reactive dependency and keep the other
readiness checks unchanged.

251-354: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive chart data once instead of recomputing it in two places.

generateChartData() is invoked from both the chartSummary reactive block and updateChart(), duplicating the full O(keys × configs) walk (each getModelValueRaw also does a .find over eval results) and leaving two independent computations that must stay in sync. A single reactive $: chartData = generateChartData() consumed by both keeps them consistent and halves the work.

Also note the max loop at Line 312 iterates selectedRunConfigIds rather than plottedConfigs; harmless today (excluded configs are all-null by construction) but easy to drift — iterating plottedConfigs matches the series being drawn.

♻️ Sketch
+  $: chartData =
+    dataKeys && selectedRunConfigIds && axisScaleMode && scoreAxisMaxes
+      ? generateChartData()
+      : null
   $: chartSummary = (() => {
-    if (!dataKeys || dataKeys.length === 0 || !selectedRunConfigIds) {
+    if (!chartData) {
       return { hasData: false, noResultKeyCount: 0 }
     }
-    const { indicators, series, noResultKeyCount } = generateChartData()
+    const { indicators, series, noResultKeyCount } = chartData
     return {
       hasData: indicators.length > 0 && series.length > 0,
       noResultKeyCount,
     }
   })()

and in updateChart():

-    const { indicators, series, legend, axisMaxes, keys } = generateChartData()
+    if (!chartData) return
+    const { indicators, series, legend, axisMaxes, keys } = chartData
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web_ui/src/lib/components/compare_radar_chart.svelte` around lines 251 -
354, Derive the result of generateChartData() once in a reactive chartData
assignment, then have both chartSummary and updateChart consume that shared
object instead of invoking generateChartData independently. In
generateChartData, update the axis maximum loop to iterate plottedConfigs rather
than selectedRunConfigIds, keeping max calculations aligned with the series that
are rendered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/web_ui/src/lib/components/compare_radar_chart.svelte`:
- Around line 203-231: Escape all user- or project-authored strings before
interpolating them into the ECharts tooltip HTML. Add or reuse a shared
escapeHtml helper in the tooltip-building logic, applying it to score.label from
getKeyLabel(key) and the model/prompt names interpolated in the surrounding
HTML; preserve numeric formatting and existing tooltip behavior.

---

Nitpick comments:
In `@app/web_ui/src/lib/components/compare_radar_chart.svelte`:
- Around line 472-482: Update the reactive updateChart guard so scoreAxisMaxes
is considered ready only when it contains at least one key, using
Object.keys(scoreAxisMaxes).length > 0. Preserve scoreAxisMaxes as a reactive
dependency and keep the other readiness checks unchanged.
- Around line 251-354: Derive the result of generateChartData() once in a
reactive chartData assignment, then have both chartSummary and updateChart
consume that shared object instead of invoking generateChartData independently.
In generateChartData, update the axis maximum loop to iterate plottedConfigs
rather than selectedRunConfigIds, keeping max calculations aligned with the
series that are rendered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 84913d9d-3997-418d-ac9b-60bc9bb5ef7a

📥 Commits

Reviewing files that changed from the base of the PR and between 04a5e39 and 2676d84.

📒 Files selected for processing (3)
  • app/web_ui/src/lib/components/chart_no_data.svelte
  • app/web_ui/src/lib/components/compare_radar_chart.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/compare/+page.svelte

Comment thread app/web_ui/src/lib/components/compare_radar_chart.svelte Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/web_ui/src/lib/components/compare_radar_chart.svelte (2)

604-618: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

run_configs is missing from the reactive dependency list.

Series names, legend entries and plottedConfigs all derive from run_configs, but it isn't referenced in this guard, so a later-arriving run_configs update won't redraw unless comparisonFeatures happens to change at the same time.

♻️ Add the dependency
   $: if (
     chartInstance &&
     comparisonFeatures &&
     selectedRunConfigIds &&
+    run_configs &&
     axisScaleMode &&
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web_ui/src/lib/components/compare_radar_chart.svelte` around lines 604 -
618, Add run_configs to the reactive guard surrounding updateChart() in the
chart component, alongside the other inputs that control rendering. Ensure the
guard waits for run_configs to be available while preserving support for its
explicit null state, so later run_configs updates trigger a redraw and refresh
series names, legends, and plottedConfigs.

386-402: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Align the radar-axis threshold with the final axis set.

generateChartData() counts usage axes in keys, but the chart is only rendered through {#if dataKeys.length >= MIN_RADAR_AXES}. That can hide valid charts with enough shared eval+usage axes; use the same axis set for both gates (ideally keys.length with the chart container guard).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web_ui/src/lib/components/compare_radar_chart.svelte` around lines 386 -
402, Align the chart-rendering guard with the final filtered axis set produced
by generateChartData(): use keys.length, or the returned equivalent, for the
MIN_RADAR_AXES check instead of dataKeys.length. Ensure charts with enough
shared evaluation and usage axes render consistently with the existing
generateChartData threshold.
🧹 Nitpick comments (1)
app/web_ui/src/lib/components/compare_radar_chart.svelte (1)

470-505: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

generateChartData() runs twice per update.

chartSummary computes it reactively and updateChart() recomputes it immediately after. Each pass does keys × configs getModelValueRaw calls, and each of those does a linear find over eval_results. Storing the full result on chartSummary and having updateChart() consume it removes the duplicate work and guarantees both paths agree on the same snapshot.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web_ui/src/lib/components/compare_radar_chart.svelte` around lines 470 -
505, Store the full generateChartData() result in the reactive chartSummary
computation, including indicators, series, legend, axisMaxes, and keys, while
preserving its existing summary fields. Update updateChart() to consume that
stored result instead of calling generateChartData() again, ensuring the
empty-data path still clears the chart and both paths use the same snapshot.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/web_ui/src/routes/`(app)/specs/[project_id]/[task_id]/compare/+page.svelte:
- Around line 460-486: The hidden-evals dropdown styling relies on positional
selectors that incorrectly prefix the inserted “Show Metric” header and misalign
dividers. Update the `hiddenEvalsMenuItems`/`FloatingMenu` item rendering and
the `.hidden-evals-dropdown` styles to identify headers or divider items by an
explicit type/class instead of `:nth-child` positions, preserving the intended
“+” prefix only for applicable menu entries.
- Around line 376-412: Update filterVisibleFeatures so the kiln_cost_section is
removed when hiddenUsageKeys filters out all of its items, preventing the empty
cost section from reaching the generic no-scores error state. Preserve the
existing behavior for partially hidden cost sections, hidden evaluation
sections, and unchanged feature lists.

---

Outside diff comments:
In `@app/web_ui/src/lib/components/compare_radar_chart.svelte`:
- Around line 604-618: Add run_configs to the reactive guard surrounding
updateChart() in the chart component, alongside the other inputs that control
rendering. Ensure the guard waits for run_configs to be available while
preserving support for its explicit null state, so later run_configs updates
trigger a redraw and refresh series names, legends, and plottedConfigs.
- Around line 386-402: Align the chart-rendering guard with the final filtered
axis set produced by generateChartData(): use keys.length, or the returned
equivalent, for the MIN_RADAR_AXES check instead of dataKeys.length. Ensure
charts with enough shared evaluation and usage axes render consistently with the
existing generateChartData threshold.

---

Nitpick comments:
In `@app/web_ui/src/lib/components/compare_radar_chart.svelte`:
- Around line 470-505: Store the full generateChartData() result in the reactive
chartSummary computation, including indicators, series, legend, axisMaxes, and
keys, while preserving its existing summary fields. Update updateChart() to
consume that stored result instead of calling generateChartData() again,
ensuring the empty-data path still clears the chart and both paths use the same
snapshot.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 40678c33-68f3-42e7-9ef0-4c9048020471

📥 Commits

Reviewing files that changed from the base of the PR and between 03ea083 and 6d4e706.

📒 Files selected for processing (2)
  • app/web_ui/src/lib/components/compare_radar_chart.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/compare/+page.svelte

@tawnymanticore
tawnymanticore force-pushed the mike/radar-chart-legibility branch from 6d4e706 to 619013f Compare July 28, 2026 15:41

@tawnymanticore tawnymanticore left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few things I hit reading this through.

Comment thread app/web_ui/src/lib/components/compare_radar_chart.svelte Outdated
section.eval_id === "kiln_cost_section" && hiddenUsage.length > 0
? {
...section,
items: section.items.filter(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hiding all five cost rows leaves this section with items: []. The table's {#if section.items.length == 0} branch then falls through to Unknown issue - no scores found, because the cost section's has_default_eval_config is undefined rather than false. Easy to hit now that each row has its own .

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0de4b1a0e: the empty-section branch is now skipped for kiln_cost_section, so an emptied cost section renders as just its header.

Comment thread app/web_ui/src/lib/components/compare_radar_chart.svelte Outdated
Comment thread app/web_ui/src/lib/components/compare_radar_chart.svelte Outdated
tawnymanticore and others added 4 commits July 28, 2026 11:55
Every radar axis was scaled to the highest value observed across the
selected run configs. With a single run config selected that config is
the max on every axis, so it sat on the outer ring everywhere and the
chart carried no information at all.

"Full Scale" scales each axis to the score's own range instead: 1 for
pass/fail and pass/fail/critical, 5 for 5-star, taken from the eval
definitions the compare page already caches. Scores with no known range
(unbounded custom scores) and any data that exceeds its expected range
keep the relative max, so nothing is clipped. The chart defaults to Full
Scale when only one run config has results, since there is nothing to
compare against; an explicit click on either mode wins from then on.

Also gives the plot room to be read at that scale. For one or two run
configs the radar is centered with the legend underneath, instead of
always reserving the right 40% of the card for a legend column, and the
container is taller. Long axis names wrap rather than colliding with
their neighbours, a single series is drawn filled (one polygon reads far
better filled, several read as mud), and the tooltip is confined to the
chart instead of covering the table above.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cost and latency were already radar axes; the three token counts were
not on the chart at all. All five are now axes, scored by metricToScore
as a position within the selected run configs on a shared 0-100 axis.
They are named for the direction that's better - Cost Efficiency, Speed,
Token Efficiency, Input Token Efficiency, Output Token Efficiency - since
a bigger value means less cost, less time, fewer tokens. Because they
score a config against the others rather than against a fixed range, they
stay relative in Full Scale mode too, which the scale tooltip explains.

Input and output tokens sum to total, so drawing all three at once
repeats the same spoke three times. Rather than invent a control for
that, each usage axis is dropped with the same x the eval sections
already have, moved onto the rows of the cost section where the numbers
are. Hidden rows are tracked in the URL as hidden_usage and restorable
from the "Hidden (n)" dropdown under a "Show Metric" group, alongside
hidden evals. The chart's usage axes are just whatever cost rows survive,
so hiding a row removes its axis. An emptied cost section renders as just
its header: the table's empty-section branch would otherwise fall through
to "Unknown issue - no scores found", since the cost section's
has_default_eval_config is undefined rather than false.

Splitting the usage keys out of dataKeys also means the card's "at least
three axes" gate has to count them, or a task with one or two eval scores
would hide a chart that has six or seven axes to draw.

The tooltip keeps the raw mean cost, latency and total tokens, since the
axes plot a relative score rather than the quantity itself - and for the
same reason the usage axes are left out of the tooltip's weakest-scores
list, where a 0-100 relative score was being compared against pass rates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A run config with no result for a score was plotted at 0, which on the
radar is indistinguishable from actually scoring 0 - the shape said
"failed everything" while the tooltip said "N/A". On one of my run
configs, 26 of 28 scores are null and none are genuinely zero, and the
chart drew all 26 at the center.

Plotting null instead doesn't help: echarts maps a missing radar value to
the center of the chart by design (radarLayout's getValueMissingPoint),
so there's no way to draw a gap. The chart now plots only the scores that
every selected run config has a result for, and reports the count of the
rest under the title. A run config with no results at all is left out
rather than emptying every axis, and axis maxima are taken over those
plotted configs rather than every selected one. Below three shared scores
there's no shape to read, so ChartNoData takes a title and message and
says which of the two reasons it is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
updateURL() records existing UI state in the query string, but goto()
scrolls to the top by default, so hiding a row, hiding an eval, adding a
column or picking a run config all threw the page back to the top - and
hiding a metric near the bottom of a long table meant scrolling all the
way back down to hide the next one. It also dropped focus from the button
that was just clicked.

noScroll and keepFocus. Measured on the real page: hiding a row from a
scroll position of 3219px left it at 3219px, where it previously went
to 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tawnymanticore
tawnymanticore force-pushed the mike/radar-chart-legibility branch from 619013f to 73c2dd6 Compare July 28, 2026 16:15
@tawnymanticore tawnymanticore changed the title Make the compare radar chart readable with few run configs Compare radar chart: relative/full scale toggle, removable usage axes, no false zeros Jul 28, 2026
@scosman

scosman commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

We need cost and speed on the chart. It's essential to every pitch/demo we have done. Up for improving it, but the values have meaning even if relative

@tawnymanticore

Copy link
Copy Markdown
Collaborator Author

We need cost and speed on the chart. It's essential to every pitch/demo we have done. Up for improving it, but the values have meaning even if relative

@scosman Right I agree, this PR just unifies all eval rows to make any X'able in the Compare Run Config screen (today all are X'able except for speed/tokens/latency)... the user may want to make the radar chart demo-able and may need to modify any of it. Also if there is just 1 run config shown on the radar chart, then speed/cost/tokens don't make any sense on the Radar Chart because it is hard coded to be at 50 out of 100, its a relative measure so requires >1 run config to make any sense since we don't have an upper bound on Low Cost or High Cost, or Low Latency or High Latency etc unlike the rest of the radar chart axes which are bounded by the eval scores (fail --> pass, 0-5 stars)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants