Bug Hunt Shell

The visual wrapper for a bug-hunt puzzle. Runs the trace / find / fix / done state machine, hosts a BugHuntCode panel by default, and publishes the live state to any descendant via BugHuntProvider. Drop BugHuntFeedback, BugHuntFixPhase, or any custom widget into the children / footer slots and they can read useBugHunt() without prop drilling.

Preview
Find the bug1 remaining
Customize
Options

Installation

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

Usage

import { BugHuntShell } from "@craft-bits/core";
 
<BugHuntShell
  codeLines={code}
  buggyLines={[4]}
  fixOptions={fixOptions}
  lang="typescript"
  onComplete={(correct) => console.log("done", correct)}
/>

Add bespoke chrome around the embedded panel by composing children and footer slots:

<BugHuntShell codeLines={code} buggyLines={[4]} fixOptions={fixes}>
  <p className="text-cb-fg-muted">Trace the loop guard, then tap the bug.</p>
  {/* the embedded BugHuntCode renders here */}
</BugHuntShell>

Wire descendants to the shared state with useBugHunt:

function Hint() {
  const { state } = useBugHunt();
  if (state.wrongLineCount === 0) return null;
  return <p>Look at the loop guard.</p>;
}

Anatomy

  • ProviderBugHuntProvider wraps the entire tree so any descendant can call useBugHunt(). The provider carries state, actions, codeLines, buggyLines, fixOptions, lang, and trackHex.
  • Layout — a <div> styled flex flex-col gap-4 carries data-cb-edu="bug-hunt-shell" and data-phase reflecting the current state machine phase.
  • Children slot — rendered above the embedded code panel; ideal for narration, a trace stepper, or any context the learner needs before tapping.
  • Embedded BugHuntCode — rendered with the shell's state. Suppress it with hideCodePanel when you want a fully bespoke layout but still want the shared state machine.
  • Footer slot — rendered below the embedded code panel; ideal for BugHuntFeedback, BugHuntFixPhase, or a custom result card.

Props

PropTypeDefaultDescription
codeLinesreadonly string[]Lines of code rendered in the panel.
buggyLinesreadonly number[]1-indexed lines that contain a bug.
langstring"typescript"Shiki language id forwarded to the panel.
traceLengthnumber0Number of trace steps before the find phase.
fixOptionsreadonly BugHuntFixOptionDef[][]Candidate fixes shown after the last bug is found.
fixRetryablebooleanfalseWhen true, a wrong fix resets so the learner can try again.
onComplete(correct: boolean) => voidFires once the puzzle resolves.
trackHexstringvar(--cb-accent)Accent color forwarded into the context.
codeHeaderTextReactNode"Find the bug"Header text on the embedded panel.
hideCodeHeaderbooleanfalseSuppress the panel's header strip.
showLineNumbersbooleantrueShow gutter line numbers in the panel.
codeMaxHeightstringmax-height on the panel's scroll container.
childrenReactNodeRendered above the embedded panel.
footerReactNodeRendered below the embedded panel.
hideCodePanelbooleanfalseSuppress the embedded panel entirely.
onCorrectTap() => voidFires on a confirmed bug tap or a correct fix pick.
onWrongTap() => voidFires on a wrong-line tap or a wrong fix pick.
classNamestringMerged onto the layout wrapper via cn().

Accessibility

  • The shell exposes data-cb-edu="bug-hunt-shell" and data-phase on its layout wrapper — useful both as a styling hook and as a landmark for screen-reader users navigating phase changes.
  • The embedded BugHuntCode keeps its own role="group" plus per-line aria-label / aria-pressed; the shell does not override them.
  • Motion comes from the embedded BugHuntCode; the shell adds none of its own. prefers-reduced-motion flows through to every descendant.
  • Sound is opt-in via onCorrectTap / onWrongTap. Consumers that wire audio should also pair it with a visual equivalent (the panel's tap feedback already covers this for hit/miss).

Credits

  • Extracted from: AlgoFlashcards (src/lessons/primitives/BugHunt/BugHuntShell.tsx). Folded the project's useBugHunt reducer into a private state-machine hook, stripped the useLessonContextSafe fallback (onScreenComplete / trackInfo.hex defaults), replaced playSound("correct" / "wrong") with onCorrectTap / onWrongTap callbacks, swapped the always-present BugHuntCode child for a controllable hideCodePanel slot, and added a footer slot so feedback and fix-phase panels sit naturally below the code without ad-hoc composition.