# Ensemble Ledger — Live Task Board
The shared queue all agents read and write. Lifecycle and rules: [`PROTOCOL.md`](PROTOCOL.md). Roster + cadence weights: [`ROSTER.md`](ROSTER.md).
**Status:** `backlog` → `ready` → `claimed` → `in-review` → `done` / `returned`
**Lock:** a task's `files` column is its lock — no two `claimed`/`in-review` tasks may overlap files.
---
## Active sprint: `sprint-knowledge-center` (branch: `sprint-jamguide-piano` — continued, per user directive)
**Goal (user directive 2026-07-08):** revise the **visual structure of the Knowledge Center** — today's knowledge lives in three disconnected surfaces (JamGuide/Roadmap, ExplorePanel, EducationPanel). Four user asks, verbatim priorities:
1. **Playable voicings per chord** — browse *and hear* alternative voicings for any chord (guitar shapes + generated piano voicings), with simple switching.
2. **Simple ways of seeing techniques for licks** — lick cards with technique markers (hammer-on, slide, bend…), per style.
3. **Intermediate-level progressions** — research them and find the *easiest* way to surface them in-app (level tags + filter, not a new panel).
4. **Keep expanding music knowledge** — continue KB cells per `docs/kb-backlog.md` in spare iterations.
**Loop:** `/jam-loop` every 30 min for 12 h (24 iterations), started 2026-07-08. User is away — no user-gated tasks this sprint; where the old sprint would have asked (concept selection), Muse picks the strongest option and records the rationale in the concept doc. Weights: Muse 3, Luthier 3, Professor 2, Critic gate, Herald 0.
| id | title | domain | status | depends-on | files (lock) | definition of done |
|----|-------|--------|--------|-----------|--------------|--------------------|
| M-03 | Seed `sprint-knowledge-center` from user directive 2026-07-08; carry L-11 forward (hold lifted — user asked to continue on this branch) | maestro | done | — | `docs/agents/LEDGER.md` | sprint seeded, loop scheduled |
| L-11 | Wire piano into `JamGuide.jsx`: Piano tab active; per station render `MiniPiano voicing={…}` from `pianoVoicing`/`pianoVoicingChain`; keep the guitar path intact. **Carried from piano sprint; user hold lifted 2026-07-08** | engineering | done | — | `src/components/JamGuide.jsx` | piano tab shows per-station keyboards synced to the loop; guitar unchanged; build + smoke green |
| D-20 | Knowledge Center concept doc: one unified IA for JamGuide (live) + ExplorePanel (browse) + EducationPanel (learn) — sections *Jam Guide / Explore / Voicings / Licks & Techniques*, where the voicing player and lick cards mount, level-filter placement, narrow-viewport plan. **No user gate: pick the strongest layout, record rationale + rejected alternatives in the doc** | design | done | — | `docs/design/knowledge-center.md` | doc names every component to touch, the target structure, and the migration order for L-22; honest about what stays untouched |
| L-20 | `src/lib/chordAudio.js` — Web Audio voicing playback: `playVoicing(notes, {strumMs, durMs})` (piano `notes` are absolute 0–36 above C3; also accept guitar `{string,fret}` arrays via a midi mapper), own `AudioContext` created lazily on first user gesture, simple triangle/partials envelope, `stopAll()`. **Must not touch the detection pipeline's contexts**; document mic-feedback caveat (playback may be heard by detection) | engineering | done | — | `src/lib/chordAudio.js` | pure module + tiny API; no import from AudioCapture/App; plays a Cmaj7 shell audibly in dev; build green |
| D-21 | `VoicingBrowser.jsx` — for `{rootPc, quality}`: guitar alternatives (all matching `voicings.js` shapes via `ChordDiagram`) + piano alternatives (`pianoVoicing` root/shell/rootlessA/rootlessB via `MiniPiano voicing`) as switchable chips, each with a ▶ button calling `chordAudio`. Note (D-20 gate): `GUITAR_SHAPES` is private — lock includes an **export-only** line in `voicings.js`; do not cite the D-20 doc for small-text AA on active chips (use surface-background rule) | design | done | L-20 | `src/components/VoicingBrowser.jsx`, `src/lib/voicings.js` (export-only) | renders + plays all styles for any KB quality in any key; design tokens; AA contrast; keyboard-reachable |
| L-21 | Integrate VoicingBrowser: `ChordDetailModal` (replace/extend its static voicing grids) + JamGuide station-enlarge view — tap any chord anywhere → browse + audition voicings | engineering | done | D-21, L-11 | `src/components/ChordDetailModal.jsx`, `src/components/JamGuide.jsx` | both entry points work; legacy modal content preserved where not superseded; build + smoke green; audio-callback contract intact (App.jsx untouched or additive-only) |
| C-20 | Schema + validator + smoke for the two new KB shapes (both **optional/additive**): (a) `level: 'foundation'\|'intermediate'` on progressions; (b) per-style `licks` — `{id, name, level, chordContext, techniques[]}` from a fixed technique vocab (hammer-on, pull-off, slide, bend, double-stop, ghost-note, chromatic-approach, vibrato) + `tab: [{string, fret, technique?}]` ordered notes. Existing KB stays green untouched | quality | done | — | `src/data/kb/SCHEMA.md`, `scripts/validate-kb.mjs`, `scripts/smoke.mjs` | validator enforces the vocab + tab shape when present; 10-style KB still green; smoke still 776+ green |
| P-20 | Intermediate progressions: research per `docs/progression-repertoire.md`, then tag existing KB progressions with `level` and author **2 new intermediate progressions each** for jazz, gospel, neo-soul/rnb (secondary dominants, chained ii-Vs, backdoor, borrowed iv…) with guitar plays | content | done | C-20 | `src/data/kb/**` (progressions + guitar files of jazz/gospel/rnb), `src/data/kb/index.js` | every progression carries `level`; 6 new intermediate progressions validator-green; tips written for the "confident enough to jam" player (GOAL Part 2 audience) |
| P-21 | Licks pack #1: author 3–4 licks each for **blues, jazz, funk** guitar using the C-20 lick schema — technique tags, chordContext (which station/quality it fits), honest attribution | content | done | C-20, P-20 (done) | `src/data/kb/blues/guitar.js`, `src/data/kb/jazz/guitar.js`, `src/data/kb/funk/guitar.js` | validator green; every lick playable as written (Critic hand-checks fret/string sanity) |
| L-23 | Fix latent `getGuitarVoicings` bug (found by L-20): `strIdx = shape.rootStr - 1` indexes `OPEN` (ordered s6…s1) wrongly — rootStr 5 reads the B string's pc, rootStr 4 reads G's; A-shape C major computes fret 1 instead of 3. Use the ChordDiagram convention `6 - rootStr`. Affects fret labels in ChordDetailModal/ExplorePanel/CurrentJamPanel for rootStr 4/5 movable shapes; ChordDiagram itself is correct. Also fix the `playChord` docstring octave example in `chordAudio.js` (Critic nit) | engineering | done | — | `src/lib/voicings.js`, `src/lib/chordAudio.js` (docstring only) | A-shape/C-shape fret labels correct (hand-verify 3 shapes × 2 keys); build + smoke green |
| D-22 | `LickCard.jsx` — tab-style SVG lick renderer: ordered notes on a string×fret window, technique glyphs (h, p, ⌒ slide, ↑ bend), compact card + technique legend; graceful on empty/malformed | design | done | C-20 | `src/components/LickCard.jsx` | renders every P-21 lick; glyph legend readable at card size; tokens; AA contrast |
| L-22 | Knowledge Center shell per D-20: restructure how JamGuide/ExplorePanel/EducationPanel mount into the unified IA; add the **level filter chip** (foundation/intermediate) wherever progressions list; mount Licks section (LickCard grid per style) | engineering | done | D-20, D-22, P-20, P-21 (licks commit may land empty-state-first per the D-20 doc). Ride-along polish (D-22 gate nit): LickCard stack-collision guard should check all same-column notes, not just prev (`LickCard.jsx:100`) | `src/components/JamGuide.jsx`, `src/components/ExplorePanel.jsx`, `src/App.jsx` (mount only) | unified structure live; level filter works; licks browsable per style; audio contract intact; build + smoke green |
| P-22 | KB expansion: **jazz piano** cell (first piano cell per `docs/kb-backlog.md`) — authored comp voicings override computed where they add signature value | content | claimed | L-11 | `src/data/kb/jazz/piano.js`, `src/data/kb/index.js` | validator green + registered (SCHEMA "Piano play" recipes); **rendering the authored pack is L-24, not this task** — status: done |
| C-22 | `checkPianoRecipe` span check: enforce SCHEMA rule 3 (each hand ≤ a 10th = 15 semitones) in the validator so future piano cells don't rely on hand-verification (P-22 gate follow-up) | quality | ready | P-22 | `scripts/validate-kb.mjs`, `scripts/smoke.mjs` | span violation fails validator; existing KB green; smoke proves the check bites |
| D-23 | VoicingBrowser `show="guitar"\|"piano"\|"both"` prop (default both — zero change for existing mounts) + LickCard stack-collision guard checks ALL same-column notes not just prev (D-22 gate nit, `LickCard.jsx:100`) | design | done | D-21, D-22 | `src/components/VoicingBrowser.jsx`, `src/components/LickCard.jsx` | prop-gated rows; all existing mounts render identically with no prop; guard fixed; build + smoke green |
| L-25 | ChordDetailModal Guitar/Piano tabs pass `show="guitar"`/`show="piano"` to VoicingBrowser (JamGuide station-enlarge + Voicings section keep both rows) | engineering | ready | D-23 | `src/components/ChordDetailModal.jsx` | tabs show only their instrument's row; build + smoke green |
| D-24 | MiniPiano voicing-path compact window: crop the keyboard to the voicing's octave span (+1 key padding each side) so Jam Guide station thumbs aren't ~390 px (L-11 gate observation); `size="full"` and the legacy lh/rh path byte-unchanged | design | done | D-10 | `src/components/MiniPiano.jsx` | thumbs materially narrower for ≤1-octave voicings; root/bass marks intact; legacy consumers render identically; build + smoke green |
| P-24 | Licks pack #2: 3–4 licks each for **rock, country, reggae** guitar (C-20 schema; same bar as P-21 — playable as written, honest sources) | content | done | C-20, P-21 | `src/data/kb/rock/guitar.js`, `src/data/kb/country/guitar.js`, `src/data/kb/reggae/guitar.js` | validator green with ~21+ licks; every note serves its chordContext (Critic verifies) |
| L-24 | JamGuide piano branch prefers an authored piano pack (SCHEMA piano recipes) over computed `pianoVoicingChain` when the style has one; fall back to computed otherwise. Recipe→notes convention: order inside each hand = voicing order low→high (documented at `jazz/piano.js:14`) | engineering | ready | P-22, L-22 | `src/components/JamGuide.jsx` | jazz piano stations render the authored comp; other styles keep computed voicings; build + smoke green |
| P-23 | KB expansion: **gospel piano** cell | content | ready | P-22 | `src/data/kb/gospel/piano.js`, `src/data/kb/index.js` | validator green; renders in the piano tab |
| C-21 | Sprint-end sweep: full build + validator + smoke, cross-browser sanity notes, PR body draft summarising the sprint for `main` | quality | backlog | L-21, L-22, P-21 | (none — verification) | all green; PR body ready |
> Sequencing: L-11 ‖ D-20 ‖ L-20 ‖ C-20 are all file-disjoint and ready now. Then D-21 → L-21, and P-20/P-21/D-22 → L-22. P-22/P-23 fill spare iterations. Critic gates every task as usual.
---
## Shipped sprint: `sprint-jamguide-piano` (branch: `sprint-jamguide-piano` — piano resolver phase complete; L-11 carried into the sprint above)
**Goal:** extend the shipped Roadmap Jam Guide to **piano** (guitar→piano→bass roadmap). When the Piano instrument tab is selected, each Roadmap station shows a *generated* piano voicing on a mini-keyboard — voicings computed from the progression's `degrees`+`qualities` (GOAL G4: generate piano voicings from interval recipes; no authored data needed for the MVP). Branch cut off `sprint-jam-guide` HEAD. Weights: Luthier 3, Muse 2, Critic gate.
| id | title | domain | status | depends-on | files (lock) | definition of done |
|----|-------|--------|--------|-----------|--------------|--------------------|
| M-02 | Cut `sprint-jamguide-piano` branch off `sprint-jam-guide`; seed sprint | maestro | done | — | (branch) | branch cut, ledger seeded |
| L-10 | `src/lib/piano.js` — pure piano voicing resolver: `pianoVoicing({rootPc, quality}, opts?)` → shell (1-3-7), rootless A/B (3-5-7-9 / 7-9-3-5), root-position; picks the inversion minimizing movement from a previous voicing (voice-leading); returns absolute key positions + pcs + a label | engineering | done | M-02 | `src/lib/piano.js` | pure fns; correct tones for all 14 CHORD_TYPES; voice-leading-minimal vs a prev voicing; build + smoke green; output shape documented for D-10/L-11 |
| D-10 | **Extend** existing `src/components/MiniPiano.jsx` (already imported by `ChordDetailModal`/`ExplorePanel` via `{rootPc, lh, rh}`) with an ADDITIVE `voicing` prop that renders a `pianoVoicing` output (`{notes,pcs,bass,style,label}`) on a 2–3 octave keyboard — root in accent, bass marked, thumb/full sizes; **legacy lh/rh path unchanged** | design | done | L-10 | `src/components/MiniPiano.jsx` | renders any `pianoVoicing` in any key; **ChordDetailModal + ExplorePanel render identically (legacy path untouched)**; build green; design tokens; AA contrast |
| L-11 | Wire piano into `JamGuide.jsx` | engineering | **carried → sprint-knowledge-center** | L-10, D-10 | — | see the active sprint table above (hold lifted by user 2026-07-08) |
| C-10 | Extend `scripts/smoke.mjs`: assert `pianoVoicing` returns a valid voicing for every KB quality (no crash, notes in range, contains the chord's defining tones); also assert no duplicate absolute note across styles (the sus2 forced-rootless nit from L-10 gate) | quality | done | L-10 | `scripts/smoke.mjs` | smoke covers the piano resolver; exits non-zero on regression — **currently red (1/776): surfaces the sus2 dup-note nit → L-13 fixes the resolver** |
| L-13 | Resolver dedupe: `pianoVoicing` must never emit a **duplicate absolute note** (sus2 forced-`rootlessB` collides pc2 → `notes=[26,26,31]`). Drop/merge collided notes so every voicing's `notes` are distinct; keep `pcs`/`bass` valid; default + chain paths unchanged | engineering | done | L-10 | `src/lib/piano.js` | no duplicate absolute note for any quality×style; C-10 smoke goes green (776/776); build green; existing 56-voicing correctness preserved |
> Piano authored packs (signature comps overriding computed voicings) are deferred to a later sprint — computed voicings suffice for the piano MVP.
---
## Shipped sprint: `sprint-jam-guide` (branch: `sprint-jam-guide`, PR #2 — complete)
Emphasis this sprint: **ship the Jam Guide MVP** (put the 8 guitar style packs on screen, synced to live detection) + **reframe the repo as a learning platform**. Weights this sprint: Luthier 3, Muse 3 (design-heavy), Professor 2, Herald 2, Critic gate.
**Design-first gate (user directive 2026-06-14):** Muse explored three distinct concepts; **user chose "Roadmap"** (improv-first highway) on 2026-06-15. Implementation tasks below are re-planned to build Roadmap. Concept doc: `docs/design/jam-guide-concept-c.md`. Stage/Playbook (`-a`/`-b`) kept as reference for future modes.
**Roadmap build shape:** a horizontal timeline panel — stations (= loop chords) carry guide-tone lanes (3rd/7th), a solo-scale label, and voice-leading rails (7→3) between stations; a playhead + beat grid track loop position; the chosen voicing is a secondary thumbnail per station; tap a station → guide tones on the fretboard. Needs derived theory (`guideTones`, `voiceLeadingPairs`, default `soloScale`) — Luthier owns those.
| id | title | domain | status | depends-on | files (lock) | definition of done |
|----|-------|--------|--------|-----------|--------------|--------------------|
| M-01 | Cut `sprint-jam-guide` branch; seed sprint | maestro | done | — | (branch) | branch cut, ledger header set |
| D-00a | Viz concept A — "Stage" | design | done | — | `docs/design/jam-guide-concept-a.md` | delivered; reference for future collapsed/glance mode |
| D-00b | Viz concept B — "Playbook" | design | done | — | `docs/design/jam-guide-concept-b.md` | delivered; reference for future study mode |
| D-00c | Viz concept C — "Roadmap" | design | done | — | `docs/design/jam-guide-concept-c.md` | delivered; **CHOSEN** |
| D-SEL | User selects a concept | maestro | done | D-00a, D-00b, D-00c | `docs/agents/LEDGER.md` | **Roadmap chosen 2026-06-15**; tasks re-planned below |
| L-01 | Shared matcher: extract/author `findLoopPosition` (which chord index the loop is on) + rotation-invariant degree-relative loop→progression-id matcher into `src/lib/match.js`; refactor `ProgressionBanner.jsx` to use it | engineering | done | M-01 | `src/lib/match.js`, `src/components/ProgressionBanner.jsx` | matcher maps detected loop → style progression id (rotation-invariant) AND returns current position index for the playhead; banner still works; build green — VERIFY: `npm run build` green; banner renders identically (findLoopPosition now imported from match.js); matchLoopToProgression rotation-invariant + quality tie-break (smoke: ii–V–I & its rotation → jazz-251-major rot 0/2; minor 251 → jazz-251-minor; chromatic → no match) |
| L-01b | Derived theory for Roadmap: add `guideTones(chordRoot,quality)` (3rd & 7th pcs), `voiceLeadingPairs(chordA,chordB)` (7→3 / nearest-tone rails), and default `soloScale(quality,mode)` (Mixo/dom7, Dorian/m7, Ionian/maj7…) to `theory.js` — additive exports only | engineering | done | M-01 | `src/lib/theory.js` | pure functions, unit-sane (ii–V–I in C → C→B, F→E rails); reuses existing `getChordTones`; build green; no existing export changed |
| L-02 | `JamGuide.jsx` panel shell: mounts last in `App.jsx`, reads `kb/index.js`, instrument+style tabs from registry, collapsed header → ~70vh; receives live loop + position props | engineering | done | L-01 | `src/components/JamGuide.jsx`, `src/App.jsx` | panel renders, tabs from KB, matched progression or fallback; build green |
| D-01 | `RoadmapTrack.jsx` — the heart: horizontal stations (loop chords) with guide-tone lanes (3rd/7th dots), solo-scale label, voice-leading rails between stations, playhead + beat grid, lookahead highlight on the next station | design | done | L-01, L-01b | `src/components/RoadmapTrack.jsx` | renders a real ii–V–I & a 12-bar blues from KB; lanes computed from `guideTones`/`voiceLeadingPairs`/`soloScale`; design tokens; AA contrast |
| D-01b | `ChordDiagram.jsx` — secondary per-station voicing thumbnail (movable `rootStr`+`offsets`, open `frets`+`onlyRoot`), key-aware fret placement, tap-to-enlarge | design | done | L-01 | `src/components/ChordDiagram.jsx` | renders any KB guitar shape in any key; chord-tone colour tier; compact thumbnail + enlarged states |
| D-02 | Roadmap assembly & live polish in `JamGuide.jsx`: place `RoadmapTrack` + thumbnails, animate playhead against BPM/onset, narrow-viewport reflow, tap-station → fretboard guide tones | design | done | L-02, D-01, D-01b | `src/components/JamGuide.jsx` (assembly/styling), `tailwind.config.js` | playhead tracks position; reflows on narrow; keyboard-reachable; AA contrast |
| D-03 | Finish the flagship: tap a Roadmap station → highlight that chord's guide tones (3rd/7th) on the **main Fretboard**. Lift a `jamFocusChord` state into `App.jsx`, `JamGuide` emits the selected station's `{rootPc, quality}` via a callback prop, `Fretboard` highlights those guide tones reusing its existing tier rendering. **Returned by Critic** → also fix root cause `guideTones` so 4-tone non-seventh chords (add9/maj6/min6) don't badge their 5th/6th as a "7" | engineering | done | D-02 | `src/App.jsx`, `src/components/JamGuide.jsx`, `src/components/Fretboard.jsx`, `src/lib/theory.js` | tapping a station lights its 3rd/7th (honest secondary anchor for non-7th chords); build green; smoke green; **audio-callback contract intact**; RoadmapTrack lane also corrected; keyboard-reachable; clears on deselect |
| P-01 | Gospel guitar KB cell | content | done | — | `src/data/kb/gospel/**`, `src/data/kb/index.js` | 5 progressions × 2 plays, validator green (run `/kb-expand`) |
| P-02 | Pop guitar KB cell | content | done | P-01 | `src/data/kb/pop/**`, `src/data/kb/index.js` | 5 progressions × 2 plays, validator green |
| H-01 | README reframe: lead with the learning-platform vision (detect → guide → learn), screenshot/gif placeholder, link `GOAL.md` + `docs/agents/` | community | done | — | `README.md` | README states the dual vision; links resolve |
| H-02 | `CONTRIBUTING.md` + issue templates: the data-contract path so a musician can PR a style without coding (point at `src/data/kb/SCHEMA.md`) | community | done | H-01 | `CONTRIBUTING.md`, `.github/ISSUE_TEMPLATE/**` | a non-coder can follow "add a style"; `/kb-expand` documented as the assisted path |
| C-01 | Smoke-test harness: a `scripts/smoke.mjs` that imports `kb/index.js` + runs the matcher on sample loops, wired alongside `validate-kb.mjs` | quality | done | L-01 | `scripts/smoke.mjs` | catches a broken matcher/registry before merge; documented in PROTOCOL gate |
> Critic (C-) reviews every L-/D-/P-/H- task as it reaches `in-review` — those reviews are the gate, not separate ledger rows, except where new test infra is itself the deliverable (e.g. C-01).
> GATE 2026-06-15 (Critic) · **L-01 PASS** — build green, validator green (8 styles/40 progs/80 plays). `findLoopPosition` extracted byte-identical (verified by diff, no semantic change). matchLoopToProgression verified rotation-invariant with quality tie-break: `[Dm7,G7,Cmaj7]`→`jazz-251-major` rot 0; rotation `[G7,Cmaj7,Dm7]`→ rot 2; `[Dm7b5,G7,Cm7]`→`jazz-251-minor` (tie-break works); chromatic→clean `{matched:false}`. rotation index correctly points at the loop slot aligning with KB degrees[0]. `match.js` holds only matching/position logic + a small local chord-root parser (theory.js exposes no pc helper — acceptable). Note (non-blocking): banner refactor also adds `onChordClick` click affordances — beyond a pure extract but already wired to `setSelectedChord`/ChordDetailModal in App.jsx and behaviour-preserving for `findLoopPosition`. Minor: `match.js` uses extensionless `import … from './theory'` (works under Vite; matches existing `education.js` convention).
> GATE 2026-06-15 (Critic) · **L-01b PASS** — build green. theory.js diff is purely additive (zero `-` lines; no existing export touched). Verified by running the real functions: ii–V–I in C → **Dm7→G7 = C→B (−1)** and **G7→Cmaj7 = F→E (−1)** rails (plus intentional 0-semitone common-tone rails). `guideTones` triad fallback flags `hasSeventh:false` and uses the 5th. `soloScale` defaults sane: dom7→mixolydian, min7→dorian, maj7→major(ionian), half_dim→locrian, dom7/minor→phrygian-dominant. Functions are pure and reuse CHORD_TYPES/SCALES.
> GATE 2026-06-15 (Critic) · **L-02 PASS** — build green; validator green (8 styles/40 progs/80 plays). 🚨 **Audio contract INTACT**: `git diff HEAD -- src/App.jsx` is +10 lines, zero deletions — only the `import JamGuide` line and the `` mount (placed last, after the tuner block). `handleNote`/`handleChroma`/`handleOnset` lines do not appear anywhere in the diff; no `useCallback` dep, ref-sync `useEffect`, or `AudioCapture` prop was touched. Black-screen/infinite-restart risk avoided. All 5 props passed (`detectedProgression`, `effectiveKey`, `chordHistory`, `bpm`, `currentChord`) exist in App scope. Tabs derive from the KB registry (instrument availability from `style.instruments` keys, style labels from `meta.label`); matched-progression vs. dashed-fallback both render. Disabled instrument tabs marked "soon" with `disabled` + title.
> GATE 2026-06-15 (Critic) · **D-01 PASS** — RoadmapTrack renders from props only (pure/presentational). Verified against the real KB: guide-tone honesty confirmed by running `guideTones` — C-major **triad** returns `hasSeventh:false`, secondary anchor = the 5th (pc 7), and the Station labels it **"5th"** not "7th" (`seventhKind` gate at line 113). The headline rail picks `voiceLeadingPairs(...)[0]` = the 7→3: Dm7→G7 ⇒ C→B (−1), G7→Cmaj7 ⇒ F→E (−1). BeatGrid station-start indexing is canonical-order-consistent with the stations array; playhead `pct` clamps at 0 when `position=-1`. Tokens used; `aria-current`, `role=progressbar` + `aria-valuetext`, focus rings present. Malformed/empty progression → graceful "no loop to map" card, no crash.
> GATE 2026-06-15 (Critic) · **D-01b PASS** — ChordDiagram movable base-fret formula verified independently: **E-shape A7 → fret 5** ✓ (rootStr 6, rootPc 9: `((9−4)%12)=5`); A-shape C → fret 3 ✓; open-string root → octave barre (12), a sane degradation. Root-tone coloured in accent tier (`isRoot` → `#a855f7`), non-root dots light-gray (AA on dark board). Both `thumb`/`full` sizes; `full` adds finger numbers + label. Undefined/unplaceable shape (root > fret 15 or `!shape`) → graceful "—" placeholder, no crash — covers the empty-thumbnail path. Open vs movable forms both handled.
> GATE 2026-06-15 (Critic) · **P-01 PASS** — validator green: `✓ KB valid — 9 style(s), 45 progressions, 90 plays` (gospel now included); `npm run build` green (gospel imported by `kb/index.js`, 87 modules, built in 5.65s). Schema: 5 progressions × 2 plays = 10 plays, all ids `gospel-`-prefixed, rn/qualities/bars all length-consistent with degrees, qualities all in CHORD_TYPES (min7/dom7/maj7/maj/add9/sus4/dim7). No duplicate id across all 45. Registered in `index.js` mirroring the other 8 styles (no broken import). **Voicings hand-verified** (independent pc computation matching the validator's): SHELL_6.maj7 → {0,11,4}=R-7-3 ✓; SUS4_5 → {0,5,7}=R-4-5 ✓; DIM7_5 → {0,6,9,3}=R-♭5-𝄫7-♭3 symmetric ✓; ADD9_6 → {0,2,4,7} full add9 (9th present) ✓; MIN9_5 → {0,2,3,7,10} full min9 ✓ — confirms the validator genuinely exercises these shapes via `checkGuitarShape`. **Theory** sound: backdoor ♭VII7=pc10 rises a whole step to I; #iv°7=pc6 passing chord; 6-2-5-1 + plagal IV-I Amen tag; cascading cycle-of-dominants all correctly spelled. **Authenticity** honest: attributions hedged appropriately ("traditional gospel turnaround", "reharmonized", "Every Praise … vamp feel", "e.g. Tenderly bars 5-7" as a back-door reference) — no claim that a specific recording uses an exact voicing; no music-theory error in any tip (Dorian-over-min7, Mixo-over-dom7, sus-non-resolution advice all correct). *Non-blocking nit*: a few shape comments list tones in interval order, not low-to-high string order (e.g. MAJ9_6 "R–3–7–9" sounds R-9-3-7) — labeling nicety, all tones present and correctly spelled; not a defect.
> GATE 2026-06-15 (Critic) · **H-01 PASS** — every relative link resolves on disk: `GOAL.md`, `CLAUDE.md`, `src/data/kb/SCHEMA.md`, `src/data/kb/jazz/` (dir), `docs/agents/` + ROSTER/PROTOCOL/LEDGER all exist; `CONTRIBUTING.md` is correctly referenced as **"coming (tracked as task H-02)"** and does NOT exist on disk — honest, not a dead live-link. Every command real: `electron:dev`, `dev`, `electron:build:win|mac|linux` all in `package.json`; `scripts/validate-kb.mjs` exists. **No `frontend/` path remains**; build output dir stated as `release/` matches electron-builder `directories.output: "release"`. git clone URL `whattheflat/whattheflat.git` matches actual `origin`. **Honesty verified**: gospel described as "landing next" (not shipped, still in-review at review time); Piano/bass packs, Progression Builder, detection-powered drills all under **In progress**, not today; Roadmap Jam Guide listed as working-today (true — D-02 passed, MVP live). Leads with detect → guide → level up; links GOAL.md + docs/agents/. No overstatement found.
> GATE 2026-06-15 (Critic) · **P-02 PASS** — gated against the FINAL combined tree (P-02/H-02/C-01 merged). `node scripts/validate-kb.mjs` → `✓ KB valid — 10 style(s), 50 progressions, 100 plays` (pop registered in `index.js`; the transient "pop not registered" mid-batch failure is resolved — `index.js` imports popMeta/popProgressions/popGuitar and exports the `pop` block). Schema: 5 progressions × 2 plays = 10, all ids `pop-`-prefixed, no duplicate across all 50 (verified). **Voicings hand-computed** (independent pc calc): OPEN_CADD9 → {0,2,4,7} = R-9-3-5, the 9th (pc2) genuinely present (true add9, not plain C) ✓; OPEN_G/OPEN_F → {0,4,7} major ✓; BARRE_MAJ_6 (E-shape) {0,4,7}, BARRE_MIN_5 (Am-shape) {0,3,7}, BARRE_MAJ_5 (A-shape) {0,4,7} ✓ — confirms validator exercises the shapes. **Non-duplication judged ACCEPTABLE**: pop-axis (0-7-9-5 maj) shares shape with rock-axis/reggae-nwnc and pop-mixo-bVII (0-10-5 mixo) with rock-mixo-vamp, but ids differ, songs differ (No Woman No Cry/Adele/Beatles vs rock), and guitar plays are capo-and-open framed vs rock's barre framing — a real genre reuse with distinct content, not a duplication smell; differentiation rationale documented in the progressions.js header. **Honesty CONFIRMED**: every sus2/sus4 mention is prose (note/tips/styleIntro) framed as a hammer-on/pull-off fill over a held chord ("the chord stays major, your finger wiggles"); zero sus in the data — all pop-mixo chords are voiced plain `maj`; tips never claim a voicing the data lacks. Song attributions hedged (Clocks "♭VII colour", Royals "verse", Memories "Pachelbel, set in B").
> GATE 2026-06-15 (Critic) · **H-02 PASS** — every relative link resolves on disk: GOAL.md, README.md, src/data/kb/SCHEMA.md, gospel/{meta,progressions,guitar}.js, index.js, src/lib/theory.js, scripts/validate-kb.mjs, docs/agents/ROSTER.md, and all three issue templates (add-style/bug_report/feature_request) + config.yml — verified via on-disk existence check. Every command real (npm install / electron:dev / dev / build / node scripts/validate-kb.mjs all in package.json). **Schema fidelity exact**: the quality list `maj,min,dom7,maj7,min7,dim,dim7,half_dim,aug,sus4,sus2,maj6,min6,add9` matches `CHORD_TYPES` keys byte-for-byte; modes list matches the validator's MODES; degrees-0-11, movable(rootStr/offsets) vs open(frets/onlyRoot), and extensions/rootless/omit3 explanations all match SCHEMA.md + validator logic — no invented field, no drift. LICENSE correctly NOT added (CONTRIBUTING honestly states "doesn't have a license file yet … reuse terms aren't settled"). Issue templates present and sane. *Non-blocking nit*: CONTRIBUTING's illustrative validator-output sample reads `9 style(s)/45/90` (true at authoring time, pre-pop) — it's a "what a passing run looks like" example, not a load-bearing claim; the instructions don't depend on the count. Worth a one-char refresh to 10/50/100 someday, not a return.
> GATE 2026-06-15 (Critic) · **C-01 PASS** — `node scripts/smoke.mjs` → `✓ Smoke test passed — 71/71 checks green (10 styles, 50 progressions)`, exit 0; reports the FULL final KB (10 styles, derived from the loaded registry, not hardcoded). Imports the REAL `kb/index.js` + `match.js` (no mocks); asserts registry integrity (every style meta/progressions[]/instruments.guitar, per-prog degrees/qualities/rn/bars consistency, cross-KB id uniqueness) and matcher correctness (ii–V–I matched:true + structural rebased-degree-shape check, rotation invariance to the SAME derived id with non-zero rotation, axis match guarded with skip-if-absent, chromatic → matched:false/id:null, findLoopPosition in-range/-1/-1-on-empty). Exits non-zero on failure (mirrors validate-kb). **Adversarial proof**: sabotaged `matchLoopToProgression` to always return no-match → smoke FAILED 4 checks, **exit code 1** (assertions are NOT too weak — a broken matcher cannot pass). match.js restored, no diff. **Resolve hook judged SOUND**: the inline `module.register` ESM hook retries ONLY on `catch`, ONLY for relative+extensionless specifiers (`/^\.{1,2}\//` && `!/\.[mc]?js$/`) — verified the regex skips `./theory.js`, `react`, `node:url`, `./a.mjs` and retries only `./theory`/`../lib/match`; it cannot mask a genuine missing module (the `.js` retry itself surfaces the real ERR_MODULE_NOT_FOUND). Correctly chosen over editing match.js (locked to L-01). **No npm script added** (validate-kb has none) — `package.json` untouched (git: only `src/data/kb/index.js` is modified; smoke.mjs/pop/CONTRIBUTING/templates are new untracked). No hardcoded fragile ids — expected ids derived from the loaded KB; survives KB growth.
> GATE 2026-06-15 (Critic) · **D-03 FAIL (returned)** — build green (vite, 90 modules, 10.66s); validator green (`✓ KB valid — 10 style(s), 50 progressions, 100 plays`, no KB change). 🚨 **Audio contract INTACT**: `git diff -- src/App.jsx` is purely additive — exactly (a) one new `useState(jamFocusChord/null)` placed in the UI-state block (outside all audio-callback/ref logic, with a comment that it is not read by any callback), (b) `jamFocusChord={jamFocusChord}` on the existing `` mount, (c) `onFocusChord={setJamFocusChord}` on the existing `` mount. Grep of the **changed lines** for `handleNote|handleChroma|handleOnset|useCallback|AudioCapture|Ref|useEffect` → **NO MATCHES**. No callback, ref, ref-sync useEffect, or AudioCapture prop touched. Black-screen/infinite-restart risk avoided. **Wiring correct**: JamGuide emits `{rootPc, quality:qualities[i]}` (station's actual KB quality) on select, `null` on deselect; the existing `selectedStation→null` reset effect (loop/style/instrument change) flows through the new cross-link effect and clears the highlight; an unmount-cleanup effect also clears; all guarded `onFocusChord?.` so JamGuide stays standalone. Fretboard **null path verified unchanged** (`hasFocus=false` ⇒ `tag` always null ⇒ `if(!color&&!tag)` collapses to original `if(!color)`; no halo/badge/legend). Emphasis is visually distinct (purple ring `DOT_R+3` + degree badge, accent token `#a855f7`). Triad labeling **correct** for maj/min/dim/aug/sus4/sus2 (all `hasSeventh:false` → badge/legend read "5"). **DEFECT (blocking)**: `guideTones`' secondary-anchor heuristic is `hasSeventh = ints.length >= 4`, so the **4-tone non-seventh** KB qualities `add9` `[0,2,4,7]`, `maj6`/`min6` `[0,4,7,9]` return `hasSeventh:true` with `seventh = ints[last]` = the **5th** (add9) or **6th** (maj6/min6). D-03's `focusLabel` trusts that boolean and badges those tones **"7"**, legend reads **"3 / 7"** — a perfect fifth / sixth rendered to the user as a seventh on the flagship Fretboard. **Reachable**: `add9` is in gospel progression qualities (×4), `min6` in bossa + rnb qualities — tapping those stations mislabels a non-7th. Verified by running `guideTones(0,'add9')→{third:4,seventh:7,hasSeventh:true}` (G badged "7"), `guideTones(0,'min6')→{third:3,seventh:9,hasSeventh:true}` (A badged "7"). The DoD's "the chord's guide tones (3rd/7th)" / "badge reads 5 not 7 for a triad" contract is violated for these KB-present qualities. **Fix direction (return to Luthier):** make the secondary anchor honest for extended non-7th chords — either tighten `guideTones` so a chord with no minor/major 7th reports `hasSeventh:false` and falls back to the 5th (root cause, L-01b owns theory.js), or have `focusLabel`/the badge derive the actual interval of `seventh` above root (10/11 ⇒ "7", 9 ⇒ "6", 7 ⇒ "5", 2 ⇒ "9") rather than trusting the boolean. Re-gate after fix.
> GATE 2026-06-15 (Critic) · **D-03 PASS (re-gate)** — the returned defect is fixed at the root cause and hardened. `npm run build` green (vite, 90 modules, built in 3.25s); `node scripts/smoke.mjs` → `✓ Smoke test passed — 71/71 checks green (10 styles, 50 progressions)`, exit 0; `node scripts/validate-kb.mjs` → `✓ KB valid — 10 style(s), 50 progressions, 100 plays` (no KB change). **Root-cause fix VERIFIED by running the real `guideTones`**: `hasSeventh` now keys on the actual presence of interval 10 or 11 (`ints.find(i=>i===10||i===11)`), not `length>=4`. Independent truth table (root C): add9→{third:2,seventh:7,**hasSeventh:false**,anchor=5th G}; maj6→{4,7,false,5th}; min6→{3,7,false,5th}; dom7→{4,10,true,7=A♯}; maj7→{4,11,true,7=B}; min7→{3,10,true,7=A♯}; triads maj/min/sus4/sus2→false,anchor 5th(7); dim→anchor ♭5(6); aug→anchor #5(8); dim7→♭5(6); half_dim→true,10. Return shape `{third, seventh, root, hasSeventh}` **unchanged** (keys verified). **Badge hardening (Fretboard) VERIFIED**: `focusSeventhLabel` derives the label from the *actual* interval of the anchor pc above the chord root (10/11→"7", 9→"6", 8→"♭6", 7→"5", 6→"♭5"), so a wrong boolean can't mislabel — adversarial sweep of all 14 KB qualities × keys {3,7,11}: a "7" badge appears iff the interval is truly 10/11; **NO MISLABEL**. Unknown quality → `maj` fallback, no crash. **Null-focus path byte-equivalent**: `jamFocusChord=null` ⇒ `hasFocus=false` ⇒ `tag` always null ⇒ `if(!color&&!tag)` collapses to the original `if(!color) return null`; no halo/badge/legend; `focusSeventhLabel` is dead-but-harmless. **No collateral**: `voiceLeadingPairs`/`soloScale` signatures untouched (theory.js diff hits only `guideTones` body+jsdoc); ii–V–I rails still correct (Dm7→G7 C→B [0→11,−1]; G7→Cmaj7 F→E [5→4,−1]) and `voiceLeadingPairs` does NOT crash for the now-5th-anchored add9/maj6/min6 (Cadd9→G7, Cmaj6→F, Amin6→Dm7 all return smooth rails). match.js/RoadmapTrack/ChordDiagram/KB untouched (`git diff --name-only` = only the 4 declared D-03 files). 🚨 **Audio contract INTACT**: `git diff -- src/App.jsx` purely additive (one `useState(jamFocusChord/null)` outside the callback/ref block + an additive `jamFocusChord=` prop on `` + `onFocusChord=setJamFocusChord` on ``); grep of changed lines for `handleNote|handleChroma|handleOnset|useCallback|AudioCapture|Ref|configRef|useEffect` → NO MATCHES; the sole "−" line is the Fretboard mount being re-emitted with the added prop. *Non-blocking observation (pre-existing, out of D-03 scope):* `guideTones` uses `ints[1]` for the 3rd, so `add9` `[0,2,4,7]` reports its **9th (pc 2)** as `third`, not the major 3rd (pc 4) — true at L-01b time, unchanged by this fix, and D-03's bug was strictly the seventh/secondary anchor; worth a future tidy, not a return.
> GATE 2026-06-15 (Critic) · **L-10 PASS** — (re-gated cleanly after the prior dispatch died on an API error pre-verdict.) `npm run build` green (vite 7.3.1, 90 modules, built in 2.24s); `node scripts/smoke.mjs` → `✓ Smoke test passed — 71/71 checks green (10 styles, 50 progressions)` exit 0; `node scripts/validate-kb.mjs` → `✓ KB valid — 10 style(s), 50 progressions, 100 plays`. **Purity confirmed** by reading the full module: exports `pianoVoicing({rootPc,quality},opts?)`→`{notes,pcs,bass,style,label}` plus `pianoVoicingChain`, `hasTrueSeventh`, `voicingToneSet` (and re-exports `guideTones`); imports ONLY `{CHORD_TYPES, guideTones}` from `./theory`; no `Date`/`Math.random`/module-level mutable state — determinism verified (every quality voiced twice → byte-identical). **Music verified myself** via a throwaway ESM script (deleted): swept **all 14 CHORD_TYPES × 4 styles = 56 voicings** at rootPc 0 — every `pcs ⊆ voicingToneSet(...)` (**NO out-of-chord note**), real 3rd present (sus→2/5), every TRUE 7th (interval 10/11: dom7/maj7/min7/half_dim) present in shell+rootless, all `notes ∈ [0,36]` (≤3 octaves), `bass=min(notes)`. Spot-checks exact: **Cmaj7 shell→{0,4,11}** ✓, **Dm7 rootlessA→{5,9,0,4}=F-A-C-E** ✓, **G7 shell→{7,11,5}** ✓. **Awkward qualities honest**: half_dim shell→{0,3,10} (true ♭7 kept), rootlessA→{3,6,10,2} "3-5-7-9" ✓; **dim7 correctly NOT a true-7th chord** — its 7-slot is interval 9 (𝄫7 = enharmonic maj6, not 10/11), so `hasTrueSeventh('dim7')=false`, shells as R-3-5 {0,3,6}, no fabricated 7th; **min6/maj6** (no true 7th) → root {0,3,7,9}/{0,4,7,9}, rootless labels honestly read "3-5-6-9" using the 6th as upper colour — no wrong note. **Voice-leading confirmed**: `pianoVoicingChain([Dm7,G7,Cmaj7])` total semitone motion **16 vs 18** unthreaded (`opts.prev` slides whole shapes by octaves toward the previous voicing); pcs **unchanged** by threading (per-chord pc sets identical to unthreaded — re-registered only, never corrupted). **Adversarial**: bogus/empty/null chord → graceful `maj` fallback, no crash. **One non-blocking nit (NOT a defect):** forcing `rootlessB` (or `rootlessA`) explicitly onto a **sus2** chord produces a cosmetic duplicate absolute note (`notes=[26,26,31]`) because the suspended-2nd "3rd-slot" tone (pc2) and the synthesized 9th (pc2) collide — still no wrong pc, in range, and honestly labeled "no 7th, 9-3-5 colour". It is **unreachable on the shipping path**: `defaultStyle(sus2)='root'` and L-11/`pianoVoicingChain` use defaults, so sus2 renders as clean root `[0,2,7]`; the dup only appears under an explicit forced-rootless override of a sus chord (documented graceful degradation). Default-style sweep of all 14 qualities confirmed zero duplicate notes. Recommend a future tidy (dedupe collided absolute notes in the rootless fallback) — not a return. `src/lib/piano.js` NOT edited; throwaway script removed.
> GATE 2026-06-15 (Critic) · **D-02 PASS** — the load-bearing integration is correct. **Independent rotation check**: `canonicalPos = ((position − rotation) % n + n) % n` translates a `findLoopPosition` index (which is into the *detected loop*) into the KB-canonical station RoadmapTrack/stationVoicings render. Worked a rotated [ii V I VI] of KB [I VI ii V] (rotation=2, n=4): playhead@detected-idx1(V)→canonical 3(V), @2(I)→0(I), @3(VI)→1(VI), @0(ii)→2(ii) — NOW lands on the chord actually playing. ✓ **rootPc parity** confirmed: thumbnail `rootPc` and RoadmapTrack station root are byte-identical `(((keyRoot+deg)%12)+12)%12`, and `keyInfo.root` (a note *name*, e.g. `detectKey`→"F#") is converted once via `chordRootPC` → pc, guarded `>=0`. stationVoicings `chords[i]` aligns 1:1 with `degrees[i]` (canonical). Auto-scroll is prop-driven off `position` (no rAF on the audio thread). Reflows (`flex-wrap`/`sm:flex-nowrap`), keyboard-reachable, AA contrast. NO_MATCH path → dashed fallback, never empty/crash. *Non-blocking deferral*: the DoD's "tap-station → **fretboard** guide tones" cross-link and `tailwind.config.js` edit were not delivered — the implementation cross-links via an inline enlarged ChordDiagram instead (flagged in-code as deferred). Panel is fully functional without it; recommend Maestro file the fretboard cross-link as a phase-2 follow-up.
> GATE 2026-06-16 (Critic) · **D-10 PASS** — `npm run build` green (vite 7.3.1, 90 modules, built in 9.36s); `node scripts/smoke.mjs` → 776/776 exit 0; `node scripts/validate-kb.mjs` → 10 styles/50/100 (no KB change). **ADDITIVE confirmed** by reading the full diff: the public `MiniPiano` now dispatches `if (voicing) else `. The legacy `{rootPc,lh,rh}` path is the SAME code, only refactored to named constants holding the IDENTICAL hex values (`LH_BLUE=#3b82f6`, `ACCENT=#a855f7`, `WHITE_FILL=#f5f5f5`, `WHITE_STROKE=#374151`, `BLACK_FILL=#1f2937`, `BLACK_STROKE=#111827`) — geometry (OCTAVES=2, WW/WH/BW/BH, BLACK_OFFSETS), highlight map, R-label all unchanged. **Consumers render identically**: `grep` confirms `ChordDetailModal.jsx:86` and `ExplorePanel.jsx:85` both call `` with NO `voicing` prop → `voicing` is `undefined` → both hit `LegacyPiano` → byte-equivalent output. **Voicing path correct**: maps absolute `notes` (0=low C, [0,36]) to keys via `oct*12+pc`, root pc lights accent `#a855f7` + "R", bass key amber-ringed (`#fbbf24` AA), other tones soft accent `#c084fc`; OCTAVES auto-spans 2–3 to contain `max(notes)`. Worked example `pianoVoicing({rootPc:0,quality:'maj7'})` → default shell, pcs `{0,4,11}`=C-E-B (R-3-7): C lit accent+R, B & E soft purple, C bass-ringed — musically right. `role="img"`+`aria-label`. Design tokens used. No legacy regression, no colour/render bug.
> GATE 2026-06-16 (Critic) · **C-10 PASS** — `node scripts/smoke.mjs` → **776/776 exit 0** (was 71 pre-piano; +705 piano checks). Diff ADDED a piano block, kept ALL prior registry/matcher checks. Sweeps 14 qualities × {C,G} × {root,shell,rootlessA,rootlessB,default} asserting: valid shape (notes/pcs/bass/style/label), `pcs ⊆ voicingToneSet(quality,actualStyle)`, real 3rd (or sus tone) present, true 7th present in shell/rootless when `hasTrueSeventh`, `notes ∈ [0,36]`, `bass===min(notes)`, no-dup-note, determinism, and chain pcs-preservation. **Adversarially proven NON-vacuous**: inject pc1 into pcs → **125 fails, exit 1** (pcs⊆toneSet bites); `bass=max(notes)` → **140 fails, exit 1**; revert L-13 dedupe (`notes=placed`) → the no-dup check fails on exactly `sus2@0 [rootlessB] notes=[26,26,31]` + `sus2@7 [21,21,26]`, **exit 1**. So the suite now passes LEGITIMATELY because L-13 fixed the resolver, not because any check was weakened. piano.js restored to its L-13 diff after each sabotage (git: 9 insertions, clean).
> GATE 2026-06-16 (Critic) · **L-13 PASS** — minimal fix at the single funnel point in `pianoVoicing`: `const notes = [...new Set(placed)]` (dedupe ABSOLUTE notes), `pcs`/`bass` derived from the deduped array so both stay consistent. Verified via a throwaway resolve-hook ESM check (written + deleted): (a) `pianoVoicing({rootPc:0,quality:'sus2'},{style:'rootlessB'})` → `[26,31]` distinct (was `[26,26,31]`); (b) NO default-style voicing across all 14 qualities has a duplicate, so the dedupe is a pure **no-op on the default/chain path** — L-11's render is unaffected; ii-V-I chain `[14,17,24]|[19,23,29]|[24,28,35]` all distinct & voice-led. The full 56-voicing correctness is re-asserted by the now-776-green smoke (pcs⊆toneSet, 3rd, 7th, [0,36], bass for every quality×style). **Safety reasoning — dedupe NEVER drops a needed note**: a JS `Set` of absolute integers merges two entries only when they are the *identical integer*, i.e. identical pc AND identical octave — the *same physical key*, which can sound only one note anyway. Two musically-distinct intended tones (different pc, or same pc a different octave like 14 vs 26) yield different integers and are never merged. The sus2 collision is a genuine unison (pc2 stand-in 3rd at +24=26 vs synthesized 9th at +12=26, both pc2 same octave) — a redundant doubling, safe to drop. No information loss possible. build green; default + chain paths unchanged.
---
## Backlog (future sprints)
- **Retire `EducationPanel.jsx`** — confirmed orphaned (no importer anywhere in `src/`, D-20 gate verified); delete after the Knowledge Center ships. Fold `CurrentJamPanel`'s overlapping content (voicings/scales/similar progressions) into the Knowledge Center and retire it too.
- **Level-tag `FAMOUS_PROGRESSIONS`** (`src/lib/education.js`, Professor): famous-progression cards are exempt from the level filter this sprint (Maestro call 2026-07-08, per D-20 open question 1).
- **Piano thumb width polish (Muse):** 2–3-octave MiniPiano station thumbs are ~266–390 px — consider cropping to the voicing's octave span (L-11 gate observation).
- **VoicingBrowser `show` prop (Muse, small):** add `show="guitar"|"piano"|"both"` (default both) so ChordDetailModal's instrument tabs can show only their own row — L-21 gate follow-up. Also stale JamGuide header comment (line ~19) cleanup.
- **`pianoVoicing` should return `rootPc` (Luthier, tiny):** callers currently must attach it themselves (JamGuide and VoicingBrowser both do); returning it at the source removes the false-"R" foot-gun for future consumers (D-24 gate observation — NOT a live bug, both call sites verified correct 2026-07-08).
- **Negative-fret shape handling inconsistency (Luthier, small):** `voicings.js` skips shapes that compute negative frets while `chordAudio.guitarShapeToNotes` slides them up an octave — pre-existing, flagged at the L-23 gate; unify someday.
- **Jam Guide phase 2:** `MiniPiano.jsx` + piano recipe resolver (unblocks piano packs); bass pattern renderer.
- **Content:** piano packs (jazz → gospel → neo-soul first), then bass packs (blues → jazz → funk first) — see `docs/kb-backlog.md`.
- **`guideTones` third bug (L-12, Luthier — small):** `guideTones` takes `ints[1]` as the "third", so `add9` `[0,2,4,7]` reports its **9th (pc 2)** as the 3rd instead of the major 3rd (pc 4) — wrong "3rd" lane/badge for add9 (live in gospel + pop-axis Cadd9). Pre-existing since L-01b; flagged by Critic during the D-03 re-gate. Fix: pick the actual 3rd/4th interval (3 or 4, or sus 2/5) rather than positional `ints[1]`. Smoke + a guideTones truth-table check.
- **Learning features (Professor + Luthier):** drills tab seeded from `docs/learn-curriculum.md`; target-note highlighting; ear-training quiz on own chord history; pocket report from the onset pipeline.
- **Progression Builder (GOAL G3):** key-relative palette, drag-reorder, Nashville-number toggle, voicing alternatives.
- **Platform (Herald):** GitHub Pages docs site; "you're playing the Creep progression" engagement hook; contributor leaderboard for styles added.
---
## Iteration log
_(Maestro appends one line per completed iteration: ` · · `.)_
- 2026-06-14 · done: M-01 · in-review (awaiting user pick): D-00a/b/c viz concepts · next: D-SEL (user chooses) → then L-01/D-01/D-02 implement chosen concept
- 2026-06-15 · done: D-00a/b/c, D-SEL (Roadmap chosen), L-01 (match.js + banner refactor), L-01b (guideTones/voiceLeadingPairs/soloScale) — Critic PASS both · next: L-02 (JamGuide shell) → D-01 (RoadmapTrack) ‖ D-01b (ChordDiagram)
- 2026-06-15 · done: L-02 (JamGuide shell + App mount), D-01 (RoadmapTrack), D-01b (ChordDiagram), D-02 (assembly + playhead rotation + thumbnails) — Critic PASS all 4, audio contract intact (App.jsx +10/−0) · **Roadmap Jam Guide MVP is live & builds green** · next: D-03 (fretboard cross-link, backlog) ‖ P-01 (gospel content) ‖ H-01 (README reframe)
- 2026-06-15 · done: P-01 (gospel guitar pack — 5 progs × 2 plays, validator 9 styles/45/90), H-01 (README reframed as learning platform, stale frontend/ paths fixed) — Critic PASS both · next ready: P-02 (pop pack) ‖ H-02 (CONTRIBUTING) ‖ C-01 (smoke harness) ‖ D-03 (fretboard cross-link)
- 2026-06-15 · done: P-02 (pop guitar pack — KB now 10 styles/50/100), H-02 (CONTRIBUTING.md + issue templates), C-01 (scripts/smoke.mjs — 71/71, adversarially proven to catch a broken matcher) — Critic PASS all 3 · **SPRINT `sprint-jam-guide` COMPLETE** → opening PR to main · remaining (next sprint): D-03 fretboard cross-link, piano/bass packs, drills
- 2026-06-15 · done: D-03 (tap station → guide tones on the main Fretboard; root-cause fix to `guideTones` hasSeventh + badge hardening) — Critic **returned once** (add9/maj6/min6 mislabeled 5th/6th as "7"), fixed, **PASS on re-gate** · audio contract intact · folded into PR #2 (completes the flagship) · filed L-12 (guideTones third bug) to backlog · next: seed piano phase-2 sprint (resolver + MiniPiano + wire)
- 2026-06-15 · **new sprint `sprint-jamguide-piano`** cut off sprint-jam-guide (keeps PR #2 stable) · done: M-02 (seed), L-10 (`src/lib/piano.js` voicing resolver — shells/rootless A·B/root, voice-leading threading; Critic PASS, 56 voicings, 0 wrong notes) · next ready: D-10 (MiniPiano) ‖ C-10 (smoke piano) → then L-11 (wire piano tab) · L-10 chosen as a new unimported lib so the user's live frontend review stayed undisturbed
- 2026-06-16 · done: D-10 (extended existing `MiniPiano.jsx` with additive `voicing` prop — legacy lh/rh path for ChordDetailModal/ExplorePanel byte-unchanged), C-10 (smoke +705 piano checks → 776 total), L-13 (resolver dedupe — `[...new Set]`, fixes sus2 dup-note C-10 caught) — Critic PASS all 3 (adversarially proved C-10 checks bite + dedupe never drops a needed note) · **mid-iteration corrections:** D-10's "new file" premise was wrong (MiniPiano already existed + imported) → re-scoped to additive extend; C-10 correctly turned the branch red surfacing a real wart → filed+fixed L-13; 3 subagents hit API errors (2 produced nothing, 1 landed its diff before dying) · **L-11 (wire piano tab) intentionally HELD** — it's the first UI-visible change; waiting until the user finishes reviewing localhost:5173 · next: L-11 on the user's go
- 2026-07-08 · **new sprint `sprint-knowledge-center`** seeded on the SAME branch (user directive: continue on `sprint-jamguide-piano`) · user asks: playable voicings per chord, lick/technique cards, intermediate progressions (easiest in-app surface = level tags + filter), Knowledge Center visual restructure, keep expanding KB · L-11 hold LIFTED, carried forward · loop scheduled: /jam-loop every 30 min × 24 iterations (12 h) · ready now (file-disjoint): L-11 ‖ D-20 ‖ L-20 ‖ C-20
- 2026-07-08 · **iteration 1/24** · done: M-03, L-20 (`4b0f0c8` audio engine), L-11 (`7b669f8` piano tab LIVE), D-20 (`96dda6a` Knowledge Center concept), C-20 (`793e60a` lick/level schema, smoke 787) — Critic PASS all four, zero returns · findings folded into the board: D-21 lock += `voicings.js` export-only (GUITAR_SHAPES private); L-22 deps += P-21; P-21 serialized behind P-20 (`jazz/**` overlap); filed L-23 (pre-existing `getGuitarVoicings` rootStr fret-label bug, found by L-20); D-20 discovered ExplorePanel + EducationPanel are ORPHANED (never mounted) → retirement backlogged; famous-progressions level-filter exemption recorded · next ready (file-disjoint): D-21 ‖ P-20 ‖ D-22 (D-22 may build against the SCHEMA fixture until P-21 lands)
- 2026-07-08 · **iteration 2/24** · done: D-22 (`44d9c4f` LickCard), D-21 (`3c903ad` VoicingBrowser — returned once: rootless voicings badged lowest voice as "R", one-line rootPc fix, PASS on re-gate), P-20 (`c08b334` — returned once: 3 caption theory misstatements, string fixes, PASS on re-gate; KB 56 progs/112 plays, all jazz/gospel/rnb progressions level-tagged) · incident: both first-round gates stalled on API watchdog (600s) twice for P-20 — fresh critic instances completed them; one cron fire absorbed into gate completion · Critic gate quality note: both returns were real musical defects caught before commit
- 2026-07-08 · **iteration 4/24** · done: P-21 (`6ea1089` 12 licks blues/jazz/funk — every note pitch-verified), P-22 (`9e2567c` jazz piano cell, KB's first authored piano pack, 126 plays — returned once: 6 prose voice-leading overclaims on fifths-apart dominants, fixed + arithmetic-verified, PASS on re-gate), L-22 (`78706ce` **Knowledge Center shell LIVE** — 4-section dock, level filter, licks grid; App.jsx +1 line, audio contract grep-clean) · filed: C-22 (validator span check), L-24 scoped out of P-22 (authored-pack rendering) · promoted: L-24, C-22, P-23 · iteration 5 batch already in flight: D-23 ‖ D-24 ‖ P-24
- 2026-07-08/09 · **iteration 5/24** · done: D-23 (`db397f6` VoicingBrowser show prop + LickCard stack-guard), D-24 (`e2110ae` MiniPiano thumbs crop to voicing span, 266→142 px), P-24 (11 licks rock/country/reggae — KB now **23 licks across 6 styles**, every note pitch-verified) — Critic PASS all three, zero returns · D-24 gate raised a false alarm on the D-21 rootPc fix (its harness bypassed VoicingBrowser; Maestro verified line 297 intact — no regression); hardening idea backlogged (`pianoVoicing` return rootPc) · promoted L-25 · next ready (all file-disjoint): L-24 ‖ L-25 ‖ C-22 ‖ P-23, then C-21 closes the sprint
- 2026-07-08 · **iteration 3/24** · done: L-21 (`951d544` VoicingBrowser live in ChordDetailModal Guitar/Piano tabs + JamGuide enlarged stations — the "playable voicings per chord" user ask is now end-to-end), L-23 (`f4e0ac6` getGuitarVoicings base-fret fix — 252 movable-shape placements corrected, A-shape C was labeled fret 1 not 3) — Critic PASS both, zero returns this round · follow-ups filed: VoicingBrowser `show` prop (Muse), negative-fret handling inconsistency (Luthier), stale JamGuide header comment · P-21 lock narrowed to the 3 guitar.js files → P-22 now disjoint · next ready (file-disjoint): P-21 ‖ P-22 ‖ L-22 (licks section may land empty-state-first per D-20 doc)