Bug Hunt Fix Phase

The second half of a BugHunt. After the learner has located the bug, this presents a small set of candidate fixes and reveals the correct one with per-option explanations.

Preview

Which fix is correct?

Customize
Options

Installation

npx shadcn@latest add https://craftbits.dev/r/bug-hunt-fix-phase.json

Usage

import { BugHuntFixPhase } from "@craft-bits/core";
 
<BugHuntFixPhase
  options={[
    {
      id: "lt-equals",
      code: "for (let i = 0; i < nums.length; i++)",
      correct: true,
      explanation: "Stops the loop one index earlier.",
    },
    {
      id: "minus-one",
      code: "for (let i = 0; i <= nums.length - 1; i++)",
      correct: false,
      explanation: "Equivalent, but harder to read.",
    },
  ]}
  explanation="The loop guard is the bug — visit every index exactly once."
/>

Controlled — drive selectedId and revealed from a parent reducer:

<BugHuntFixPhase
  options={options}
  selectedId={state.selectedFixId}
  revealed={state.fixRevealed}
  onSelect={(opt) => dispatch({ type: "select-fix", id: opt.id })}
/>

Anatomy

  • Question label — a small centered caption above the options (defaults to "Which fix is correct?").
  • Option stack — each option is a radio button in a radiogroup. Code snippets render in a small monospace tile; plain options render the label slot.
  • Reveal — once a pick is made (or revealed flips to true in controlled mode), correct options paint green, the picked-wrong option paints red and shakes, untouched options dim.
  • Phase explanation — a tinted card below the options with an accent left-border. Color via accentColor; defaults to var(--cb-accent).
  • Per-option caption — the selected option's own explanation renders as a small caption beneath the phase explanation, ideal for distractor-specific feedback.

Props

PropTypeDefaultDescription
optionsreadonly BugHuntFixOption[]Candidate fixes. Exactly one should be correct: true.
selectedIdstring | nullControlled selection. Omit for uncontrolled.
defaultSelectedIdstring | nullnullInitial selection when uncontrolled.
onSelect(option) => voidFires whenever a selection is made.
revealedbooleanControlled reveal state. Omit to flip on first pick.
onReveal(option) => voidFires on the first pick when uncontrolled.
questionReactNode"Which fix is correct?"Label above the option stack.
explanationReactNodePhase-level explanation rendered after reveal.
accentColorstringvar(--cb-accent)Left-border accent for the explanation card.
classNamestringMerged onto the root via cn().

Accessibility

  • The option stack is a role="radiogroup" with an aria-label; each option is a role="radio" button with aria-checked mirroring the selection.
  • Disabled-after-reveal is communicated via the native disabled attribute so screen-reader users know further taps are inert.
  • Both explanation slots render in role="status" regions and are announced politely as they appear.
  • A :focus-visible ring replaces the default outline so keyboard users can step through the options with Tab and activate with Enter or Space.

Credits

  • Extracted from: AlgoFlashcards (src/lessons/primitives/BugHunt/BugHuntFixPhase.tsx). Stripped the useBugHuntContext coupling (phase / state / actions / trackHex / fixOptions all came from a lesson-level context), reshaped the option API from a correct: boolean array indexed by position into a stable id-keyed BugHuntFixOption[] so options can be re-ordered or hot-swapped without losing selection state, and replaced the hardcoded ring-foreground/* and bg-success/N Tailwind classes with cb-* semantic tokens.