Live Code Panel
A step-synced code surface for choreographed algorithm playback. Pass a steps array (one entry per playback step) and the current step index — the panel maps each step to an active line in the underlying CodeTrace, a caption beneath it, and an optional chip row of state variables (queues, pointers, accumulators).
Code
function bfs(graph, start) { const queue = [start];Seed the queue with the start node.
const seen = new Set([start]); while (queue.length) { const node = queue.shift(); for (const next of graph[node]) { if (seen.has(next)) continue; seen.add(next); queue.push(next); } } return seen;}queue = [A]
seen = [A]
Installation
npx shadcn@latest add https://craftbits.dev/r/live-code-panel.jsonThis pulls code-trace as a transitive registry dependency.
Usage
import { LiveCodePanel } from "@craft-bits/core";
const steps = [
{ line: 1, annotation: "Init queue" },
{ line: 3, annotation: "Enter loop" },
{ line: 4, annotation: "Dequeue node" },
{ line: 6, annotation: "Check neighbor" },
];
<LiveCodePanel
code={BFS_CODE}
lang="ts"
steps={steps}
currentStep={stepIdx}
/>With per-step state-var readouts and a deferred entrance:
<LiveCodePanel
code={BFS_CODE}
lang="ts"
steps={[
{ line: 2, annotation: "seed queue", stateVars: [{ label: "queue", value: ["A"] }] },
{ line: 5, annotation: "dequeue", stateVars: [{ label: "node", value: "A" }] },
]}
currentStep={stepIdx}
codeDelay={2}
label="Trace"
/>Understanding the component
- Parent owns the step. Playback timing lives outside the panel — a
useEffectinterval, a scrubber, a reducer dispatched on prediction. The panel is a pure projection ofcurrentStepoversteps, which keeps it composable with any pacing model and side-steps the "who owns play/pause" question that plagues bundled players. - One step = one line + caption + vars. Each
LiveCodePanelStepcarries an optionalline, anannotation, and astateVarsarray. The panel forwardslineto the underlyingCodeTraceasactiveLine, surfacesannotationas an inline callout under that line, and rendersstateVarsas a chip row beneath the code. - Index clamping is built in. Pass any
currentStep— the panel clamps to the valid range before indexing. Overshooting the end of a playback just pins to the last step instead of throwing. - Deferred entrance. Set
codeDelayto a positive number to hide the panel untilcurrentStepreaches it. The mount animation slides in from the right onSPRINGS.smooth. Use it for "interaction first, code reveal later" choreography where the algorithm visual lands before the code does. - State-vars crossfade between steps. The chip row is keyed by
currentStep, so each step swap fades the old readout out and the new one in onSPRINGS.snap. Reduced motion collapses both the entrance and the crossfade to zero duration.
Variants
- Code-only timeline — omit
stateVarson every step to render just the active-line highlight + caption. - Variables-only — omit
lineandannotationto render a "state inspector" panel that tracks vars without moving a highlight. - Always-on — leave
codeDelayat the default so the panel is mounted from step 0.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
code | string | required | Full source code rendered in the panel. |
lang | 'tsx' | 'ts' | 'jsx' | 'js' | 'py' | 'bash' | 'tsx' | Shiki language id forwarded to CodeTrace. |
steps | readonly LiveCodePanelStep[] | required | Playback timeline. One entry per step. |
currentStep | number | required | Index into steps. Clamped to the valid range. |
codeDelay | number | 0 | Hide the panel until currentStep reaches this value. |
label | ReactNode | 'Code' | Small uppercase label above the panel. |
showLineNumbers | boolean | true | Show the line-number gutter on the underlying CodeTrace. |
className | string | — | Merged onto the root <motion.div>. |
LiveCodePanelStep
| Field | Type | Description |
|---|---|---|
line | number | null | 1-indexed line to highlight. null clears the highlight. |
annotation | ReactNode | Caption rendered beneath the active line. |
stateVars | readonly LiveCodePanelStateVar[] | Chip row of variable readouts under the code. |
LiveCodePanelStateVar
| Field | Type | Description |
|---|---|---|
label | string | Short identifier (e.g. lo, hi, node). |
value | string | number | readonly (string | number)[] | Current value. Arrays render comma-joined inside brackets. |
color | string | Optional CSS color for the value. Defaults to var(--cb-accent). |
Accessibility
- Active-line announcements come for free from the underlying
CodeTrace— its code region is wrapped inaria-live="polite", so screen readers receive the active line ascurrentStepadvances. - The state-var chip row is plain text, not a live region — readers shouldn't be interrupted on every step change. If you want the vars announced, wrap the panel in your own labelled region.
- The entrance slide is purely cosmetic. Under
prefers-reduced-motion: reduce, both the slide-in and the state-var crossfade collapse to zero duration. - The label slot is rendered as plain text, not a heading — drop a real heading above the panel if you need landmark navigation.
Credits
- Extracted from:
algoflashcards(src/lessons/primitives/viz/LiveCodePanel.tsx). The source pulled a per-tracktrackHexand a project-localRichTextfor annotations; the craft-bits extract drops both — annotations are now genericReactNode, and active-state colour flows throughvar(--cb-accent)so the panel re-skins with any theme override.