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.json

Usage

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

  1. Events are sorted by time. The caller can pass them in any order; the component sorts ascending by time before plotting. Ties resolve in the caller's original array order. Time can be milliseconds, step indices, or frame counts.
  2. Three kinds, three colours. expand ticks read in the accent tone, shrink ticks in the error tone, record ticks in the success tone. A decorative legend below the lane mirrors the mapping so colour-blind readers always have a label.
  3. The lane stretches to span. The smallest time anchors the left edge; the largest anchors the right. With a single event, the tick lands at the centre.
  4. Tally chips count every kind. Below the lane, the tally reports shrink, expand, record, and the longest run of consecutive shrink events — the canonical "while-shrink" cluster signal.
  5. Empty state. With no events the lane renders an axis line and a "no events" caption; the tally chips render zeros.
  6. Reduced motion. When prefers-reduced-motion: reduce is set, the tick enter stagger collapses to instant.

Props

PropTypeDefaultDescription
eventsreadonly { time, kind }[]requiredEvent 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.
titleReactNodeOptional title rendered above the lane.
hideTallybooleanfalseHide the tally chip row.
laneHeightnumber36Ideal lane height in px.
formatTally(state) => ReactNodeOverride the tally line with a caller-formatted node.
compactbooleanfalseSmaller ticks, tighter lane.
classNamestringMerged onto the outer <div> via cn().

Accessibility

  • The root carries role="img" with aria-roledescription="shrink timing" and an aria-label summarising 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 from if to while, 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.