Training Progress Viz
A controlled-render dashboard for a live (or replayed) training run. The user
streams an array of { iter, loss, sample?, msPerIter? } entries plus an
optional latest snapshot; the component draws a 520×200 loss curve with raw
points, an EMA-smoothed accent trace, dashed semantic threshold flags
(random guessing, …, Shakespeare-like), and a pulsing marker on the latest
point while isTraining is true. Below the curve, the recent generated
samples appear in an auto-scrolling feed colored from warning (high loss) to
success (low loss). A progress bar, iter/ETA stats line, and
Stop / Resume / Train-more buttons sit at the bottom.
0/500
Installation
npx shadcn@latest add https://craftbits.dev/r/training-progress-viz.jsonUsage
import { TrainingProgressViz } from "@craft-bits/viz/training-progress-viz";
<TrainingProgressViz
history={history}
latest={latest}
totalIters={500}
isTraining={isTraining}
onStop={handleStop}
onResume={handleResume}
onTrainMore={handleTrainMore}
/>;Stream new entries as the trainer produces them — the component is a pure
function of history + latest + isTraining, so any shape of upstream
state (Redux, Zustand, plain useState) works.
Custom thresholds for a non-language task:
<TrainingProgressViz
history={history}
yMax={2}
thresholds={[
{ loss: 1.0, label: "baseline", color: "var(--cb-warning)" },
{ loss: 0.5, label: "target", color: "var(--cb-success)" },
]}
/>Hide the thresholds entirely:
<TrainingProgressViz history={history} thresholds={[]} />Understanding the component
- Loss curve. A 520×200 SVG. The x axis spans
0..max(totalIters, last iter)so the run always fills the canvas; the y axis spans0..yMax. Dashed grid lines at every integer loss double as y-axis labels. All theme-able color comes fromvar(--cb-…)tokens. - Threshold flags. Each entry in
thresholdsdraws a dashed horizontal line, a small dot at the right edge, and a label. Defaults reflect the four ML-training milestones (random guessing → Shakespeare-like). Pass[]to suppress or your own list to retarget for a non-language model. - Raw points + EMA trace. Every history entry renders as a low-opacity accent dot; the EMA-smoothed trace (default α = 0.95) is drawn over the top as a 2px accent line. Tune
emaAlphafor noisier vs flatter curves. - Latest point. When
latestis present, a 4px accent circle is drawn at its(iter, loss). WhileisTrainingis true the radius and opacity loop in a 1.2s breathe; otherwise it sits static. - Sample feed. Any history entry whose
sampleis a non-empty string lands in the feed (capped to the most recentsampleWindow, default 5). Each card's background tint maps loss towarning(high) →success(low) viacolor-mix. New samples land with a spring entrance and auto-scroll the feed to the bottom. - Progress + ETA. The bar fills proportional to
currentIter / totalIters. The stats line showsiter/total, last measuredms/iter, and a remaining-seconds estimate. - Controls. Stop is shown while training; Resume is shown when paused mid-run; Train-more is shown once
currentIter >= totalIters. Each is a real<button type="button">with focus-visible rings and adata-cb-viz-actionattribute for telemetry hooks. - Reduced motion. Under
prefers-reduced-motion: reduce, the latest-point breathe, list entrance, sample-feed scroll, and progress-bar tween all collapse to instant state changes.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
history | readonly TrainingProgressVizHistoryEntry[] | — | Completed iterations in order. |
latest | TrainingProgressVizLatest | — | Most recent point — pulses while isTraining. |
totalIters | number | 500 | Target iteration count; drives x-axis + progress bar. |
isTraining | boolean | false | If true, latest-point breathes and the Stop button is shown. |
thresholds | readonly TrainingProgressVizThreshold[] | four ML thresholds | Semantic loss flags drawn dashed across the curve. |
yMax | number | 5 | Y-axis ceiling. Values above clamp to the top edge. |
emaAlpha | number | 0.95 | EMA smoothing factor for the trace line. |
sampleWindow | number | 5 | Number of recent sample entries to keep on screen. |
transition | Transition | SPRINGS.snap | Override the progress-bar / latest-point tween. |
onStop | () => void | — | Fires when the user clicks Stop. |
onResume | () => void | — | Fires when the user clicks Resume. |
onTrainMore | () => void | — | Fires when the user clicks Train more. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The SVG canvas is
role="img"with anaria-labelsummarising the current iteration, target, and latest loss. - The progress bar is a real
role="progressbar"witharia-valuemin,aria-valuemax,aria-valuenow, and a label. - The sample-feed wrapper carries an
aria-labelso screen readers can find it as a landmark. - Stop / Resume / Train-more are real
<button type="button">controls with focus-visible rings, tap-down scale, anddata-cb-viz-actionattributes. - Colour is never the only signal — every threshold has a text label, every sample card has a numeric
loss n.nnlabel, and the progress bar reports its value to assistive tech. - Motion respects
prefers-reduced-motion: reduce— the latest-point breathe, sample-feed entrance, auto-scroll, and progress-bar tween collapse to instant.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/viz/TrainingProgressViz.tsx). The source dropped theSvgLabelprimitive and the lesson-onlylessonId/runCountprops, generalised the hard-coded four-flag threshold table into athresholdsprop with aTrainingProgressVizThresholdtype, promotedyMax,emaAlpha, andsampleWindowto typed knobs, remapped the palette from--color-{ink,accent,warn,success}-*tovar(--cb-*)semantic tokens, swapped the inline pulse spring forSPRINGS.snap, added a realrole="progressbar"to the bar, and routed the latest-point breathe + sample-feed motion throughprefers-reduced-motion.