Full Loop Stepper
A four-step, click-through visualizer of the complete training loop on the toy model y = w · x. The learner taps Forward to compute the prediction, Loss to measure error, Gradient to see the descent direction, and Update to slide w one step closer to the target. Each completed cycle drops a dot onto a loss-history strip and the narration recolours from observe → measuring → updating → insight.
With the defaults — x = 1, y_target = 2, lr = 0.1 — the optimal weight is 2.0. Starting at w = 0.5, the loop reaches the insight phase in around five clicks: the loss bar shrinks, the dot lands on the target marker, and the narration declares the loop done.
Click Forward to make a prediction with the current weight. The target function has slope 2.00, so the weight should converge there.
Installation
npx shadcn@latest add https://craftbits.dev/r/full-loop-stepper.jsonUsage
import { FullLoopStepper } from "@craft-bits/viz/full-loop-stepper";
<FullLoopStepper />Start further from the target so the user sees a longer walk:
<FullLoopStepper defaultWeight={0} learningRate={0.05} />Subscribe to cycle completions to drive an external chart:
<FullLoopStepper
onCycleComplete={(record) => {
/* read record.loss / record.wAfter */
}}
/>Understanding the component
- Four-step strip. Across the top, four rounded boxes — Forward, Loss, Gradient, Update — represent the steps of one cycle. The active step lights up in its semantic colour; completed steps fade to a muted state. A loop glyph appears after the strip once a cycle has completed.
- Arithmetic readout. A single mono-font line at the top updates per step to show the live computation — first
y = w · x, then the loss expression, then the full update equation. - Number line for w. A horizontal axis from
0to3. The dashed green vertical marker is the target weight. The accent-coloured dot isw— it animates with a smooth spring on update and pulses on forward. - Gradient arrow. During the gradient and update steps, a warning-coloured arrow appears below the number line pointing in the direction of the negative gradient. Its length is fixed; the magnitude lives on the readout.
- Loss bar. A horizontal bar below the number line fills proportionally to the current loss, capped at the visual ceiling. Its width is tweened via motion's
animate()on the loss step — no full re-render per frame. - Cycle history strip. Each completed cycle drops a small accent dot underneath the loss bar with the cycle's final loss labelled in mono. Dot opacity reflects how close that cycle's loss was to zero.
- Phase machine. The component classifies its own state into
observe/measuring/updating/insightfrom the cycle count and the most recent loss. The phase tints the narration background and triggers the celebration ring once the loop converges. - Reduced motion. Under
prefers-reduced-motion: reduce, every animation collapses to an instant state change so the lesson is fully usable without motion.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
defaultWeight | number | 0.5 | Initial value of w on the number line. |
targetWeight | number | 2.0 | Position of the dashed target marker. |
x | number | 1 | Training input — used to compute the prediction. |
yTarget | number | 2 | Training label — used to compute the loss. |
learningRate | number | 0.1 | Multiplied into the gradient on the update step. |
convergeThreshold | number | 0.1 | Loss below this enters the insight phase. |
insightCycles | number | 5 | Cycle count that also triggers the insight phase. |
transition | Transition | SPRINGS.smooth | Override the w-dot slide transition. |
onStep | (step, snapshot) => void | — | Fires after each step inside a cycle. |
onCycleComplete | (record) => void | — | Fires after a full cycle completes. |
onReset | () => void | — | Fires when the user clicks Reset. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The SVG canvas is
role="img"with anaria-labelsummarising the current weight, cycle count, and target. - The step and reset buttons are real
<button type="button">elements with focus-visible rings and a tap-down scale. - A live region below the buttons announces the current cycle, active step, weight, and most recent loss after every change.
- The narration paragraph is
aria-live="polite"and reads as plain prose — it is the canonical explanation for each phase. - Colour is never the only signal; the active step is named in the live region and the narration, and the loss value is read out alongside the bar.
- Motion respects
prefers-reduced-motion: reduce— every animation collapses to an instant state change.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/math/FullLoopStepper.tsx). The source was a tightly bundled lesson primitive — it consumedSvgLabelandChallengeBtnfrom the lesson chrome, hardcoded the toy model, and inlined its own ad-hoc spring names. The viz extract drops the lesson chrome, promotes every magic number to a typed prop, remaps the palette tovar(--cb-*)semantic tokens so consumer themes repaint freely, and re-keys all motion to the canonicalSPRINGSentries.