# 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-roulette-kb` (branch: `sprint-jamguide-piano` — runs CONCURRENTLY with sprint-one-screen below; hourly cron, file-locks keep them disjoint)

**Goal (user directive 2026-07-11):** "a 12h loop with one iteration per hour for adding more piano licks and knowledge base voicings, potentially including some sort of circle of fifths etc in the knowledge center. on top of that … one button that would be a random chord progression selection. a button up top 'jam roulette' and then select a genre (like we have jazz, blues rock etc) and then have a random key chosen and an interesting progression show up as a loop in the screen. it would fill in the loop section and a 4 bar progression or something so that everyone can see the key and the chords to be played with different voicing so they can start playing… then musicians can start playing and it continues from it."

**Loop:** `/jam-loop` hourly × 12 (12 h), started 2026-07-11. Engineering tasks that need one-screen's files are dep-blocked on C-50 — content tasks fill the early iterations. Weights: Professor 3, Muse 2, Luthier 2, Critic gate.

| id | title | domain | status | depends-on | files (lock) | definition of done |
|----|-------|--------|--------|-----------|--------------|--------------------|
| M-07 | Seed `sprint-roulette-kb`; schedule hourly cron | maestro | done | — | `docs/agents/LEDGER.md` | seeded, cron created |
| C-60 | **Piano lick schema** (additive): licks today are guitar-tab-only — design the piano-lick shape reusing the bass-pattern language (per-note `deg` XOR typed `approach`, `beat`, `octave`, technique from a PIANO-appropriate vocab subset — decide which of the 8 words apply to keys and whether grace-note/roll style additions are needed; keep it validateable like C-41 did) in SCHEMA.md + validate-kb.mjs + smoke bite-test | quality | done | — | `src/data/kb/SCHEMA.md`, `scripts/validate-kb.mjs`, `scripts/smoke.mjs` | schema documented; validator enforces; existing KB green; smoke proves it bites |
| D-60 | **PianoLickCard** renderer — visual rhyme with LickCard's tab idiom but for keys (timeline + MiniPiano-style pitches or a degree ladder — pick per readability at strip size); playback via chordAudio like BassPatternCard. Must export `PIANO_TECHNIQUE_VOCAB` set-equal to the validator's (the dormant C-60 smoke guard activates on this file's existence). **Component only — LicksStrip wiring deferred (JamGuide.jsx reserved for one-screen L-50); wiring folded into C-61-era integration. Wiring DoD must include (D-60 gate): a shared sequencer-stop so BassPatternCard/PianoLickCard previews can't layer (each holds a private currentSeq today — impossible to trigger until both mount together)** | design | done | C-60 | `src/components/PianoLickCard.jsx` (new) ONLY — done `a7dbd6a` | renders every P-60 lick; playable; graceful empty/malformed |
| P-60 | Piano licks pack #1: 3-4 each for **jazz + blues** piano (C-60 schema; arithmetic-verified prose, the P-41 bar). **PASS first try** (`70263e2` — gate realized all 8 in two keys, 35/35 prose claims; dom7-heavy blues coverage accepted as musically right) | content | done | C-60 | `src/data/kb/jazz/piano.js`, `src/data/kb/blues/piano.js` | validator green; every lick playable as written |
| P-61 | Piano licks pack #2: 3-4 each for **gospel + rnb** piano. **Returned once (2026-07-11):** one false interval claim in rnb-quartal-slip tips ("each move a step" — actually skip-then-step); 42/43 checks passed. Fixed with the gate's own prescribed wording; Maestro verified the line against the gate's computed −3/−2 motion (scoped re-gate waived — the fix IS the gate's pre-approved example) | content | done | P-60 | `src/data/kb/gospel/piano.js`, `src/data/kb/rnb/piano.js` | validator green |
| P-62 | KB voicings expansion: **pop piano cell** (next per kb-backlog) + audit `GUITAR_SHAPES` for qualities with <3 placeable shapes and author additions where idiomatic | content | done | — | `src/data/kb/pop/piano.js`, `src/data/kb/index.js`, `src/lib/voicings.js` (additive shapes only) | validator green; every new shape hand-verified (L-23 lesson: base-fret convention) |
| D-61 | **Circle of fifths** in the Knowledge Center: interactive SVG (12 keys around the circle, live/locked key highlighted, relative minor ring, diatonic chords on tap-free hover/inline, key-signature glyphs), mounted inside ExploreSection; educational copy in the app's voice. **Returned once (2026-07-11):** modal hub key signature wrong (A dorian showed ♮ while its chips show F♯ — all 4 dropdown modal modes affected; fix = derive the parent-major signature, teaching "A dorian = G major's notes") + preview flat/sharp spelling split; geometry/rings/tiers/a11y all passed 60/60. Fixed + re-gate PASS (`c999c95` — hub computed from SCALES set-equality, 72/72 uniqueness, one spelling authority) | design | done | — | `src/components/CircleOfFifths.jsx` (new), `src/components/ExplorePanel.jsx` (ExploreSection integration only) | renders any key incl. none-detected; follows effectiveKey live; tokens + AA; zero clicks for the core view |
| D-62 | **Jam Roulette UX spec** (doc): button up top ("Jam roulette", controls bar per one-screen's layout), genre picker (the 10 KB styles), randomization rules (random key; "interesting" progression selection — weight intermediate level + variety, avoid repeats within a session), how the seed fills the screen (seeds the committed loop + locked key so the banner/strip, voicing rail, licks, related progressions all populate exactly as if detected — reuse, don't duplicate), and the **detection handoff**: musicians start playing and live detection continues from the seed (spec the interplay with the L-31 commit layer: the seed behaves like a committed loop — replacement needs the same consistent evidence; New Song clears it) **Returned once (2026-07-11):** "matched by construction" false for 10/56 progressions — buildLoopIndex indexes RAW degrees while collapse changes name-sequence length (incl. blues-12bar: LIVE detection of a real 12-bar gets an empty JamGuide — pre-existing bug surfaced!); detector len-cap 8 makes collapsed 9-10 unconfirmable; round-trip empirical not constructional (blues-8bar loses to its own [V7,I7] alternation); funk-one-chord collapses to 2 names not 1; flag-1 timeline off by one (EVERY seed dies unguarded — guard even more necessary). Maestro direction: fix (a) — additively index collapsed forms in match.js (fixes live detection too); pool = empirical round-trip passers ∩ collapsed len 2-8. **Returned twice (2026-07-11):** round 2 verified fix-(a) construction/timeline/proofs sound but found the sweep protocol as written evicts all 8 two-name vamps (needs steady-state + all-offsets), behaviour-additive false in exactly one case (country-145 now self-attributes — an improvement, enumerate it), census 11 not 10, and the BASS plays path (BassGuideRows :530/:533) missed by the remap. Round-3 PASS (`see commit` — literal protocol implementation reproduced 4/56 failures + 52/56 pool + 11-census exactly; sourceIndex mechanism verified at current line numbers) | design | done | — | `docs/design/jam-roulette.md` | mechanism spec'd against the real App state (lockedKey, detectedProgression, progressionVoteRef); no state duplication; L-60 scope bounded |
| L-60 | Implement Jam Roulette per D-62 (App.jsx UI-state + seeding fn; the roulette button + genre menu; 🚨 audio contract grep-gated — seeding touches display state and the progression refs ONLY, never callbacks). Ride-alongs: pass `dense` to the heard-live VoicingBrowser in the rail (D-51 gate flag — same fragile pair fit in the fallback path); one-screen.md §7 "two-octave cells alone" sentence is stale by a few px (D-51 gate nit) | engineering | done | D-62, C-50 | `src/App.jsx` (+ per D-62 — re-lock at promotion) | one click → genre → key+progression fills the jam dashboard; detection continues from it; New Song clears; build + smoke green; contract grep clean |
| C-61 | Sprint-end sweep + PR #3 update (COMBINED — covers BOTH sprint-one-screen and sprint-roulette-kb in one comment) | quality | done | L-60, D-60, P-61, P-62, D-61 | (none — verification) | all green; PR updated — **BOTH SPRINTS COMPLETE** |

> Sequencing: C-60 ‖ P-62 ‖ D-61 ‖ D-62 are file-disjoint and ready now (none touch one-screen's locks). Then C-60 → {D-60, P-60 → P-61}, and D-62 + C-50(one-screen) → L-60. C-61 closes. Critic gates every task.

---

## Active sprint: `sprint-one-screen` (branch: `sprint-jamguide-piano` — continued; commits extend PR #3)

**Goal (user directive 2026-07-11, after testing integrated-glance — "i like it a lot, so i would like to make some alterations"):**

1. **The top block is too big** — "the chord view and the chord loop incl the instrument below is too big, it takes up too much space… this main view doesnt really add value." Compress the detection area and the instrument view.
2. **Kill the big now-playing chord** — "seeing the big chord 'now playing' in big is also unnecessary as you already see that one in the chords (loop) part." The loop chips already carry it (playhead chip).
3. **Rethink ProgressionSuggestions** — "the suggested progressions on the right are also almost useless… we need to rethink them in the form of SUGGESTED VOICINGS and the progressions we add relating to the loop." Replace the generic genre table with (a) the suggested-voicings rail and (b) KB-sourced progressions RELATED to the detected loop.
4. **Voicings on the right** — "i'd like to have the suggested voicings on the right side of the screen (the jam guide)." The Jam Guide rail becomes a right column. (Overrides D-40's rejected side-by-side alternative — user directive wins.)
5. **One screen** — "i wanna see the chords, the loop, the voicings, the progressions, the licks all on one screen, everything you need to follow the jam correctly."
6. **Jam mode / fullscreen (refinement 2026-07-11):** "it can still have a scroll down, but the main concept needs to be in one screen: potentially we add a fullscreen button so we can just show that when we are playing a jam… everything that is learning and behind the scenes etc you can keep below/scrollable but we need one main view for live jams where we see everything at once." The dashboard fits 100vh as a hard constraint; a fullscreen/jam-mode button shows only it; KnowledgeDock/Debug/Tuner (LoopStation per Muse's call) live below in normal mode.

Standing principles (memory): scroll > click; nothing duplicated; one global instrument selector; playhead highlights, never hides. Weights: Muse 3, Luthier 3, Critic gate. No cron this sprint — Maestro drives on notifications. ⚠️ Spend-limit risk: the account limit was hit 2026-07-10; first dispatch is the probe.

| id | title | domain | status | depends-on | files (lock) | definition of done |
|----|-------|--------|--------|-----------|--------------|--------------------|
| M-06 | Seed `sprint-one-screen` | maestro | done | — | `docs/agents/LEDGER.md` | seeded |
| D-50 | One-screen dashboard concept doc: full-viewport grid at 1280×900 — LEFT: slim loop strip (banner without the big now-playing chord; playhead chip is the "now"), compact instrument view (decide the mechanism: height cap / reduced fret range / scale — honest about legibility), licks strip, related-progressions card; RIGHT column: the suggested-voicings rail (GlanceRail adapted to ~420-520px — decide cell strategy: recommended voicing prominent + gallery wrapped, sizes recomputed honestly per the D-40 corrected math); where LoopStation/collapsibles/KnowledgeDock land; what "related progressions" means computationally (KB-sourced: same style + level + degree-shape overlap with the loop — spec the ranking); narrow-viewport plan (columns stack); migration order with bounded L-50/D-51/L-51 scopes; ≥2 rejected alternatives. No user gate: pick strongest, record rationale. **Returned once (2026-07-11):** strip really ~92-96px (fix: spec p-4→p-2 trim); `sticky` rail geometrically inert in the flex row (zero travel — flick claims deleted, rationale re-grounded); §4 double-counted cell padding (piano pair actually fits TODAY by ~4px; D-51 premise → margin-hardening); +100 ranking term inherits the raw-vs-collapsed mismatch (collapse pairs before canonicalDegrees/T(p), coordinate with D-62 fix (a)). Layout/jam-view/scopes all held. Fixed + re-gate PASS (`d24bf35` — all budgets recomputed clean) | design | done | — | `docs/design/one-screen.md` | every component named incl. what's removed (big chord display, ProgressionSuggestions); honest space math (everything visible at 1280×900 with at most the licks/progressions row below a fold-flick); bounded scopes |
| L-50 | App restructure per D-50: grid columns, banner slimmed (big now-playing chord removed — ProgressionBanner edit), JamGuide rail moves to the right column, ProgressionSuggestions unmounted (file kept), instrument view compacted per doc, licks strip placed per doc. 🚨 App.jsx = layout/mounts/UI-state only, audio contract grep-gated. **PASS** (`800fd43`+`e58a7a6` — SSR 73/73; gate applied a sanctioned one-class legend restoration for non-compact byte-identity). Nits filed: dead currentChord prop on the banner mount (ride-along for L-51); strip chips are div-onClick (pre-existing a11y debt → backlog) | engineering | done | D-50 | `src/App.jsx`, `src/components/ProgressionBanner.jsx`, `src/components/JamGuide.jsx` (+ per doc — re-lock at promotion) | one-screen layout live; no duplicated info; jam view works both layers; build + smoke green; contract grep clean |
| D-51 | Rail right-column adaptation per D-50: GlanceRail/VoicingBrowser sizing for the narrow column (recommended-first cell strategy per doc), playhead highlight preserved, wrap never horizontal-scroll | design | done | D-50, L-50 | `src/components/GlanceRail.jsx`, `src/components/VoicingBrowser.jsx` (+ per doc) | rail readable at column width for guitar/piano/bass; zero clicks; build + smoke green — **PASS** (`see commit` — arithmetic verified to the built CSS; heard-live dense-pass flag folded into L-60's ride-alongs) |
| L-51 | RelatedProgressions component per D-50: KB-sourced, loop-relative (style + level + degree-shape overlap ranking per the doc's spec), replaces the old generic suggestions; mounts in the D-50 slot. **PASS** (`see commit` — scores recomputed independently, collapse counterfactual proven). Taste flag for the user: "same changes" links quality-mismatched same-root-motion relatives (ii-V-I ≡ I-IV-V) — spec-conformant; a "same motion" wording tweak is a one-word option | engineering | done | D-50, L-50 | `src/components/RelatedProgressions.jsx` (new) + mount slot per doc | shows genuinely related KB progressions for a live loop; honest empty state; build + smoke green |
| C-50 | Sprint-end sweep (PR comment consolidated into C-61's combined summary — Maestro call). Folded (L-51 gate): pin the two verified ranking scores + the no-collapse counterfactual as smoke checks; add a global-id-uniqueness assertion to validate-kb.mjs | quality | done | L-50, D-51, L-51 | `scripts/smoke.mjs`, `scripts/validate-kb.mjs` (additions only) | all green; ranking pinned; ids asserted unique — **PASS** (`see commit` — smoke 886, SSR 60/60, whole-sprint contract clean). **SPRINT sprint-one-screen COMPLETE** |

> Sequencing: D-50 first (everything hangs off it) → L-50 → D-51 ‖ L-51 (re-lock at promotion to enforce disjointness) → C-50. Critic gates every task.

---

## Active sprint: `sprint-dashboard-polish` (branch: `sprint-jamguide-piano` — continued; commits extend PR #3)

**Goal (user directive 2026-07-13, after running the one-screen build):** "this looks amazing" + six refinements to the live dashboard:

1. **Guitar: 4 options max** — "only have 4 guitar options visible so it would fit the screen without scrolling." Cap each chord's guitar gallery to ≤4 shapes (a musically-sensible top-4 selection rule; fewer is fine).
2. **Hidden-but-scrollable dark scrollbars** — "the scroll bars are not visible but it can scroll if we need to (make them black or something)." Thin/dark styled scrollbars, overlay feel; content still scrolls.
3. **Piano: 2×2 smaller keyboards** — "smaller keyboards so there would be 2×2 for each chord." The 4 pianoVoicing styles in a 2-col × 2-row grid of compact MiniPianos per chord.
4. **Remove play buttons** — "leave off the PLAY buttons, they take up a lot of space for no reason, no need to hear it." Drop every ▶ from the voicings rail (and the licks strip — Maestro extension, matching the stated glance-over-audio preference; also removes the shared-sequencer blocker so piano licks can wire in cleanly).
5. **Licks: uniform size + follow the instrument** — "the licks section should all have the same size and transform into piano when selected." Uniform card size; the strip follows the global GUITAR/PIANO/BASS selector — wiring the deferred PianoLickCard into the strip (bass → no licks / honest note).
6. **Related progressions: same-style bridge/chorus/modifications** — "i would also want bridge/chorus/modifications in the same style… say i select jam roulette with blues, then i want for that progression other options and not necessarily go into other styles." Same-style variations for the active/rolled loop. **User decided 2026-07-13: reuse existing KB same-style progressions (lightest option) — NO computed modifications, NO new authored sections, NO song forms (verse/chorus/bridge — KB has no section data and most progressions are complete forms; Maestro + user agreed not to fake it).** So L-72 ships the honest same-style variations. **NEW feature added same day** (user: "imagine we play Am C F, i'd like an alternative to that F… an option that says 'try this'… i want musicians to learn how they can make the jam more interesting"): a **per-chord "Try this" chord-substitution** surface — for a loop chord in the detected key/style, suggest a small curated set of alternative chords (relative sub, borrowed-minor colour, extension colour, a circle-of-fifths / secondary-dominant move) each with a plain-language WHY. Circle-of-fifths-informed where it applies (honest that not all subs are circle-adjacent). Keep it simple/learnable, not a reharm engine. → tasks D-73/L-73/L-74 below.

Standing principles (memory): scroll > click; nothing duplicated; one global instrument selector; playhead highlights; glance over audio. User is PRESENT — notification-driven, no cron. Weights: Muse 3, Luthier 3, Critic gate.

| id | title | domain | status | depends-on | files (lock) | definition of done |
|----|-------|--------|--------|-----------|--------------|--------------------|
| M-08 | Seed `sprint-dashboard-polish` | maestro | done | — | `docs/agents/LEDGER.md` | seeded |
| D-70 | Rail + licks layout concept doc: (a) guitar ≤4 shapes with the selection rule (open + common movable, lowest-position-first — name it); (b) piano 4 voicings as a 2×2 grid of compact MiniPianos — pick the MiniPiano thumb scale that fits two side-by-side in the ~456px column interior and two rows within a sane row height, honest math; (c) all ▶ removed from the rail's guitar+piano cells AND the licks strip (name every removal site); (d) hidden-but-scrollable scrollbars — the mechanism (webkit ::-webkit-scrollbar thin + dark thumb, and Firefox scrollbar-width/color; overlay where supported) and WHERE it applies (the rail's overflow-y column, any inner scrollers); (e) licks: uniform card footprint (thumb size parity between LickCard and PianoLickCard) + the strip follows the global instrument (guitar→piano licks on PIANO; bass honest empty); (f) recompute the row heights + the 500px column budget with the new smaller cells; migration order with bounded L-70/L-71 scopes (keep them file-disjoint or serialize on JamGuide.jsx). No user gate: pick strongest, record rationale + ≥2 rejected alternatives | design | claimed | — | `docs/design/dashboard-polish.md` | every change specced with honest numbers; the 2×2 piano scale chosen + proven to fit; bounded impl scopes |
| L-70 | Implement the voicings rail per D-70: guitar ≤4, 2×2 smaller piano, ▶ removed, dark hidden scrollbars | engineering | backlog | D-70 | `src/components/GlanceRail.jsx`, `src/components/VoicingBrowser.jsx`, `src/index.css` (scrollbar CSS) (+ per doc — re-lock at promotion) | rail matches the spec; no ▶; guitar ≤4; piano 2×2; scrollbars hidden+dark+functional; build + smoke green |
| L-71 | Implement the licks strip per D-70: uniform card size, follow the global instrument (wire PianoLickCard), ▶ removed | engineering | backlog | D-70, L-70 | `src/components/JamGuide.jsx` (LicksStrip), `src/components/LickCard.jsx`, `src/components/PianoLickCard.jsx` (+ per doc — re-lock at promotion) | licks uniform; piano licks show under PIANO; no ▶; bass honest; build + smoke green |
| D-72 | Same-style related-progressions design (user answer = reuse existing KB): decide the presentation — when a style is active, RelatedProgressions leads with same-style siblings reframed as "variations / sections to try in {style}" (labels/section framing that reads as bridge/chorus/variation without new content); how the active style is known (roulette seed carries it; live detection's match yields it — name the prop/source); whether cross-style entries stay as a secondary "other styles with these changes" section or are dropped when a style is locked; empty/edge states. Files disjoint from the rail/licks chain (RelatedProgressions.jsx only) → runs in PARALLEL | design | done | — | `docs/design/related-same-style.md` | presentation spec'd against the real RelatedProgressions/match.js; active-style source named; L-72 bounded |
| L-72 | Implement D-72: RelatedProgressions leads with same-style variations when a style is active; reframed labels; cross-style demoted/dropped per the doc. **Unblocked 2026-07-13** (no song forms). Resolutions: (finding-A) when a style IS locked, DROP the secondary cross-style section — same-style only, per goal #6 + the user's "not necessarily other styles"; keep cross-style ONLY when no style is locked (match.matched===false). (finding-B) null the role phrase when a same-style sibling shares NO transitions with the loop (no false "variation" claim — just name + level). Re-pin smoke §8 labels ('shares I7→V7'→'shorter form' etc.; scores stable) | engineering | claimed | D-72 | `src/components/RelatedProgressions.jsx`, `scripts/smoke.mjs` (§8 re-pin) | same-style-only when locked; honest cross-style only when unlocked; role phrase honest (finding-B); build + smoke green |
| D-73 | **"Try this" chord-substitution feature** design (engine rules + UI): for a loop chord `{rootPc,quality}` in the detected key/style, a SMALL curated set (~3-4) of alternative chords — recommend the categories (relative/diatonic-third sub, borrowed-minor colour e.g. IV→iv, extension/colour e.g. maj7/add9/sus, a circle-of-fifths / secondary-dominant move) — each with a plain one-line WHY that teaches. Concrete worked examples for the user's Am–C–F case (both A-minor and C-major readings), arithmetically correct (the gate WILL recompute). The UI surface: where it mounts in the jam dashboard (per-station in the rail? a "Try this" line under the loop? — decide, keep it glanceable, tappable→ChordDetailModal), honest circle-of-fifths tie-in (a lens for the relative/neighbour subs, NOT claimed for borrowed/extension), edge states (no key / atonal), keep-it-simple (learnable, not a reharm engine). Use a SEPARATE component (disjoint from RelatedProgressions.jsx). No user gate: pick strongest, ≥2 rejected alternatives | design | ready | — | `docs/design/try-this-subs.md` | substitution categories + rules + why-copy spec'd and arithmetically honest; Am–C–F worked; UI surface + mount decided; L-73/L-74 bounded |
| L-73 | Substitution engine: `suggestSubstitutions({rootPc,quality}, keyInfo, opts?)` in `src/lib/theory.js` (additive) → `[{rootPc, quality, label, why, category}]` per D-73's rules + a smoke truth-table (expected subs for known chords/keys, sabotage-proven like the resolveDegree guard) | engineering | backlog | D-73 | `src/lib/theory.js` (additive), `scripts/smoke.mjs` | engine returns musically-correct subs for all 14 qualities in-key; smoke truth-table bites; build green |
| L-74 | "Try this" UI per D-73: new component consuming the engine, mounted in the jam dashboard per the doc; tappable→ChordDetailModal; tokens/AA; glanceable | engineering | backlog | D-73, L-73 | `src/components/TryThis.jsx` (new) + mount (per doc — re-lock) | try-this shows honest subs with why-copy for the live loop; zero-click; build + smoke green |
| C-70 | Sprint-end sweep + PR #3 update (covers rail/licks + related + try-this) | quality | backlog | L-70, L-71, L-72, L-74 | (none — verification) | all green; PR updated |

> Sequencing (three parallel chains, file-disjoint): (A) D-70 → L-70 → L-71 (rail/licks); (B) D-72✓ → L-72 (related, same-style); (C) D-73 → L-73 → L-74 (try-this subs — theory.js + new TryThis.jsx, disjoint from A/B). C-70 closes after all. Critic gates every task.

---

## Shipped sprint: `sprint-integrated-glance` (branch: `sprint-jamguide-piano` — complete 2026-07-10, 4 iterations, PR #3 updated)

**Goal (user directive 2026-07-10, after testing the glance-mode sprint — "its already a lot better, but"):**

1. **Move it up & integrate** — "i would like to move it up and integrate it somewhat in the space of the main module up top. or maybe right below it as it is super important." The Jam Guide content is currently the LAST mount in App.jsx (line ~712); the main module (ProgressionBanner + instrument view) is at ~609-622.
2. **One loop display** — "i would like to be able to see the loop clearly, no need to have this repeated again. as its already in the main module." ProgressionBanner (top) and the Roadmap track (inside JamGuide) both show the detected loop — exactly one clear loop display must survive.
3. **One instrument selector** — "only one selection for GUITAR/PIANO/BASS and everything should show in that instrument." App.jsx line 58 ALREADY holds global `instrument` state ('piano'|'guitar'|'bass') driving the main views; JamGuide ignores it and has its own internal tabs. Everything (voicings, licks, guide) must follow the one selector. Bass has a main-view BassFretboard but zero KB content — needs an honest state (and a stretch chain to make it real).
4. **All chords' voicings visible at once** — "it's difficult to see as it changes when the chord is not playing so i'd like to see all the chords and their voicings in a specific way in case a loop is detected (or at least just like 4 chords or something at least, so you can follow and potentially learn new ways to play it while you are playing the loop." The playhead accordion (only the active station expanded) is hard to follow live — show EVERY loop station's voicings simultaneously (≥4 chords), playhead highlights rather than reveals.
5. **Scroll-first** — "scrolling is easier then clicking." Layout principle for everything above.

**Loop:** `/jam-loop` every 30 min for 12 h (24 iterations), started 2026-07-10 evening (cron 13,43). "let each agent do a part and revise the work" — standard ensemble + gates. Weights: Luthier 3, Muse 3, Professor 2, Critic gate.

| id | title | domain | status | depends-on | files (lock) | definition of done |
|----|-------|--------|--------|-----------|--------------|--------------------|
| M-05 | Seed `sprint-integrated-glance`; schedule the 24×30m loop | maestro | done | — | `docs/agents/LEDGER.md` | seeded, cron created |
| D-40 | Integration concept doc: (a) where the Jam Guide content lands relative to the main module (in it vs right below — pick, record rationale); (b) which loop display survives (ProgressionBanner vs Roadmap track — the loser's unique value, e.g. guide-tone rails, must be honestly dispositioned); (c) JamGuide obeys App's existing global `instrument` — internal tabs retired; honest bass state until the bass chain lands; (d) the all-stations-expanded voicings layout (≥4 chords visible, playhead HIGHLIGHTS not reveals, scroll-first — the user explicitly licensed scrolling, so D-31's "everything expanded" rejection is overturned by directive); (e) what remains of the four-section dock below; (f) honest space math + migration order. No user gate: pick strongest, record rejected alternatives. **Returned once (2026-07-10):** piano worst-case Dm7-specific (G7 gallery ≈1,178px, roots above D need 2-octave crops → rows WRAP, screenful counts restated); LicksSection lives in JamGuide.jsx not ExplorePanel; ChordBox keeps importers (only RiffDiagram orphans); L-40 interim would ship the page-yank (GlanceRail scrollIntoView in page flow) — doc added the one-line effect deletion to L-40's lock, same-commit mandatory. Fixed + re-gate PASS (pixel-exact recompute: gallery 1,178, row 1,470, all table arithmetic verified) | design | done | — | `docs/design/integrated-glance.md` | doc names every component, the single-loop-display call, the instrument-threading contract, ≥2 rejected alternatives, bounded L-40/D-41 scopes |
| L-40 | App restructure per D-40: promote the Jam Guide content to the D-40 position; retire the duplicated loop display per the doc's call; JamGuide takes `instrument` as a prop from App's existing selector (internal tabs removed; VoicingsSection/LicksSection/ExplorePanel mounts follow too); honest bass empty-state. Ride-along (C-40 follow-up): add an export-only line to JamGuide's `resolveDegree` (JamGuide.jsx:73) so smoke §7 can sweep both copies directly (today it pins the validator side behaviourally). 🚨 App.jsx changes = layout/mount/UI-state ONLY — audio callbacks/refs/AudioCapture untouched (grep-gated) | engineering | done | D-40 | `src/App.jsx` (layout + UI-state only), `src/components/JamGuide.jsx`, `src/components/ExplorePanel.jsx` (VoicingsSection instrument prop), `src/components/GlanceRail.jsx` (scrollIntoView effect deletion ONLY, same commit as band promotion — D-40 §6.1) | one selector drives everything; guide content up top; loop shown exactly once; build + smoke green; audio contract grep-clean |
| D-41 | All-expanded voicings rail per D-40: every station's gallery visible simultaneously (≥4 chords), active station highlighted by the playhead (accent ring/header, NO reveal/hide), scroll-first reflow, licks strip follows the global instrument per the doc. **Folded (L-40 flag, 2026-07-10):** VoicingBrowser's `show` prop falls through unknown values to 'both' — the dock's VoicingsSection under bass passes show='bass' and renders BOTH galleries, violating D-40 §3 prose; fix in VoicingBrowser (honest bass empty-state or explicit 'bass' handling), lock extended accordingly. Also: bass row headers gain solo-scale/aim-dots here per D-40 row anatomy | design | done | D-40, L-40 | `src/components/GlanceRail.jsx`, `src/components/VoicingBrowser.jsx` (dense styling + show='bass' handling), `src/components/JamGuide.jsx` (row anatomy per doc) | all voicings of all loop chords on screen/scroll with zero clicks; playhead never hides content; AA + tokens; build + smoke green |
| C-40 | Drift guards in smoke (two hand-synced duplications, backlog debt): (a) JamGuide's `resolveDegree` copy vs validator's — 16-degree × 14-quality truth table; (b) technique vocab `validate-kb.mjs` LICK_TECHNIQUES ↔ `LickCard.jsx` TECHNIQUE_VOCAB. Ride-along: fix `scripts/loop-fixtures.mjs` header's stale "App.jsx:322-324" line-ref (C-31 sweep finding) | quality | done | — | `scripts/smoke.mjs`, `scripts/loop-fixtures.mjs` (comment only) | drift in either duplication turns smoke red (sabotage-proven); header ref current; smoke green |
| P-40 | Refresh `docs/kb-backlog.md`: mark all shipped cells done (gospel/pop guitar; jazz/gospel/rnb/blues piano), set the bass column as next (blues → jazz → funk per plan) | content | done | — | `docs/kb-backlog.md` | backlog reflects reality; next-cell pointer correct |
| C-41 | Bass play schema (additive): SCHEMA.md + validator + smoke bite-test for bass patterns (per-station patterns: root/fifth/approach-note walks — design the minimal honest shape with 4-string, fret 0-15 representation). Ride-along (C-40 gate finding): document in SCHEMA.md that `resolveDegree('7', maj6|min6)` = 9 (the 6th) in both hand-synced copies — a contract quirk, never misspells a chord, but authors should write `'6'` explicitly | quality | done | C-40 | `src/data/kb/SCHEMA.md`, `scripts/validate-kb.mjs`, `scripts/smoke.mjs` | schema documented; validator enforces; existing KB untouched-green; smoke proves the check bites |
| P-41 | First bass cell: **blues bass** (kb-backlog order blues → jazz → funk). C-41 gate notes: offset 11 (maj7 over qualities lacking it) is unrepresentable — if you hit it, STOP and report (schema extension, not workaround); approaches are terminal-only but mid-pattern passing tones are expressible as fixed degrees (b9/9/b3/11/b5/b13/6/b7 all covered) | content | done | C-41 | `src/data/kb/blues/bass.js`, `src/data/kb/index.js` | validator green; every pattern playable as written; prose arithmetic-verified |
| L-42 | Minimal bass pattern renderer for the guide (per-station BassPatternCard per D-40 §3; the computed root·fifth·approach line stays as fallback) — makes the BASS selector real end-to-end. C-41 gate note: `chrom-below` into a next root at open E has no pitch below the instrument — renderer must octave-displace the approach or place the root higher | engineering | done | C-41, P-41, L-40 | per D-40 doc — re-lock at promotion | bass stations render the authored patterns; guitar/piano paths untouched; build + smoke green |
| C-42 | Sprint-end sweep + PR #3 update per PROTOCOL §6. Folded (L-40 gate): wire smoke §7 to import JamGuide's now-exported `resolveDegree` (the jsx load hook already exists) and refresh the stale "unexported (checked 2026-07-10)" comment at smoke.mjs:855-862 — tiny Critic-owned fix inside the sweep | quality | done | L-40, D-41 | `scripts/smoke.mjs` (§7 wiring only) | all green; smoke sweeps both resolveDegree copies; PR updated |

> GATE 2026-07-10 (Critic) · **P-40 PASS** — validator totals verbatim-match; ALL 14 cell play-counts recounted from the real registry (sum 164 ✓); lick arrays counted per style (4/4/4/4/4/3 = 23 ✓); 10 dates cross-checked against ledger + git commits, all hold; diff +36/−15 confined, format preserved. Non-blocking: piano next-cell = pop is a priority call (plan's default ranks bossa first — doc licenses reorder, accepted as Professor's call); L-42 row omits its L-40 dep (one word, fix if touched).

> Sequencing: D-40 ‖ C-40 ‖ P-40 file-disjoint, ready now. Then D-40 → L-40 → D-41 (App/JamGuide/GlanceRail serialized — same surfaces), and C-40 → C-41 → P-41 → L-42 (bass chain, stretch). C-42 closes. Critic gates every task.

---

## Shipped sprint: `sprint-glance-and-loop` (branch: `sprint-jamguide-piano` — complete 2026-07-10, 4 iterations, PR #3 updated)

**Goal (user directive 2026-07-10):** three asks, in the user's words:

1. **All voicing variations in one view** — "in the music theory … see all the variations G shape, C shape, etc in one view without having to push a button. so they all line up next to each other." VoicingBrowser currently chip-switches one shape at a time; replace with a gallery that renders every shape/style side by side, each independently playable.
2. **Fix the loop logic** — "it doesn't recognize when 3 chords return, or when its 5 and then 2 others." `detectRepeatingProgression` (theory.js) does exact contiguous block matching, lengths 2–6: one spurious misdetected chord breaks a rep; a 5-chord loop followed by 2 foreign chords (tag/turnaround) is missed; 7–8-chord loops can't be found at all. Redesign to be noise-tolerant; tune the App.jsx commit layer so a committed loop survives fills.
3. **Glanceable learn session** — "expand the learn session so we see as much as possible in voicing variations and styles without having to click buttons, the idea is to have it open as the jam is playing." Knowledge Center should auto-follow the live jam with zero clicks.

**Loop:** `/jam-loop` every 30 min for 12 h (24 iterations), started 2026-07-10 (cron 13,43). User is away — no user-gated tasks; Muse picks the strongest option and records rationale. Weights: Luthier 3, Muse 3, Professor 1, Critic gate, Herald 0.

| id | title | domain | status | depends-on | files (lock) | definition of done |
|----|-------|--------|--------|-----------|--------------|--------------------|
| M-04 | Seed `sprint-glance-and-loop` from user directive 2026-07-10; schedule the 24×30m loop | maestro | done | — | `docs/agents/LEDGER.md` | sprint seeded, cron created |
| C-30 | Loop-detection truth fixtures: new `scripts/loop-fixtures.mjs` (+ smoke wiring) — realistic chord-history cases with expected loops: (a) clean 3-chord loop ×2–3 reps; (b) 3-chord loop with one spurious misdetection inside a rep; (c) consecutive duplicate commits of the same chord; (d) 5-chord loop followed by 2 foreign chords — loop must survive; (e) 7- and 8-chord loops; (f) section change loop-A→loop-B — detection must switch; (g) chromatic non-loop → null. Run each against the CURRENT `detectRepeatingProgression` and mark which fail today (`expectedFail: true`) so the suite is green now and L-30 flips the markers off | quality | done | — | `scripts/loop-fixtures.mjs`, `scripts/smoke.mjs` | fixtures runnable via smoke; failure map of the current algorithm documented in the fixture file header; smoke green (expected-fails annotated, not silently skipped) |
| L-30 | Redesign `detectRepeatingProgression` in `theory.js`: collapse consecutive duplicate chords before matching; tolerate ≤1 substitution/insertion per cycle (noise from misdetection); support loop lengths 2–8; recency-weighted scoring so an old section can't outvote the current loop; keep the pure signature, the rotation-`canonicalize` output, and every other export byte-untouched. Flip all C-30 `expectedFail` markers to passing. **C-30 findings folded in (2026-07-10):** (a) the `reps × len²` scoring artifact is the primary everyday bug — a clean 2-chord vamp ×4 reports a bogus 3-chord `[Am,Am,G]` via self-overlapping occurrences; candidate generation must reject candidates that are rotations/overlaps of a shorter period (not just exact periodic patterns at len ≥ 4); (b) 7/8-loops contain realistic non-adjacent internal chord repeats (`Am … Am`) — period detection must not collapse those into shorter false loops | engineering | done | C-30 | `src/lib/theory.js` (detectRepeatingProgression + private helpers only) | all C-30 fixtures green with zero expectedFail markers (incl. `vamp-2x4`/`vamp-2-sustained`); no other theory.js export changed; build + smoke green |
| L-31 | Commit-layer tuning in App.jsx's progression effect (lines ~154-173): a committed loop must survive transient misses (the miss-4-then-clear rule drops the loop during fills/turnarounds); a new loop replaces the old only with consistent evidence; first commit shouldn't need extra rounds when detection is already stable. **Primary trace case (C-30 finding, 2026-07-10):** pre-L-30 detection flaps between the true loop and ghost 4-patterns, so `progressionVoteRef` never sees 2 consecutive identical keys and nothing ever commits — verify post-L-30 stability makes the 2-vote commit reachable in the noisy fixtures, then tune (post-L-30 status: done). **L-30 gate findings folded in (2026-07-10):** (a) a 2-rep loop with one corrupted rep FLAPS through data-faithful sub-cycles commit-to-commit until the next clean rep — faster first-commit must NOT let a single transient sub-cycle detection replace an already-committed loop (replacement needs consistent evidence); note non-null detection resets the miss counter, so today's committed loop already survives the flap; (b) ride-along one-line fix: theory.js ~line 601-603 docstring overclaims "ghost occurs exactly once by construction" — false for same-chord-same-phase corruption (behavior fine, comment too strong); (c) deletions untolerated by design — fine at ≥3 reps, degrades to (a)'s flapping at exactly 2. 🚨 **Audio contract: touch ONLY the progression useEffect + its refs — no callback, ref-sync, or AudioCapture change** | engineering | done | L-30 | `src/App.jsx` (progression effect only) | loop survives fixture-style fills end-to-end (hand-trace documented in commit); App.jsx diff confined to the progression effect block; build + smoke green |
| D-30 | VoicingBrowser gallery: render ALL matching guitar shapes side by side (each = label + ChordDiagram + its own ▶) and all four piano styles side by side (label + MiniPiano thumb + ▶) — no chips, no selection state; grid wraps on narrow viewports; one-at-a-time playback preserved (new play stops the previous). All existing mounts (Voicings section, ChordDetailModal tabs, JamGuide station-enlarge) get the gallery automatically; `show` prop semantics unchanged | design | done | — | `src/components/VoicingBrowser.jsx` | every placeable shape/style visible simultaneously with zero clicks; AA contrast + tokens; keyboard-reachable play buttons; build + smoke green |
| D-31 | Glance-mode concept doc: the Knowledge Center panel open WHILE the jam plays — what auto-follows live detection with zero clicks (current station's voicing gallery inline? licks strip for the style? what stays on screen vs behind interaction), density/space budget vs the Roadmap track, narrow-viewport plan, and the migration order for L-33. No user gate: pick the strongest layout, record rationale + rejected alternatives. **Returned once (2026-07-10):** piano gallery width understated ~45% (2-octave crops for rootless 7th-chord voicings → ~940px not 640; scroll is the piano rail's normal state on most loops) + licks strip ~190px not 150 + 4 clarifying notes; layout itself held — fixed + re-gate PASS | design | done | — | `docs/design/glance-mode.md` | doc names every component to touch, the auto-follow rules, and honest space math (what fits at 1280×900 and at narrow) |
| L-33 | Implement glance mode per D-31 (`docs/design/glance-mode.md` — the "playhead accordion"): the station column at canonicalPos expands to the full D-30 gallery and advances with the playhead; licks strip below (level-filtered, token-boundary context match, strip-owned "fits — now" chrome, guitar licks under piano tab with honest heading); pin gesture inherits onFocusChord semantics; auto-follow never emits focus-chord and never auto-plays audio; piano rail EXPECTED to scroll (never assert one-row fits); follow the doc's 5-commit migration order | engineering | done | D-30, D-31 | `src/components/GlanceRail.jsx` (new), `src/components/JamGuide.jsx`, `src/components/VoicingBrowser.jsx` (optional additive `dense` prop only) | glance behaviours live per the doc; audio contract intact (App.jsx untouched or additive-only); build + smoke green |
| P-30 | KB expansion: **neo-soul/rnb piano** cell (next per `docs/kb-backlog.md` after jazz/gospel) | content | done | — | `src/data/kb/rnb/piano.js`, `src/data/kb/index.js` | validator green + registered; renders in the piano tab; prose claims arithmetic-verified |
| P-31 | KB expansion: **blues piano** cell. **Returned once (2026-07-10):** turnaround jump-kicks bars 3-4 dyad orientations flipped (breaks the play's own only-leap law — gate pre-verified the exact swap) + 2 prose rewordings (♭7 "unmoved all night" contradicted by bar 9; "octave-and-change" actually a major 7th) — fixed + re-gate PASS | content | done | P-30 | `src/data/kb/blues/piano.js`, `src/data/kb/index.js` | validator green + registered |
| C-31 | Sprint-end sweep: full build + validator + smoke + fixtures, then update PR #3 (push + PR body/comment summarising this sprint) per PROTOCOL §6 | quality | done | L-31, L-33, D-30 | (none — verification) | all green; PR #3 updated |

> Sequencing: C-30 ‖ D-30 ‖ D-31 ‖ P-30 are file-disjoint and ready now. Then C-30 → L-30 → L-31 (serial chain on theory.js/App.jsx), and D-30+D-31 → L-33. P-31 fills spare iterations. Critic gates every task.

> GATE 2026-07-10 (Critic) · **D-30 PASS** (`0945a08`) — scope confined to VoicingBrowser.jsx; build green, smoke 809/809, validator green. SSR of the real component (Cmaj7/G7/Asus2/F#m7 × show variants): guitar cell count === independently re-derived matchingShapes count (4/4/3/3), all four piano labels honest, ZERO chips/aria-pressed (button count === cell count), every ▶ aria-labeled chord+voicing, rootPc badge guard proven (rootless voicings show 0 R badges, root/shell exactly 1; buggy variant rendered + diffed = absent), show prop scoping correct, E-add9 empty state graceful. Playback single-handle verified by read. *Non-blocking:* thumb-size diagrams drop finger numbers → backlogged.

> GATE 2026-07-10 (Critic) · **C-30 PASS** — smoke 809/809 exit 0 with 8 annotated expected-fails (printed with today/contract values, never skipped); validator + build green. Failure map independently recomputed against the real function: vamp-2x4 self-overlap 2×3²=18 > pair 4×2²=16 → bogus `[Am,Am,G]` ✓; spurious-substitution ghost 4-pattern 2×16=32 > true 3×9=27 ✓; section-change stale-A 48 > 27 ✓; five-plus-tag genuinely passes pure-function (L-31 attribution honest) ✓. Sabotage ×3: broken guard → exit 1 named; stripped expectedFail → exit 1; stale marker → exit 1 with explicit STALE message — none can rot silently. `canonicalLoop` replica byte-equivalent to theory.js's private `canonicalize` + load-time self-check. clean-2x3 vs vamp-2x4 pins the exact boundary where the len² artifact kicks in. Nit (non-blocking): fixture header line 25 says `[Am,G,Am]` vs comment `[Am,Am,G]` — same loop pre/post canonicalization. NOTE: the transient smoke red the D-31 gate reported (`clean-3x3`) was this gate's own sabotage edit (a) running concurrently — tree restored, verified clean.

> GATE 2026-07-10 (Critic) · **D-31 RETURNED → PASS on scoped re-gate** — first gate verified the playhead-accordion layout, the App.jsx-zero-changes claim (all 7 props already flow; pin inherits selectedStation semantics incl. reset effects), chordContext data support, and the vertical budget (256px at 1280×900 recomputed from real component geometry) but returned one arithmetic finding: expanded piano gallery ~640px understated ~45% (rootless A/B of true-7th chords → 2-octave D-24 crops = 266px thumbs) + licks strip 150→190px. Muse fixed: 940 / 1350–1550 (scrolls at ~1200, not "borderline") / 2300 / 3,500+ / 190 / 1070, "scroll is the piano rail's normal state on most loops", commit-5 must never assert one-row-fits, + 4 notes (token-boundary chordContext match; strip-owned "fits — now" chrome; guitar licks under piano tab stated; licksFor "lifted not reused"). Re-gate recomputed Dm7 rootlessA [17,21,24,28] → 266px from piano.js/MiniPiano.jsx, grep-verified no stale figures, no regression to passed sections. Nuance (non-blocking, for L-33): "any true-7th chord" slightly overgeneral — some rootless voicings fit 1 octave (e.g. Dm7 rootlessB); 940 is the correct conservative worst case.

> GATE 2026-07-10 (Critic) · **P-30 PASS first try** (`b51daa1`) — validator 154 plays green, build green, shared-tree smoke 817/817. Independent arithmetic (own script from SCHEMA/CHORD_TYPES, not Professor's): max per-hand span 11 (≤15), ≤4 notes/hand, combined span ≤23 → every recipe renders for all 12 roots in the 36-key window (zero silent fallbacks); 11 hand-picked prose claims all recompute exactly (♭VI→V sink 1,1,1,1,2; 9sus RH = major triad on ♭7; So What [0,5,10,15,19]; I-RH ≡ vi7-RH pcs; 13sus = Type A bottom+1; ...). Rule-4 idiomatic difference genuine per pair; outside-key 9-on-iii honestly flagged; sources hedged. One chased-and-accepted oddity: ♭VI13 stab's C–F–G♭ pinch is deliberate voice-leading, not a stacking bug.

> GATE 2026-07-10 (Critic) · **L-30 PASS** (`4cc0009`) — smoke 817/817 (all 17 fixtures plain guards, 9 original greens no regression), validator + build green. Diff confined: theory.js 2 hunks in the repeating-progression section only, `canonicalize` unchanged context; loop-fixtures.mjs = marker removals + historical rewordings, every history/expect array untouched (frozen contract intact). 15 novel generalization probes: 11 correct, 3 data-faithful (only phase-locked exactly-alternating corruption yields a doubled pattern — genuinely 6-periodic data no algorithm could distinguish), 1 better-than-expected (all-reps-edited loop found via clean rotation). Every reportable "ghost" now corresponds to a pattern that exactly recurred — the old structurally-fake ghosts are gone. Perf 0.21–0.73 ms/call ×1000. Consumer contract at App.jsx:154-173 type-matches. 3 non-blocking findings folded into L-31's row.

> GATE 2026-07-10 (Critic) · **L-33 PASS** (`see commit`) — build green, smoke 817/817, validator green; diff exactly the 3-file lock (App.jsx/theory.js diffs attributed to L-31). SSR of the real JamGuide 37/37: expanded-column cell counts == independently derived shape counts (dom7@G=4, min7@D=4), aria-current one-per-list, mic microcopy exactly once (dense suppresses per-mount copies), pin semantics diffed line-by-line identical to old selectedStation (deps + emit/clear), GlanceRail imports no chordAudio + auto-follow has zero focus/audio side effects (scrollIntoView reduced-motion-guarded), heard-live + nothing-heard paths correct. Token matcher proven through the real render on a hostile country I-vs-I7 case (3 fits not 4) + full 23-lick chordContext audit — no false positive possible; exotic glyphs (♯/°/ø) outside the char class fail conservative (miss, never false-fit). `dense`-less VoicingBrowser SSR **byte-identical** to committed 0945a08. Zero-click audit: all variations + all licks in initial markup. Deviations accepted (staging cosmetic; heard-live licks strip musically right). Non-blocking: first-mount scroll on unknown playhead benign; LicksStrip perf fine unmemoized. Committed `c78baf4`.

> GATE 2026-07-10 (Critic) · **L-31 PASS** (`ce24d54`) — 🚨 audio contract CLEAN: 1 hunk in the progression useEffect, contract-grep zero hits, no ref-sync/mount props; theory.js ride-along comment-only (verified zero executable lines). Independent re-trace with own mirror + real detector: spurious-substitution commits #7 zero drops; section change replaces with zero intermediate nulls; breakdown first-null at foreign-commit 28 (comment's "~28" numerically exact) and clears at exactly 6 nulls; 2-rep flap survives with rival counter provably reset. HOSTILE-1 proved REPLACE_VOTES=3 load-bearing (rival hit 2 before agreement reset — 2 would displace-and-flap); alternating rivals never accumulate; newSong null-shape defensively handled. Build/smoke 817/817/validator green. 2/3/6 judged musically sensible; v-a commit-then-upgrade UX accepted over blank display. Non-blocking: "consecutive" comment nuance (nulls are neutral, not resetting — amend to "consecutive non-null" if ever touched); threshold consts re-created per render (trivially cheap, hoisting would exceed the lock); Luthier's +53/−12 was arithmetic sloppiness for +47/−6.

> GATE 2026-07-10 (Critic) · **P-31 RETURNED → PASS on scoped re-gate** (`81cd230`) — first gate verified 90%+ of the pack exactly (♭VI7→V7 five-voice plane numerically perfect; all spans 11/4/22 legal; all 96 recipes render for 12 roots; rule-4 contrasts genuine) but returned 3 findings: jump-kicks bars 3-4 dyad orientations flipped (broke the play's own only-leap law; gate pre-verified the exact swap), "unmoved all night" contradicted by bar 9's own note, "octave-and-change" = actually a major 7th (11 semitones). Professor applied all 3; re-gate recomputed the lap chain from scratch: E4,B♭4 → G4,D♭5 → F4,C5 → F4,B4 → E4,B♭4 — "only leap" now provably unavoidable (all pairwise pc distances = 3), validator 164 plays / smoke 817/817 / build green, no fourth edit area.

---

## Shipped sprint: `sprint-knowledge-center` (branch: `sprint-jamguide-piano` — complete 2026-07-09, PR #3)

**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 | done | 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 | done | 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 | done | 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 | done | 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 | done | 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 | done | 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 `<JamGuide …/>` 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 `<Fretboard>` mount, (c) `onFocusChord={setJamFocusChord}` on the existing `<JamGuide>` 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 `<Fretboard>` + `onFocusChord=setJamFocusChord` on `<JamGuide>`); 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) <VoicingPiano/> else <LegacyPiano/>`. 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 `<MiniPiano rootPc lh rh />` 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).
- **Drift guards in smoke (Critic, tiny):** two hand-synced duplications need permanent smoke checks — JamGuide.jsx's copy of the validator's `resolveDegree` (fold in the L-24 gate's 16-degree × 14-quality drift test) and the 8-word technique vocab duplicated between `validate-kb.mjs` `LICK_TECHNIQUES` and `LickCard.jsx` `TECHNIQUE_VOCAB` (C-21 finding).
- **`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).
- **`docs/kb-backlog.md` is stale (Professor, tiny):** still lists gospel/pop guitar + jazz/gospel piano as "todo" though shipped; P-30 (rnb piano) AND P-31 (blues piano, both 2026-07-10) need done entries — refresh next content iteration (flagged by P-30 + C-31 sweep, outside their locks).
- **`scripts/loop-fixtures.mjs` header comment drift (tiny):** cites "App.jsx:322-324" for adjacent-dup suppression; L-31 shifted it to ~line 364 — comment-only, fix when the file is next touched (C-31 sweep finding).
- **ProgressionBanner strip chips are div-onClick (Muse, small):** pre-existing a11y debt — history/loop chips should be real buttons (L-50 gate 2026-07-11; RelatedProgressions' chips already show the pattern to copy).
- **Test-infra note (any agent writing throwaway SSR harnesses):** a bare `import('esbuild')` inside a `data:` module hook dies silently (ERR_MODULE_NOT_FOUND in the hooks thread, exit 1, zero output) — smoke.mjs:1047's interpolated `import.meta.resolve` pattern is load-bearing; prefer esbuild pre-bundle harnesses (confirmed empirically twice, 2026-07-11).
- **GUITAR_SHAPES self-audit in smoke (Critic, small — P-62 gate suggestion 2026-07-11):** pc-spell every GUITAR_SHAPES entry against its quality in smoke — the validator audits only KB chord steps, never the shape library itself; would have caught both wrong shapes below and guards future additions.
- **Pop piano prose uses absolute note names (Professor, tiny — P-62 gate flag):** ~8 spots (pop/piano.js:88,117,130-133,159,186) say e.g. "the G holds; top C→B" — correct only in the declared reference key; degree-ify before any piano-prose render surface lands (sibling of the bass-tips-surface backlog item).
- **Two wrong GUITAR_SHAPES in live data (Luthier, small — P-62 audit finding 2026-07-11):** (1) `half_dim "E Barre"` offsets `[0,1,2,0,0,'x']` sounds {0,3,6,7} — natural 5th clashing the ♭5, NO ♭7; correct E-form ø7 = `[0,1,0,0,'x','x']`. (2) `add9 "Open Dadd9"` (xx0230 = D-A-D-E) has no 3rd — it's Dsus2, duplicating the sus2 open D. Both pre-existing; fix after P-62 commits (additive lock kept P-62 from editing them).
- **Matcher blues/jazz disambiguation (Luthier, small):** `['C7','A7','Dm7','G7']` matches `jazz-rhythm-a` (identical I–VI–ii–V degree shape, quality tie → jazz), so `blues-turnaround`'s bass plays are live-unreachable — pre-existing tie-break in match.js; consider quality-profile weighting (all-dom7 → blues). Evidence-verified by the L-42 gate 2026-07-10.
- **Bass play `tips` have no render surface (Muse/Luthier, small):** the authored teaching prose in bass packs renders nowhere (card shows label/feel/position/note only; the dock has no bass section) — real content-surface gap, L-42 gate 2026-07-10.
- **TransitionChip "2 step down" wording (Muse, one line):** whole-step rails render as "2 step down/up" (52 of 268 real KB transitions) — verbatim port of RoadmapTrack.jsx:59; "whole step down" is the musician's phrase (D-41 gate note 2026-07-10).
- **Fingering view lost at gallery thumb size (Muse, small):** D-30's gallery renders ChordDiagram at `size="thumb"`, which never draws finger numbers — nothing in the browser shows fingering anymore (D-30 gate note 2026-07-10). If pedagogically wanted: tap-to-enlarge to `size="full"` inside a gallery cell.
- **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 3:** bass pattern renderer (guitar → piano → **bass** roadmap; MiniPiano + piano recipe resolver shipped in sprint-knowledge-center).
- **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: `<date> · <task ids done> · <next>`.)_

- 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-09 · **iteration 8/24 — SPRINT `sprint-knowledge-center` COMPLETE** · done: C-21 (sprint-end sweep — build/validator/smoke all green on 72c6d45; full-App SSR renders; authored vs computed piano paths both proven; zero dead imports; audio contract +1 line since seed) · **PR #3 opened: https://github.com/whattheflat/JamBuddy/pull/3** (supersedes PR #2 — main hadn't absorbed it) · all 21 sprint tasks done, 4 returned-then-fixed by the gate, 0 unresolved · loop cron deleted after 8 of 24 budgeted iterations (board empty) · remaining backlog: EducationPanel retirement, FAMOUS_PROGRESSIONS level tags, smoke drift guards, pianoVoicing rootPc, negative-fret unify, L-12, bass renderer
- 2026-07-09 · **iterations 6–7/24** · done: L-25 (`e24aa4b` instrument-scoped modal tabs), C-22 (`b4e8f12` validator hand-span rule, smoke 799), P-23 (`564a9e0` gospel piano cell, 140 plays — returned once: one improv-row scale claim false for vi7/iii7, split into exact rows, PASS re-gate; 39/40 first-pass claims held), L-24 (`2165bb5` authored piano packs render at stations — implementer agent hung at verification after ~1 h, killed, completion-pass agent audited + finished; Critic PASS with 0 resolver drift) · incidents: P-23 stalled once mid-authoring (resumed from transcript, no loss); L-24 hung silently (diff intact, completed by fresh agent) · backlog: resolveDegree drift guard filed · **board empty except C-21 → sprint-end sweep + PR**
- 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-10 · **sprint-integrated-glance iteration 4/24 — SPRINT COMPLETE** · done: L-42 (`af7c3cc` **BASS real end-to-end** — BassPatternCard tab cards with playback; gate re-realized all 912 placements exact, byte-identity on untouched paths, import cycle proven safe), C-42 (`218c0ba` smoke §7 sweeps BOTH resolveDegree copies, 866/866 — the sweep agent authored the wiring then died on the ACCOUNT MONTHLY SPEND LIMIT; Maestro completed the sweep inline: build/validator/smoke green at HEAD, sabotage-proved the new guard [b13 perturbation → red naming the cell → restored green], whole-sprint App.jsx contract grep = 0 hits; per-instrument SSR relied on the L-42 gate's run minutes earlier — only smoke.mjs changed since) · backlogged: matcher blues/jazz tie-break (turnaround bass plays live-unreachable), bass tips surface · all 11 sprint tasks done, 2 returned-then-fixed (D-40, and D-41's predecessor D-31 pattern held: zero returns this sprint besides D-40), 0 unresolved · PR #3 updated; cron deleted (4 of 24 iterations) · ⚠️ NOTE: the Anthropic monthly spend limit is HIT — no further subagent work possible until raised
- 2026-07-11 · **sprint-roulette-kb iterations 1-2/12** (notification-driven; hourly cron as fallback) · done: M-07, C-60 (`5f4e2b7` piano lick schema — enclosures native, 25-cap proven tight, dormant renderer guard), P-62 (`ffc1bb1` pop piano cell 182 plays + 8 shapes; found 2 WRONG pre-existing shapes → backlog), D-61 (`c999c95` circle of fifths — returned once on modal hub signature, now teaches parent-major relation), P-60 (`70263e2` first piano licks, jazz+blues — PASS first try 35/35), D-60 (`a7dbd6a` PianoLickCard piano-roll renderer — PASS, 41/41 realization audit), D-62 (`e87b2bb` jam-roulette spec — returned TWICE, round-3 literal-implementation test passed; surfaced the pre-existing live bug: 10 progressions incl. blues-12bar never match detection; fix (a) specced into L-60), P-61 (`2c18c71` gospel+rnb licks, 39 total — returned once on one interval claim, gate-prescribed fix verified) · KB today: 172→182 plays, 23→39 licks, 6th piano style, circle of fifths · remaining: L-60 (blocked on one-screen C-50) → C-61
- 2026-07-11 · **BOTH SPRINTS COMPLETE** · sprint-roulette-kb: L-60 (`8d74e54`+`1df6fe8` **Jam Roulette LIVE** + the matcher collapse fix repairing the pre-existing live 12-bar/8-bar empty-JamGuide bug — Critic PASS on Opus: pool sweep reproduced from spec 4/52, fix-(a) additivity proven registry-wide, seed timeline hand-traced), C-61 (combined sweep — build/validator 182·39/smoke 891 all green, full-App SSR ×6 combos, whole-branch audio contract clean; PASS) · gate stats across both sprints: 9 first-try passes, 5 returns across 4 tasks (D-50, P-61, D-61, D-62×2) all re-gated to PASS · cron deleted · PR #3 updated with the combined summary · backlog: PianoLickCard LicksStrip wiring (needs shared sequencer-stop), 2 wrong GUITAR_SHAPES, strip-chip a11y, matcher blues/jazz tie-break, bass tips surface, 6 pre-existing orphan components
- 2026-07-11 · **sprint-one-screen iterations 2-3 + roulette-kb progress** · one-screen COMPLETE: L-50 (`800fd43`+`e58a7a6` dashboard grid, slim strip, jam view), D-51 (`d5a45f4` rail column adaptation — verified to the built CSS), L-51 (`6e8d8f4` RelatedProgressions — collapse step proven load-bearing), C-50 (`4deba7e` sweep, smoke 886, ranking pinned + registry id-uniqueness) — all Critic PASS · roulette-kb: P-61 (`2c18c71` gospel+rnb licks, 39 total) · **incident:** the L-60 (Jam Roulette) dispatch hit the account MONTHLY SPEND LIMIT on Fable 5 mid-read (no files written, clean tree); user switched the session to Opus 4.8 and L-60 re-dispatched fresh on Opus — proceeding · remaining: L-60 → C-61 closes both sprints
- 2026-07-11 · **sprint-one-screen iteration 1** (notification-driven) · done: M-06, D-50 (`d24bf35` one-screen dashboard + jam-view concept — returned once: strip height, geometrically-inert sticky, double-counted padding, collapse-blind ranking; all fixed, re-gate PASS with recomputed budgets) · L-50 (the build) in flight · then D-51 ‖ L-51 → C-50 → unlocks L-60
- 2026-07-10 · **sprint-integrated-glance iteration 3/24** · done: D-41 (`677d7b9` **all-expanded rail LIVE** — every station a permanently expanded row with education header [solo-scale, aim dots, voice-leading chips incl. wrap-around] + full gallery; playhead highlight-only proven byte-equal modulo highlight artifacts; focus toggle keeps the onFocusChord contract; show='bass' fall-through fixed; dense byte-identity proven across 336 SSR combos; black-on-accent 5.31:1 beats the old sub-AA white) — gate PASS, 19/19 SSR incl. independent theory recompute of a row's education · backlogged: TransitionChip "2 step down"→"whole step" wording (52 of 268 transitions) · L-42 (bass pattern renderer) dispatched — then C-42 closes
- 2026-07-10 · **sprint-integrated-glance iteration 2/24** · done: P-41 (`01a7c32` **the KB's first bass cell** — blues, 8 plays/172 total; gate machine-realized all 76 patterns in C AND A, 12 identities hold key-agnostically; PASS first try), L-40 (`c899132` **the restructure is live** — band below the main module, banner = the single loop display, RoadmapTrack/CurrentJamPanel unmounted, App's one GUITAR/PIANO/BASS selector drives band + dock, honest bass rows, GlanceRail yank-effect deleted; gate SSR 22/22, audio contract grep-clean, deletion sweep clean) · folded: smoke §7 resolveDegree wiring → C-42; VoicingBrowser show='bass' fall-through fix → D-41 (lock extended) · D-41 (all-rows-expanded rail) dispatched — then L-42 → C-42 closes
- 2026-07-10 · **sprint-integrated-glance iteration 1/24** · done: M-05, P-40 (`6f59add` kb-backlog refreshed — gate audited all 14 cells + 10 dates), C-40 (`090d3a9` drift guards, smoke 836 — gate recomputed all 224 truth-table cells), D-40 (`7d1f64c` integrated-glance concept — returned once: Dm7-specific piano math + 3 factual fixes incl. the interim page-yank, fixed, PASS re-gate with pixel-exact recompute), C-41 (`996218f` bass play schema, smoke 849 — degree-based, typed approaches, PASS with 19-semitone bound proven exactly maximal) · in flight: L-40 (App restructure — band up top, single loop display, one selector) ‖ P-41 (blues bass, first bass cell) · then D-41 → L-42 → C-42 closes
- 2026-07-10 · **iteration 4/24 — SPRINT `sprint-glance-and-loop` COMPLETE** · done: P-31 (`81cd230` blues piano cell, 164 plays — returned once: jump-kicks bars 3-4 dyad orientations flipped + 2 prose rewordings, fixed, PASS scoped re-gate), C-31 (sprint-end sweep at `81cd230` — build/validator/smoke 817 all green; full-App SSR renders with rail+gallery+strip coexisting; whole-sprint App.jsx diff = ONE hunk in the progression effect, contract grep 0 hits; no dead imports; every gate-promised follow-up verified filed) · all 11 sprint tasks done, 2 returned-then-fixed (D-31, P-31), 0 unresolved · PR #3 updated with the sprint summary; loop cron deleted after 4 of 24 budgeted iterations (board empty) · new backlog: loop-fixtures header line-ref drift; kb-backlog needs rnb+blues done entries
- 2026-07-10 · **iteration 3/24** · done: L-33 (`c78baf4` **glance mode LIVE** — playhead accordion, licks strip, heard-live fallback; gate SSR 37/37, dense-less VoicingBrowser byte-identical to 0945a08, token matcher proven hostile-proof, App.jsx untouched by construction), L-31 (`ce24d54` commit layer 2/3/6 — gate re-traced all five scenarios independently + 3 hostile traces; REPLACE_VOTES=3 proven load-bearing; audio contract grep-clean) · **all three user asks now end-to-end on the branch** · P-31 (blues piano, 164 plays) authored — gate in flight; its verifier self-caught 3 prose overclaims pre-review · C-31 promoted (holds until P-31 lands so the sweep sees a settled tree) · next: gate P-31 → C-31 closes the sprint
- 2026-07-10 · **iteration 2/24** · done: P-30 (`b51daa1` rnb piano cell, KB 154 plays — PASS first try, a piano-pack first; found+fixed its own 36-key-window stacking defect pre-review), L-30 (`4cc0009` detector rewrite: dup-collapse, weak-period ghost rejection, len 2–8, ≤1 edit/cycle with ≥2-exact evidence gate, recency-weighted coverage — smoke 817/817, gate ran 15 novel probes, 0.2–0.7ms/call) · L-33 built (playhead accordion + LicksStrip + heard-live fallback, SSR 24/24 self-checked) — gate in flight · L-31 dispatched with both gates' findings folded (flap-survival constraint, docstring ride-along) · both watchdog-stalled agents recovered cleanly via resume · promoted: P-31 · next: gate L-33, land L-31, then C-31 closes
- 2026-07-10 · **iteration 1/24** · done: M-04, D-30 (`0945a08` VoicingBrowser gallery — all shapes/styles side by side, no chips; PASS first try), C-30 (`fdd93c7` 17 loop fixtures + smoke wiring; PASS — found the len² self-overlap artifact as the root cause of the user's missed 3-chord loops, folded into L-30/L-31 DoDs), D-31 (`6c7a1a7` glance-mode "playhead accordion" doc — returned once on piano-gallery width math, fixed, PASS re-gate) · in flight: L-30 (detector redesign, dispatched early — C-30 contract ready), P-30 (rnb piano) — both stalled once on the 600s API watchdog near completion, both resumed via SendMessage · promoted: L-33 (ready, locks per D-31 doc) · finding worth noting: "5 chords then 2 others" passes at pure-function level — the live failure is the ghost-pattern flapping starving the 2-vote commit layer (L-31's trace case)
- 2026-07-10 · **new sprint `sprint-glance-and-loop`** seeded on the SAME branch (commits extend PR #3) · user asks: all voicing variations side-by-side with no button pushing; fix loop detection (misses a returning 3-chord loop and a 5-chords-then-2-others shape); glanceable zero-click learn session that follows the live jam · loop scheduled: /jam-loop every 30 min × 24 iterations (12 h, cron 13,43) · ready now (file-disjoint): C-30 ‖ D-30 ‖ D-31 ‖ P-30, then C-30→L-30→L-31 and D-30+D-31→L-33
- 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)
