)
}
diff --git a/src/components/JamGuide.jsx b/src/components/JamGuide.jsx
index 78ac67b..aa573a7 100644
--- a/src/components/JamGuide.jsx
+++ b/src/components/JamGuide.jsx
@@ -10,17 +10,24 @@ import { ExploreSection, VoicingsSection, LevelChips } from './ExplorePanel'
import { pianoVoicingChain } from '../lib/piano'
import { parseChord } from '../lib/voicings'
-// ─── JamGuide.jsx — the jam BAND + the Knowledge Center DOCK ─────────────────
+// ─── JamGuide.jsx — the jam-grid OWNER + the Knowledge Center DOCK ───────────
//
-// Task L-40 (per docs/design/integrated-glance.md §5–§6.1) split the old
-// four-section bottom dock in two:
+// Task L-50 (per docs/design/one-screen.md §6) promoted the default export from
+// "band" to the two-column jam dashboard grid:
//
-// default export `JamGuide` — the always-open, zero-chrome jam band, mounted
-// by App directly below the instrument row (CurrentJamPanel's old slot).
-// Body: GlanceRail voicings + LicksStrip for the matched loop; heard-live
-// single gallery when no loop is matched; a slim one-line hint when nothing
-// is heard. The loop itself renders ONCE, in ProgressionBanner (D-40 §2) —
-// RoadmapTrack is unmounted (file retired in place, deletion backlogged).
+// default export `JamGuide` — renders the xl: two-column flex region.
+// LEFT (flex-1): the `mainView` slot (App keeps choosing Fretboard /
+// BassFretboard / Piano — JamGuide never imports them), the LicksStrip,
+// and the `relatedSlot` (RelatedProgressions, mounted by App — null until
+// L-51). RIGHT (500px): the suggested-voicings rail — GlanceRail /
+// BassGuideRows / the heard-live fallback — inside the design's ONE
+// justified internal scroller (height-bounded, NOT sticky, §1). Below xl
+// the columns stack in jam-following order via display:contents + order
+// classes: mainView → rail → licks → related (§7; rail unbounds).
+// The `fill` prop (jam view, §1.1) swaps the rail's viewport-calc bound
+// for h-full and makes the left column a flex stack whose related slot
+// absorbs the remainder. The loop itself renders ONCE, in
+// ProgressionBanner (D-40 §2).
// named export `KnowledgeDock` — the bottom collapsible browse/study area:
// Explore / Voicings / Licks & Techniques + the shared level filter (the
// old dock minus its jam section, which IS the band now).
@@ -149,7 +156,7 @@ function lickFitsContext(lick, context) {
return wanted.length > 0 && tokens.some(t => wanted.includes(t))
}
-// ─── JamGuide — the always-open jam band (default export) ─────────────────────
+// ─── JamGuide — the jam dashboard grid (default export) ───────────────────────
//
// Props:
// detectedProgression : string[] | null — the live detected loop (chord names)
@@ -158,12 +165,16 @@ function lickFitsContext(lick, context) {
// currentChord : string | undefined — most recent committed chord
// onFocusChord : fn({rootPc,quality}|null) — Fretboard guide-tone link (D-03)
// instrument : 'guitar' | 'piano' | 'bass' — App's global selector
+// mainView : JSX slot — the compact instrument view (App-chosen; L-50)
+// relatedSlot : JSX slot — RelatedProgressions (App-mounted; null until L-51)
+// fill : boolean — jam view (one-screen.md §1.1): the grid fills
+// App's h-screen column; rail bound becomes h-full
// The band shows ALL levels — a glance surface filters nothing (D-40 §5); the
// level filter lives in the KnowledgeDock only.
const ALL_LEVELS = { foundation: true, intermediate: true }
-export default function JamGuide({ detectedProgression, keyInfo, chordHistory = [], currentChord, onFocusChord, instrument = 'guitar' }) {
+export default function JamGuide({ detectedProgression, keyInfo, chordHistory = [], currentChord, onFocusChord, instrument = 'guitar', mainView = null, relatedSlot = null, fill = false }) {
// Style labels straight from the KB registry, via each style's meta.
const styles = useMemo(
() => Object.entries(kb).map(([id, style]) => ({ id, label: style?.meta?.label ?? id })),
@@ -343,96 +354,140 @@ export default function JamGuide({ detectedProgression, keyInfo, chordHistory =
// re-sorting with the jam (D-31 §2.4).
const contextStation = stationVoicings[canonicalPos >= 0 ? canonicalPos : 0] ?? null
- return (
-
-
- {/* ── Micro-header — a line, not a button (D-40 §1: zero chrome) ── */}
-
- Jam Guide — {headerLabel}
- {match.matched && keyInfo?.root ? ` · in ${keyInfo.root} ${keyInfo.mode}` : ''}
-
-
- {match.matched ? (
- instrument === 'bass' ? (
- /* Bass rows (D-40 §3): authored pattern cards when the matched style
- ships a bass pack (L-42), computed roots/fifths/approaches as the
- honest fallback otherwise. The licks strip hides either way
- (guitar tab licks are noise to a bassist mid-jam). */
-
- ) : (
-
- {/* The voicing rail — ALL stations expanded as vertical rows; the
- playhead only highlights (D-41, D-40 §4). */}
-
-
-
- )
- ) : liveChord ? (
- /* No loop matched, but chords are committing (D-31 §2.3): a single
- "heard live" gallery, re-aimed on every chord commit. Auto-follow
- only — nothing plays by itself. Bass: D-40 §3's prose forbids guitar/
- piano galleries under BASS, so the live chord gets the same computed
- root/fifth line (no next chord → no approach) instead. */
- instrument === 'bass' ? (
-
- ) : (
-
-
-
- Heard live · {currentChord} — every voicing
-
-
- {detectedProgression?.length
- ? `Heard ${detectedProgression.join(' → ')} — no ${activeStyle} pattern matched yet; following the chord as it commits.`
- : 'No repeating loop yet — following the chord as it commits.'}
-
-
-
- {/* No station rn without a loop — context sort falls back to the
- live chord's quality key (e.g. a "dom7" lick fits a live G7). */}
-
-
- )
- ) : (
- /* Nothing heard yet — one slim line (~40px, D-40 §1): the idle band
- must not waste main-module space. */
-
+ // ── The rail (right column at xl / second block stacked): the suggested-
+ // voicings surface — GlanceRail, BassGuideRows, the heard-live gallery, or
+ // the honest idle line (one-screen.md §3, §4). ──
+ const railContent = match.matched ? (
+ instrument === 'bass' ? (
+ /* Bass rows (D-40 §3): authored pattern cards when the matched style
+ ships a bass pack (L-42), computed roots/fifths/approaches as the
+ honest fallback otherwise. The licks strip hides either way
+ (guitar tab licks are noise to a bassist mid-jam). */
+
+ ) : (
+ /* The voicing rail — ALL stations expanded as vertical rows; the
+ playhead only highlights (D-41, D-40 §4). */
+
+ )
+ ) : liveChord ? (
+ /* No loop matched, but chords are committing (D-31 §2.3): a single
+ "heard live" gallery, re-aimed on every chord commit. Auto-follow
+ only — nothing plays by itself. Bass: D-40 §3's prose forbids guitar/
+ piano galleries under BASS, so the live chord gets the same computed
+ root/fifth line (no next chord → no approach) instead. */
+ instrument === 'bass' ? (
+
+ ) : (
+
+
+ Heard live · {currentChord} — every voicing
+
+
{detectedProgression?.length
- ? `Heard ${detectedProgression.join(' → ')} — no ${activeStyle} pattern matched yet; voicings follow the next chord that commits.`
- : 'Play a few bars — voicings and licks for your loop land here.'}
+ ? `Heard ${detectedProgression.join(' → ')} — no ${activeStyle} pattern matched yet; following the chord as it commits.`
+ : 'No repeating loop yet — following the chord as it commits.'}
- )}
+
+
+ )
+ ) : (
+ /* Nothing heard yet — one slim line (~40px): the idle rail must not
+ waste dashboard space. */
+
+ {detectedProgression?.length
+ ? `Heard ${detectedProgression.join(' → ')} — no ${activeStyle} pattern matched yet; voicings follow the next chord that commits.`
+ : 'Play a few bars — voicings and licks for your loop land here.'}
+
+ )
+
+ // ── 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 ? (
+
+ ) : instrument !== 'bass' && liveChord ? (
+
+ ) : null
+
+ // ── The grid (one-screen.md §1, §6): left flex-1 / right 500px at xl;
+ // stacked below xl in jam-following order (mainView → rail → licks →
+ // related) via display:contents on the left wrapper + order classes — one
+ // mount per surface, no duplicates (§7). The rail wrapper is the design's
+ // ONE justified internal scroller: height-bounded in normal mode, h-full
+ // in jam view (`fill`), NOT sticky (§1). ──
+ return (
+
+ {/* LEFT — instrument view · licks · related progressions */}
+
+ {mainView != null && (
+
+ {mainView}
+
+ )}
+ {licksStrip != null && (
+
+ {licksStrip}
+
+ )}
+ {relatedSlot != null && (
+
+ {relatedSlot}
+
+ )}
+
+
+ {/* RIGHT — the suggested-voicings rail (the one contained scroller) */}
+
+ {/* Micro-header — a line, not a button (D-40 §1: zero chrome) */}
+