From 951d5440e14104d9ed4598b3fed665630e44d7c5 Mon Sep 17 00:00:00 2001 From: vadimwit Date: Wed, 8 Jul 2026 22:55:42 +0100 Subject: [PATCH] feat(jamguide): playable VoicingBrowser in chord modal + enlarged stations (task L-21) ChordDetailModal Guitar/Piano tabs mount VoicingBrowser on top with legacy grips/technique reference preserved below; Jam Guide enlarged station view gains the browser on both instrument tabs; parse-null never mounts a wrong-chord fallback. App.jsx untouched. Critic PASS (SSR-verified both entry points; onFocusChord effects byte-untouched). Co-Authored-By: Claude Fable 5 --- src/components/ChordDetailModal.jsx | 77 +++++++++++++++++++---------- src/components/JamGuide.jsx | 9 ++++ 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/src/components/ChordDetailModal.jsx b/src/components/ChordDetailModal.jsx index b087082..8b5f292 100644 --- a/src/components/ChordDetailModal.jsx +++ b/src/components/ChordDetailModal.jsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react' import ChordBox from './ChordBox' import MiniPiano from './MiniPiano' +import VoicingBrowser from './VoicingBrowser' import { getGuitarVoicings, getPianoTechniques, parseChord } from '../lib/voicings' import { CHORD_TYPES, NOTES, getChordsInKey, toRomanNumeral, getSuggestedProgressions } from '../lib/theory' import { FAMOUS_PROGRESSIONS, progressionInKey, getChordSubstitutions, CHORD_PLAYBOOK } from '../lib/education' @@ -29,36 +30,49 @@ function chordDisplayName(root, typeKey) { } function GuitarTab({ chordName }) { + // parseChord's `type` is a CHORD_TYPES key — exactly VoicingBrowser's + // `quality` prop (same mapping PianoTab/ExploreTab already rely on). L-21. + const parsed = parseChord(chordName) const voicings = getGuitarVoicings(chordName) - if (!voicings.length) { + if (!parsed && !voicings.length) { return

No guitar voicings found for {chordName}.

} return ( -
-

- Click any voicing to learn it. Purple = chord tones. Finger numbers inside dots (1=index, 4=pinky). - Barre chords show the fret number on the left. -

-
- {voicings.map((v, i) => ( -
- -

{v.label}

+
+ {/* Playable voicing browser (D-21) — browse + audition shapes. */} + {parsed && } + + {/* Static grid kept as the fingering reference — it carries finger + numbers and barre info the browser doesn't show. */} + {voicings.length > 0 && ( +
+

More grips — fingering reference

+

+ Click any voicing to learn it. Purple = chord tones. Finger numbers inside dots (1=index, 4=pinky). + Barre chords show the fret number on the left. +

+
+ {voicings.map((v, i) => ( +
+ +

{v.label}

+
+ ))}
- ))} -
-
-

- Pro tip: Learn the E-shape and A-shape barres first - — they cover all 12 roots. Then add open voicings for the keys you play in most. - High-fret voicings (above fret 7) work great as jazz comping shapes in a band mix. -

-
+
+

+ Pro tip: Learn the E-shape and A-shape barres first + — they cover all 12 roots. Then add open voicings for the keys you play in most. + High-fret voicings (above fret 7) work great as jazz comping shapes in a band mix. +

+
+
+ )}
) } @@ -68,17 +82,28 @@ function PianoTab({ chordName }) { const techniques = getPianoTechniques(chordName) const rootPc = parsed?.rootPc ?? 0 - if (!techniques.length) { + if (!parsed && !techniques.length) { return

No piano techniques for {chordName}.

} return (
+ {/* Playable voicing browser (D-21) — parseChord's `type` maps 1:1 to the + browser's `quality` (CHORD_TYPES key). L-21. */} + {parsed && } + + {/* Technique cards kept below — the name/desc/tip text and LH/RH note + breakdown are not covered by the browser. */} + {techniques.length > 0 && ( +

Techniques — how to use it at the keys

+ )} + {techniques.length > 0 && (

Blue = Left hand  ·  Purple = Right hand  ·  R marks the root.

+ )} {techniques.map((t, i) => (
diff --git a/src/components/JamGuide.jsx b/src/components/JamGuide.jsx index 085e794..a70026a 100644 --- a/src/components/JamGuide.jsx +++ b/src/components/JamGuide.jsx @@ -5,6 +5,7 @@ import { NOTES, CHORD_TYPES } from '../lib/theory' import RoadmapTrack from './RoadmapTrack' import ChordDiagram from './ChordDiagram' import MiniPiano from './MiniPiano' +import VoicingBrowser from './VoicingBrowser' import { pianoVoicingChain } from '../lib/piano' // ─── JamGuide — the Roadmap bottom dock ─────────────────────────────────────── @@ -412,6 +413,14 @@ function RoadmapAssembly({ label={`${selected.label}${selected.rn ? ` · ${selected.rn}` : ''}`} /> )} + {/* Browse + audition every voicing of this station's chord (L-21). + Stations already carry {rootPc, quality} (L-11); the browser is + fully self-contained (D-21) and wraps dock-friendly. Purely + additive — the onFocusChord guide-tone contract above is + untouched (it keys off selectedStation, not this render). */} +
+ +