Bug Hunt

A code-panel puzzle. The learner reads the snippet, decides which line is broken, and taps it to flag the bug. Each bug carries its own short description that fades in below the panel as it is found.

Preview
There's a bug in this code
Customize
Options

Installation

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

Usage

import { BugHunt } from "@craft-bits/core";
 
<BugHunt
  code={lines}
  bugs={[
    {
      id: "off-by-one",
      line: 4,
      description: "Off-by-one — change <= to <.",
    },
  ]}
/>

Controlled — drive foundIds from a parent reducer:

<BugHunt
  code={lines}
  bugs={bugs}
  foundIds={state.found}
  onFoundChange={(next) => dispatch({ type: "found", ids: next })}
  onMiss={(line) => dispatch({ type: "miss", line })}
/>

Anatomy

  • Header strip — a small bug glyph plus a label (defaults to "There's a bug in this code"). Tone-tinted; hidden via hideHeader.
  • Code panel — every line is a <button>. A :focus-visible background tint replaces the default outline; tap or Enter to flag a line.
  • Found highlight — flagged lines paint in the error tone and announce as aria-pressed.
  • Miss shake — tapping an empty line fires onMiss(line) and shakes the row once; nothing persists.
  • Description stack — each found bug renders a tinted card below the panel with role="status". Cards stack in find-order and animate in with SPRINGS.snap.

Props

PropTypeDefaultDescription
codereadonly string[]Lines rendered in the panel. 1-indexed in bugs.
bugsreadonly BugHuntBug[]Bugs the learner is hunting. { id, line, description }.
foundIdsreadonly string[]Controlled set of found bug ids.
defaultFoundIdsreadonly string[][]Initial found set when uncontrolled.
onFoundChange(ids) => voidFires when the found set changes.
onFindBug(bug) => voidFires once when a bug is flagged.
onMiss(line) => voidFires when a line with no bug is tapped.
disabledbooleanfalseRead-only; flagged bugs stay highlighted.
headerTone'neutral' | 'error' | 'warning''error'Tone of the header glyph.
headerTextReactNode"There's a bug in this code"Header label.
hideHeaderbooleanfalseSuppress the header strip.
showLineNumbersbooleantrueShow 1-indexed gutter line numbers.
classNamestringMerged onto the root via cn().

Accessibility

  • Each line is a <button> with an aria-label of the form "Line 4 — bug found", so screen-reader users always hear both the line number and the resolved state.
  • The code panel is role="group" with a configurable aria-label so the whole puzzle is one stop on the screen-reader rotor.
  • Bug descriptions render in role="status" regions so they are announced politely as they appear.
  • Miss feedback is a single short horizontal shake on the row — short enough to feel like a "no" without violating reduced-motion expectations.

Credits

  • Extracted from: AlgoFlashcards (src/lessons/primitives/BugHunt.tsx). Stripped the useLessonContextSafe coupling, the Shiki token renderer, the staged trace stepper, the wrong-result panel, and the single-bug onComplete callback — generalized into a presentational puzzle that owns only the tap-to-flag interaction. Re-shaped around a bugs[] array so the same panel can host multi-bug hunts.