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.jsonUsage
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-visiblebackground 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 withSPRINGS.snap.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
code | readonly string[] | — | Lines rendered in the panel. 1-indexed in bugs. |
bugs | readonly BugHuntBug[] | — | Bugs the learner is hunting. { id, line, description }. |
foundIds | readonly string[] | — | Controlled set of found bug ids. |
defaultFoundIds | readonly string[] | [] | Initial found set when uncontrolled. |
onFoundChange | (ids) => void | — | Fires when the found set changes. |
onFindBug | (bug) => void | — | Fires once when a bug is flagged. |
onMiss | (line) => void | — | Fires when a line with no bug is tapped. |
disabled | boolean | false | Read-only; flagged bugs stay highlighted. |
headerTone | 'neutral' | 'error' | 'warning' | 'error' | Tone of the header glyph. |
headerText | ReactNode | "There's a bug in this code" | Header label. |
hideHeader | boolean | false | Suppress the header strip. |
showLineNumbers | boolean | true | Show 1-indexed gutter line numbers. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- Each line is a
<button>with anaria-labelof 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 configurablearia-labelso 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 theuseLessonContextSafecoupling, the Shiki token renderer, the staged trace stepper, the wrong-result panel, and the single-bugonCompletecallback — generalized into a presentational puzzle that owns only the tap-to-flag interaction. Re-shaped around abugs[]array so the same panel can host multi-bug hunts.