diff --git a/src/components/JamGuide.jsx b/src/components/JamGuide.jsx index 762d98d..03f8dad 100644 --- a/src/components/JamGuide.jsx +++ b/src/components/JamGuide.jsx @@ -6,6 +6,7 @@ import GlanceRail, { AimDots, SoloLabel } from './GlanceRail' import BassPatternCard from './BassPatternCard' import VoicingBrowser from './VoicingBrowser' import LickCard, { TechniqueLegend } from './LickCard' +import PianoLickCard from './PianoLickCard' import { ExploreSection, VoicingsSection, LevelChips } from './ExplorePanel' import { pianoVoicingChain } from '../lib/piano' import { parseChord } from '../lib/voicings' @@ -132,8 +133,16 @@ function recipeVoicing(recipe, rootPc, quality) { // // `licksFor` was a closure-local inside LicksSection; lifted to module scope // during the L-33 restructure (D-31 §5) so the strip shares it instead of -// duplicating the defensive read. Licks are guitar-only in the KB (C-20 schema). -function licksFor(id) { +// duplicating the defensive read. Guitar licks are tab entries (LickCard); +// piano licks (D-70 §5.1) are the STRUCTURED entries only — those carrying a +// `notes` array PianoLickCard can realize — so prose-only piano education +// entries never render as placeholder cards. The dock's LicksSection reads +// guitar (its default arg), so its behaviour is unchanged. +function licksFor(id, instrument = 'guitar') { + if (instrument === 'piano') { + const l = kb?.[id]?.instruments?.piano?.licks + return Array.isArray(l) ? l.filter(x => Array.isArray(x?.notes)) : [] + } const l = kb?.[id]?.instruments?.guitar?.licks return Array.isArray(l) ? l : [] } @@ -425,23 +434,25 @@ export default function JamGuide({ detectedProgression, keyInfo, chordHistory =
) - // ── The licks strip (left column). Matched loops sort by the playhead - // station; heard-live falls back to the live chord's quality key (e.g. a - // "dom7" lick fits a live G7). Bass hides it (guitar tab licks are noise - // to a bassist mid-jam); LicksStrip also hides itself when empty. ── - const licksStrip = instrument !== 'bass' && match.matched ? ( + // ── The licks strip (left column). It now FOLLOWS the global instrument + // (D-70 §5.1): guitar → tab LickCards, piano → PianoLickCards realized over + // the playhead root, bass → an honest "no bass licks" line. Matched loops + // sort by the playhead station; heard-live falls back to the live chord's + // quality key (e.g. a "dom7" lick fits a live G7). The live-chord context + // carries rootPc so PianoLickCard can realize its degrees. LicksStrip itself + // decides the empty behaviour per instrument (guitar hides; piano/bass show a + // slim honest line). ── + const licksContext = match.matched + ? contextStation + : liveChord + ? { rn: '', quality: liveChord.type, label: currentChord, rootPc: liveChord.rootPc } + : null + const licksStrip = (match.matched || liveChord) ? (+ {children} +
+ ) +} - const visible = licksFor(styleId).filter(l => levels[lickLevel(l)]) +function LicksStrip({ styleId, levels, instrument, context }) { + // Inline enlarge (one card at a time); reset when the style/instrument changes. + const [expandedId, setExpandedId] = useState(null) + useEffect(() => { setExpandedId(null) }, [styleId, instrument]) + + const styleLabel = kb?.[styleId]?.meta?.label ?? styleId + + // Bass: no strip licks in the KB — honest line, never a vanished section. + if (instrument === 'bass') { + return
diff --git a/src/components/PianoLickCard.jsx b/src/components/PianoLickCard.jsx
index 1c23b01..5eb214d 100644
--- a/src/components/PianoLickCard.jsx
+++ b/src/components/PianoLickCard.jsx
@@ -49,14 +49,12 @@
// (MiniPiano's cropped-window geometry), technique chips, tips, source —
// mirroring LickCard's full-size behaviour.
//
-// ── Playback (▶) ──────────────────────────────────────────────────────────────
-// BassPatternCard's exact pattern: sequential single-note playVoicing calls
-// (order lives in setTimeout scheduling — playVoicing sorts/dedupes, wrong for
-// a melody), beats at a fixed preview tempo (even eighths beatless), ONE
-// sequence module-wide + stopAll() so it never layers over other previews,
-// unmount silences. Techniques are visual-only in playback (same precedent).
+// ── No playback (D-70 §3) ─────────────────────────────────────────────────────
+// The strip is purely visual — glance over audio (the user's settled call, "then
+// leave them off, better not"). The ▶ play path (sequencer + PlayButton) was
+// removed with the rest of the ▶s across the app; the card only renders now.
//
-// ── Wiring contract (future LicksStrip integration — Luthier) ─────────────────
+// ── Wiring contract (LicksStrip integration — Luthier, L-71) ──────────────────
//