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

This 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

  1. Parent owns the step. Playback timing lives outside the panel — a useEffect interval, a scrubber, a reducer dispatched on prediction. The panel is a pure projection of currentStep over steps, which keeps it composable with any pacing model and side-steps the "who owns play/pause" question that plagues bundled players.
  2. One step = one line + caption + vars. Each LiveCodePanelStep carries an optional line, an annotation, and a stateVars array. The panel forwards line to the underlying CodeTrace as activeLine, surfaces annotation as an inline callout under that line, and renders stateVars as a chip row beneath the code.
  3. 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.
  4. Deferred entrance. Set codeDelay to a positive number to hide the panel until currentStep reaches it. The mount animation slides in from the right on SPRINGS.smooth. Use it for "interaction first, code reveal later" choreography where the algorithm visual lands before the code does.
  5. 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 on SPRINGS.snap. Reduced motion collapses both the entrance and the crossfade to zero duration.

Variants

  • Code-only timeline — omit stateVars on every step to render just the active-line highlight + caption.
  • Variables-only — omit line and annotation to render a "state inspector" panel that tracks vars without moving a highlight.
  • Always-on — leave codeDelay at the default so the panel is mounted from step 0.

Props

PropTypeDefaultDescription
codestringrequiredFull source code rendered in the panel.
lang'tsx' | 'ts' | 'jsx' | 'js' | 'py' | 'bash''tsx'Shiki language id forwarded to CodeTrace.
stepsreadonly LiveCodePanelStep[]requiredPlayback timeline. One entry per step.
currentStepnumberrequiredIndex into steps. Clamped to the valid range.
codeDelaynumber0Hide the panel until currentStep reaches this value.
labelReactNode'Code'Small uppercase label above the panel.
showLineNumbersbooleantrueShow the line-number gutter on the underlying CodeTrace.
classNamestringMerged onto the root <motion.div>.

LiveCodePanelStep

FieldTypeDescription
linenumber | null1-indexed line to highlight. null clears the highlight.
annotationReactNodeCaption rendered beneath the active line.
stateVarsreadonly LiveCodePanelStateVar[]Chip row of variable readouts under the code.

LiveCodePanelStateVar

FieldTypeDescription
labelstringShort identifier (e.g. lo, hi, node).
valuestring | number | readonly (string | number)[]Current value. Arrays render comma-joined inside brackets.
colorstringOptional 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 in aria-live="polite", so screen readers receive the active line as currentStep advances.
  • 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-track trackHex and a project-local RichText for annotations; the craft-bits extract drops both — annotations are now generic ReactNode, and active-state colour flows through var(--cb-accent) so the panel re-skins with any theme override.