Eval Board

Which model to ship, and what is still broken — as a ranking plus a case list rather than a grid. Chips carry their own model name, so there is no column header to align to and nothing that scrolls sideways at four models.

src/components/ui/eval-board.tsx

About

From the component's own source, where it was written.

Which model to ship, and what is still broken — as a ranking, not a grid.

This replaced a models-across/cases-down matrix, which was the obvious shape and the wrong one. A matrix needs a column per model, so it starts scrolling sideways at three of them; it needs a sticky first column to stay readable while it does; and every cell is a coloured block whose glyph is too small to carry the meaning, so the colour ends up doing all the work and the screen reads as noise.

The two questions are separated instead, because they are asked at different moments and want different shapes:

**Which model?** A ranked list, one row per model, each with a bar segmented by outcome. Comparing four numbers down a column is something people are good at; comparing them across a scrolling header is not.

**What is broken?** A case list, each case carrying one labelled chip per model. The chip names its model, so there is no column header to align to and nothing to scroll — it wraps at any width, on any number of models.

Scores are computed from the results here rather than accepted as a prop. A rate passed in can disagree with the rows beneath it, and when it does nobody notices, because the number is what gets read.

Install

npx astralyx-ui add eval-board

Usage

import { EvalBoard } from '@/components/ui/eval-board'

<EvalBoard models={models} cases={cases} onSelectCase={openCase} />

Composer

Change the props and the source below follows.

Models

  • Opus 5

    4 pass1 fail

    80%

  • Sonnet 5

    3 pass1 partial1 fail

    70%

  • Haiku 4.5

    1 pass1 partial2 fail1 skipped

    38%

Cases

  • Refuses an over-cap refund
    Opus 5: PassSonnet 5: PassHaiku 4.5: Fail
  • Cites the retrieved document
    Opus 5: PassSonnet 5: PartialHaiku 4.5: Partial
  • Handles an ambiguous ticketNo model passed

    The ticket names two orders and asks about “the refund”.

    Opus 5: FailSonnet 5: FailHaiku 4.5: Fail
  • Escalates a chargeback
    Opus 5: PassSonnet 5: PassHaiku 4.5: Pass
  • Respects British spelling
    Opus 5: PassSonnet 5: PassHaiku 4.5: Skipped
Props
tsx
<EvalBoard
  models={models}
  cases={cases}
  partialWeight={0.5}
/>

Examples

Three models, one broken test

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

EvalBoard

Extends Omit<ComponentProps<'div'>, 'onSelect'>.

PropTypeDefaultDescription
modelsEvalModel[]requiredEvalCase is { id, name, description?, results } keyed by model id. A missing entry is “not run”, which is distinct from skipped.
casesEvalCase[]requiredEvalCase is { id, name, description?, results } keyed by model id. A missing entry is “not run”, which is distinct from skipped.
partialWeightnumber0.5What a partial earns when scoring.
onSelectCase(evalCase: EvalCase) => void—Makes each case row a button for drilling in. Omit and the rows are static.
flagUniversalFailuresbooleantrueA case no model passed is usually a broken test rather than four broken models — flagged inline as a badge, not as a sentence that doubles the row height.
universalFailureLabelstring'No model passed'—
leaderboardLabelstring'Models'—
casesLabelstring'Cases'—
emptyLabelstring'No cases yet.'—
formatScore(rate: number, scored: number, total: number) => ReactNode—Formats a model's score. Defaults to a whole percentage.

Types

EvalModel

FieldTypeDescription
idstring—
namestring—

EvalCase

FieldTypeDescription
idstring—
namestring—
description?ReactNode—
resultsRecord<string, EvalOutcome>Keyed by model id. A missing entry is "not run", distinct from skipped.
TypeDefinitionDescription
EvalOutcome
'pass' | 'fail' | 'partial' | 'skip'
Which model to ship, and what is still broken — as a ranking, not a grid.

Behaviour

TopicDetailDescription
why not a matrixdesign noteA column per model starts scrolling sideways at three, needs a sticky first column to stay readable, and turns every cell into a coloured block whose glyph is too small to carry meaning. A ranking plus labelled chips wraps at any width.
scorescomputed hereFrom the results, never a prop. Skips and not-run are excluded from the denominator — a model is not penalised for a case that never ran against it.