ShrinkTiming
A pure playback viz for the timing of sliding-window shrink operations. The caller hands in an array of events, each a { time, kind } pair where kind is one of expand, shrink, or record. The component draws a horizontal lane with one tick per event, coloured by kind, positioned by time (the smallest time anchors the left edge; the largest the right). Below the lane, a live tally reports how many shrinks fired, how many expands fired, and the longest run of consecutive shrinks — the "while-shrink" cluster the source lesson was built to surface.
Pure visualization primitive — no protocol logic, no narration, no scoring. Drop into any sliding-window narrative — if-shrink vs while-shrink, minimum-length-subarray, longest-substring-with-K — and layer narration, predictions, or score around it.
Installation
npx shadcn@latest add https://craftbits.dev/r/shrink-timing.jsonUsage
import { ShrinkTiming } from "@craft-bits/core";
<ShrinkTiming
events={[
{ time: 0, kind: "expand" },
{ time: 1, kind: "expand" },
{ time: 2, kind: "record" },
{ time: 3, kind: "shrink" },
{ time: 4, kind: "shrink" },
]}
/>Frozen window, chrome stripped — surface a static timing trace as a thumbnail:
<ShrinkTiming events={events} hideTally compact />Custom tally — replace the default chips with a caller-formatted node:
<ShrinkTiming
events={events}
formatTally={({ shrink, longestShrinkRun }) =>
`shrinks: ${shrink}, run: ${longestShrinkRun}`
}
/>Understanding the component
- Events are sorted by time. The caller can pass them in any order; the component sorts ascending by
timebefore plotting. Ties resolve in the caller's original array order. Time can be milliseconds, step indices, or frame counts. - Three kinds, three colours.
expandticks read in the accent tone,shrinkticks in the error tone,recordticks in the success tone. A decorative legend below the lane mirrors the mapping so colour-blind readers always have a label. - The lane stretches to span. The smallest
timeanchors the left edge; the largest anchors the right. With a single event, the tick lands at the centre. - Tally chips count every kind. Below the lane, the tally reports
shrink,expand,record, and the longest run of consecutiveshrinkevents — the canonical "while-shrink" cluster signal. - Empty state. With no events the lane renders an axis line and a "no events" caption; the tally chips render zeros.
- Reduced motion. When
prefers-reduced-motion: reduceis set, the tick enter stagger collapses to instant.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
events | readonly { time, kind }[] | required | Event log. Each kind is "expand", "shrink", or "record". Sorted by time. |
tone | "default" | "accent" | "success" | "warning" | "error" | "accent" | Semantic tone for the axis and longest-run chip. |
title | ReactNode | — | Optional title rendered above the lane. |
hideTally | boolean | false | Hide the tally chip row. |
laneHeight | number | 36 | Ideal lane height in px. |
formatTally | (state) => ReactNode | — | Override the tally line with a caller-formatted node. |
compact | boolean | false | Smaller ticks, tighter lane. |
className | string | — | Merged onto the outer <div> via cn(). |
Accessibility
- The root carries
role="img"witharia-roledescription="shrink timing"and anaria-labelsummarising the event count, time span, and tally on every render. - The tally row is
aria-live="polite"so screen-reader users hear the chip values update on each event-log change without focus stealing. - The tick lane and legend are decorative (
aria-hidden="true") — the canonical state lives in the root summary instead of being read N times. - Event kind is conveyed by colour, position, and the labelled legend — never colour alone.
- Motion respects
prefers-reduced-motion: reduce.
Credits
- Extracted from:
algoflashcards(src/lessons/primitives/observation/ShrinkTiming.tsx). The source was a seven-phase if-shrink-versus-while-shrink lesson bundling a problem-statement card, a step-by-step if-shrink simulation with per-step prediction gates and distractor feedback, an amber "verdict" comparison card, a parallel while-shrink simulation, a MagicMoveBlock code morph fromiftowhile, a rule-discovery quiz, scoring rollups, audio cues, and a mood-driven container palette. The library extract strips every lesson concern — phases, predictions, audio, scoring, code morph, narration, the array visualization — and keeps only the timing primitive: the event log, the colour-coded tick lane, and the tally of expands, shrinks, records, and the longest shrink run.