feat(jamguide): tap a Roadmap station -> guide tones on the main Fretboard (task D-03)

Completes the flagship Roadmap feature. App.jsx lifts a jamFocusChord state (additive,
+10/-0, audio callbacks/refs untouched); JamGuide emits the tapped station's {rootPc,
quality} via onFocusChord; Fretboard halos the 3rd/7th with a degree badge.

Also fixes root-cause guideTones: hasSeventh now keys on real m7/M7 (interval 10/11),
not length>=4 -- so add9/maj6/min6 no longer badge their 5th/6th as a '7'. Fretboard
badge derives its label from the actual interval (belt-and-suspenders). RoadmapTrack
lane self-corrects. Critic returned-then-PASS on re-gate; build + smoke + validator green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
vadimwit
2026-06-15 13:05:21 +01:00
parent ddbca3f197
commit 53d78ce30f
4 changed files with 120 additions and 20 deletions
+17 -1
View File
@@ -30,7 +30,7 @@ const INSTRUMENTS = [
{ id: 'bass', label: 'Bass', icon: '🎵' },
]
export default function JamGuide({ detectedProgression, keyInfo, chordHistory = [], bpm, currentChord }) {
export default function JamGuide({ detectedProgression, keyInfo, chordHistory = [], bpm, currentChord, onFocusChord }) {
const [open, setOpen] = useState(false)
// Which instruments have at least one KB pack across the registry.
@@ -124,6 +124,7 @@ export default function JamGuide({ detectedProgression, keyInfo, chordHistory =
return {
shape: chords[i]?.shape ?? null,
rootPc,
quality: qualities[i] ?? 'maj',
label: `${noteName}${suffix}`,
rn: prog?.rn?.[i] ?? '',
}
@@ -135,6 +136,21 @@ export default function JamGuide({ detectedProgression, keyInfo, chordHistory =
// Reset the selection whenever the loop or style changes underneath us.
useEffect(() => { setSelectedStation(null) }, [match.id, match.style, instrument])
// ── Cross-link to the main Fretboard (D-03) ─────────────────────────────────
// When a station is selected, report its {rootPc, quality} upward so the
// Fretboard can light that chord's guide tones; clear (null) on deselect. The
// reset effect above sets selectedStation → null on loop/style/instrument
// change, which flows through here and clears the highlight too. Guarded so
// the component still works standalone (onFocusChord optional).
useEffect(() => {
if (!onFocusChord) return
const st = selectedStation != null ? stationVoicings[selectedStation] : null
onFocusChord(st ? { rootPc: st.rootPc, quality: st.quality } : null)
}, [selectedStation, stationVoicings, onFocusChord])
// Clear the Fretboard highlight when JamGuide unmounts.
useEffect(() => () => { onFocusChord?.(null) }, [onFocusChord])
return (
<div className="mb-3 bg-panel border border-border rounded-xl overflow-hidden">