Files
JamBuddy/docs/agents/LEDGER.md
T
2026-07-08 17:08:06 +01:00

50 KiB
Raw Blame History

Ensemble Ledger — Live Task Board

The shared queue all agents read and write. Lifecycle and rules: PROTOCOL.md. Roster + cadence weights: ROSTER.md.

Status: backlogreadyclaimedin-reviewdone / returned Lock: a task's files column is its lock — no two claimed/in-review tasks may overlap files.


Active sprint: sprint-knowledge-center (branch: sprint-jamguide-piano — continued, per user directive)

Goal (user directive 2026-07-08): revise the visual structure of the Knowledge Center — today's knowledge lives in three disconnected surfaces (JamGuide/Roadmap, ExplorePanel, EducationPanel). Four user asks, verbatim priorities:

  1. Playable voicings per chord — browse and hear alternative voicings for any chord (guitar shapes + generated piano voicings), with simple switching.
  2. Simple ways of seeing techniques for licks — lick cards with technique markers (hammer-on, slide, bend…), per style.
  3. Intermediate-level progressions — research them and find the easiest way to surface them in-app (level tags + filter, not a new panel).
  4. Keep expanding music knowledge — continue KB cells per docs/kb-backlog.md in spare iterations.

Loop: /jam-loop every 30 min for 12 h (24 iterations), started 2026-07-08. User is away — no user-gated tasks this sprint; where the old sprint would have asked (concept selection), Muse picks the strongest option and records the rationale in the concept doc. Weights: Muse 3, Luthier 3, Professor 2, Critic gate, Herald 0.

id title domain status depends-on files (lock) definition of done
M-03 Seed sprint-knowledge-center from user directive 2026-07-08; carry L-11 forward (hold lifted — user asked to continue on this branch) maestro done docs/agents/LEDGER.md sprint seeded, loop scheduled
L-11 Wire piano into JamGuide.jsx: Piano tab active; per station render MiniPiano voicing={…} from pianoVoicing/pianoVoicingChain; keep the guitar path intact. Carried from piano sprint; user hold lifted 2026-07-08 engineering done src/components/JamGuide.jsx piano tab shows per-station keyboards synced to the loop; guitar unchanged; build + smoke green
D-20 Knowledge Center concept doc: one unified IA for JamGuide (live) + ExplorePanel (browse) + EducationPanel (learn) — sections Jam Guide / Explore / Voicings / Licks & Techniques, where the voicing player and lick cards mount, level-filter placement, narrow-viewport plan. No user gate: pick the strongest layout, record rationale + rejected alternatives in the doc design done docs/design/knowledge-center.md doc names every component to touch, the target structure, and the migration order for L-22; honest about what stays untouched
L-20 src/lib/chordAudio.js — Web Audio voicing playback: playVoicing(notes, {strumMs, durMs}) (piano notes are absolute 036 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 ready 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 ready 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 ready 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 34 licks each for blues, jazz, funk guitar using the C-20 lick schema — technique tags, chordContext (which station/quality it fits), honest attribution content ready C-20, P-20 (lock overlap: both touch src/data/kb/jazz/** — serialized behind P-20) src/data/kb/blues/**, src/data/kb/jazz/**, src/data/kb/funk/** 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 ready — (lock overlaps D-21's export-only voicings.js line — serialize at claim time) 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 ready 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 ready D-20, D-22, P-20, P-21 (licks commit may land empty-state-first per the D-20 doc) 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 backlog L-11 src/data/kb/jazz/piano.js, src/data/kb/index.js validator green; renders in the piano tab
P-23 KB expansion: gospel piano cell content backlog P-22 src/data/kb/gospel/piano.js, src/data/kb/index.js validator green; renders in the piano tab
C-21 Sprint-end sweep: full build + validator + smoke, cross-browser sanity notes, PR body draft summarising the sprint for main quality backlog L-21, L-22, P-21 (none — verification) all green; PR body ready

Sequencing: L-11 ‖ D-20 ‖ L-20 ‖ C-20 are all file-disjoint and ready now. Then D-21 → L-21, and P-20/P-21/D-22 → L-22. P-22/P-23 fill spare iterations. Critic gates every task as usual.


Shipped sprint: sprint-jamguide-piano (branch: sprint-jamguide-piano — piano resolver phase complete; L-11 carried into the sprint above)

Goal: extend the shipped Roadmap Jam Guide to piano (guitar→piano→bass roadmap). When the Piano instrument tab is selected, each Roadmap station shows a generated piano voicing on a mini-keyboard — voicings computed from the progression's degrees+qualities (GOAL G4: generate piano voicings from interval recipes; no authored data needed for the MVP). Branch cut off sprint-jam-guide HEAD. Weights: Luthier 3, Muse 2, Critic gate.

id title domain status depends-on files (lock) definition of done
M-02 Cut sprint-jamguide-piano branch off sprint-jam-guide; seed sprint maestro done (branch) branch cut, ledger seeded
L-10 src/lib/piano.js — pure piano voicing resolver: pianoVoicing({rootPc, quality}, opts?) → shell (1-3-7), rootless A/B (3-5-7-9 / 7-9-3-5), root-position; picks the inversion minimizing movement from a previous voicing (voice-leading); returns absolute key positions + pcs + a label engineering done M-02 src/lib/piano.js pure fns; correct tones for all 14 CHORD_TYPES; voice-leading-minimal vs a prev voicing; build + smoke green; output shape documented for D-10/L-11
D-10 Extend existing src/components/MiniPiano.jsx (already imported by ChordDetailModal/ExplorePanel via {rootPc, lh, rh}) with an ADDITIVE voicing prop that renders a pianoVoicing output ({notes,pcs,bass,style,label}) on a 23 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: iiVI & 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 (iiVI 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 iiVI & 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: iiVI 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: ((94)%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 "R379" 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 PASSnode 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 (iiVI 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|useEffectNO 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=falsetag 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=nullhasFocus=falsetag 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); iiVI 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 PASSnpm 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 23 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 PASSnode scripts/smoke.mjs776/776 exit 0 (was 71 pre-piano; +705 piano checks). Diff ADDED a piano block, kept ALL prior registry/matcher checks. Sweeps 14 qualities × {C,G} × {root,shell,rootlessA,rootlessB,default} asserting: valid shape (notes/pcs/bass/style/label), pcs ⊆ voicingToneSet(quality,actualStyle), real 3rd (or sus tone) present, true 7th present in shell/rootless when hasTrueSeventh, notes ∈ [0,36], bass===min(notes), no-dup-note, determinism, and chain pcs-preservation. Adversarially proven NON-vacuous: inject pc1 into pcs → 125 fails, exit 1 (pcs⊆toneSet bites); bass=max(notes)140 fails, exit 1; revert L-13 dedupe (notes=placed) → the no-dup check fails on exactly sus2@0 [rootlessB] notes=[26,26,31] + sus2@7 [21,21,26], exit 1. So the suite now passes LEGITIMATELY because L-13 fixed the resolver, not because any check was weakened. piano.js restored to its L-13 diff after each sabotage (git: 9 insertions, clean). GATE 2026-06-16 (Critic) · L-13 PASS — minimal fix at the single funnel point in pianoVoicing: const notes = [...new Set(placed)] (dedupe ABSOLUTE notes), pcs/bass derived from the deduped array so both stay consistent. Verified via a throwaway resolve-hook ESM check (written + deleted): (a) pianoVoicing({rootPc:0,quality:'sus2'},{style:'rootlessB'})[26,31] distinct (was [26,26,31]); (b) NO default-style voicing across all 14 qualities has a duplicate, so the dedupe is a pure no-op on the default/chain path — L-11's render is unaffected; ii-V-I chain [14,17,24]|[19,23,29]|[24,28,35] all distinct & voice-led. The full 56-voicing correctness is re-asserted by the now-776-green smoke (pcs⊆toneSet, 3rd, 7th, [0,36], bass for every quality×style). Safety reasoning — dedupe NEVER drops a needed note: a JS Set of absolute integers merges two entries only when they are the identical integer, i.e. identical pc AND identical octave — the same physical key, which can sound only one note anyway. Two musically-distinct intended tones (different pc, or same pc a different octave like 14 vs 26) yield different integers and are never merged. The sus2 collision is a genuine unison (pc2 stand-in 3rd at +24=26 vs synthesized 9th at +12=26, both pc2 same octave) — a redundant doubling, safe to drop. No information loss possible. build green; default + chain paths unchanged.


Backlog (future sprints)

  • Retire EducationPanel.jsx — confirmed orphaned (no importer anywhere in src/, D-20 gate verified); delete after the Knowledge Center ships. Fold CurrentJamPanel's overlapping content (voicings/scales/similar progressions) into the Knowledge Center and retire it too.

  • Level-tag FAMOUS_PROGRESSIONS (src/lib/education.js, Professor): famous-progression cards are exempt from the level filter this sprint (Maestro call 2026-07-08, per D-20 open question 1).

  • Piano thumb width polish (Muse): 23-octave MiniPiano station thumbs are ~266390 px — consider cropping to the voicing's octave span (L-11 gate observation).

  • Jam Guide phase 2: MiniPiano.jsx + piano recipe resolver (unblocks piano packs); bass pattern renderer.

  • Content: piano packs (jazz → gospel → neo-soul first), then bass packs (blues → jazz → funk first) — see docs/kb-backlog.md.

  • guideTones third bug (L-12, Luthier — small): guideTones takes ints[1] as the "third", so add9 [0,2,4,7] reports its 9th (pc 2) as the 3rd instead of the major 3rd (pc 4) — wrong "3rd" lane/badge for add9 (live in gospel + pop-axis Cadd9). Pre-existing since L-01b; flagged by Critic during the D-03 re-gate. Fix: pick the actual 3rd/4th interval (3 or 4, or sus 2/5) rather than positional ints[1]. Smoke + a guideTones truth-table check.

  • Learning features (Professor + Luthier): drills tab seeded from docs/learn-curriculum.md; target-note highlighting; ear-training quiz on own chord history; pocket report from the onset pipeline.

  • Progression Builder (GOAL G3): key-relative palette, drag-reorder, Nashville-number toggle, voicing alternatives.

  • Platform (Herald): GitHub Pages docs site; "you're playing the Creep progression" engagement hook; contributor leaderboard for styles added.


Iteration log

(Maestro appends one line per completed iteration: <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)