6c7a1a7828
Knowledge Center behaviour while a jam plays, zero clicks: the station under the playhead auto-expands to the full D-30 voicing gallery and advances with the music; level-filtered licks strip below with context-first sort; pin gesture inherits onFocusChord semantics; auto-follow never emits focus or auto-plays audio. Honest space math (piano rail scrolls as normal state), 5-commit migration order for L-33, four rejected alternatives. Critic returned once (piano gallery width understated ~45%), fixed, PASS on scoped re-gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
315 lines
20 KiB
Markdown
315 lines
20 KiB
Markdown
# Glance Mode — the Knowledge Center while the jam plays (task D-31)
|
||
|
||
> **Thesis:** while a loop is live, the Jam Guide section stops being "Roadmap + one
|
||
> thumbnail per chord, tap to see more" and becomes a **playhead accordion**: a
|
||
> station-aligned rail under the Roadmap where the *current* station's column is
|
||
> expanded to the full D-30 voicing gallery (every placeable guitar shape, or every
|
||
> piano style, side by side) and every other station shows its recommended thumb.
|
||
> The expansion **follows the playhead** — over one loop cycle you see every
|
||
> variation of every chord with **zero clicks**. A licks strip for the matched style
|
||
> sits directly below, auto-sorted to the chord you're on.
|
||
>
|
||
> User directive (2026-07-10, verbatim): *"expand the learn session so we see as
|
||
> much as possible in voicing variations and styles without having to click buttons,
|
||
> the idea is to have it open as the jam is playing."*
|
||
|
||
**Decision authority:** per the sprint header (user away, no user gate), Muse picks
|
||
the strongest layout and records rationale + rejected alternatives (§6).
|
||
Implementation is task **L-33**; this doc is its blueprint. Depends on **D-30**
|
||
(VoicingBrowser becomes an all-variations gallery — chips removed, every
|
||
shape/style rendered simultaneously, each with its own ▶).
|
||
|
||
---
|
||
|
||
## 0. What exists today (read from the code, not assumed)
|
||
|
||
- `JamGuide.jsx` §jam renders `RoadmapAssembly`: `RoadmapTrack` on top, then a
|
||
**voicing strip** — ONE thumb per station (ChordDiagram thumb 75×72 px, or
|
||
MiniPiano D-24 cropped thumb ≈142 px for a 1-octave voicing / ≈266 px for a
|
||
2-octave one), tap-to-enlarge → full diagram + a `VoicingBrowser` mount.
|
||
Variations are therefore **one click away per chord** — the exact thing the
|
||
directive kills.
|
||
- `canonicalPos` (playhead station index, canonical KB order) already exists and
|
||
already drives `scrollIntoView` on the strip. **All glance data is already in
|
||
the component.**
|
||
- `VoicingBrowser` (post-D-30) renders ALL matching guitar shapes side by side
|
||
(cells ≈90–120 px wide) and all four piano styles side by side (cropped
|
||
MiniPiano thumbs ≈140 px+), each independently playable. Placeable guitar
|
||
shapes per chord: **3–5** (maj: 4 movable + 1 native open; min7/dom7/maj7: 3–4;
|
||
dim/half_dim: 2). Piano styles: always **4** (root/shell/rootlessA/rootlessB),
|
||
or the authored recipe (L-24) plus the computed four.
|
||
- `LickCard` has `size="thumb"` and `LicksSection` already knows how to read
|
||
`kb[style].instruments.guitar.licks` defensively.
|
||
|
||
---
|
||
|
||
## 1. Chosen layout — the playhead accordion
|
||
|
||
```
|
||
┌ KNOWLEDGE CENTER — ii–V–I in C major ────────────────────────────────── ▲ ┐
|
||
│ [▶ Jam Guide ●] [Explore] [Voicings] [Licks & Techniques] │
|
||
│ [🎸 Guitar][🎹 Piano][🎵 Bass] · [Jazz●][Blues][Rock]… │
|
||
│ ┌─ ROADMAP ──────────────────────────────────────────────────────────────┐ │
|
||
│ │ Dm7 (ii) ──C→B── G7 (V) «NOW» ──F→E── Cmaj7 (I) ▁▁▁▂█▂▁▁▁ beat │ │
|
||
│ └────────────────────────────────────────────────────────────────────────┘ │
|
||
│ ┌─ VARIATIONS · follows the playhead ────────────────────────────────────┐ │
|
||
│ │ Dm7 ┃ G7 — every shape ┃ Cmaj7 │ │
|
||
│ │ [thumb] ┃ [E Barre][A7 Barre][D Shape][Open G7] ┃ [thumb] │ │
|
||
│ │ (recmd.) ┃ ▶ ▶ ▶ ▶ ┃ (recmd.) │ │
|
||
│ └─────────────┸───────────────────────────────────────┸───────────────────┘ │
|
||
│ ┌─ JAZZ LICKS · over the V7 first ─────────────────────────── (scroll) ──┐ │
|
||
│ │ [lick thumb «fits G7»] [lick thumb] [lick thumb] … │ │
|
||
│ └────────────────────────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
- **Columns = stations** (canonical KB order — the kb-plan §4 grid intent,
|
||
finally literal): the rail's columns sit under the Roadmap's stations, same
|
||
order, same labels/rn.
|
||
- **Active column = expanded**: the station at `canonicalPos` renders the full
|
||
D-30 gallery (`<VoicingBrowser rootPc quality show={instrument}/>`). Collapsed
|
||
columns render today's recommended thumb (the KB play's shape / the threaded
|
||
piano voicing) — so the loop context and lookahead never disappear.
|
||
- **The accordion advances with the playhead.** Chord changes are the animation;
|
||
nothing else moves. Constant footprint: expanding one column collapses the
|
||
previous one, so the rail never grows taller mid-jam.
|
||
- **Licks strip** below: thumb LickCards for the active style, level-filtered,
|
||
sorted current-chord-context first (see §2.4).
|
||
|
||
Why this wins: it satisfies "as much as possible without clicking" *without*
|
||
pretending everything fits at once (§3 shows it can't) — across one loop cycle
|
||
the player is shown 100% of the variations, always for the chord their hands are
|
||
on, which is when a variation is actually learnable.
|
||
|
||
---
|
||
|
||
## 2. Auto-follow rules (zero clicks, precisely)
|
||
|
||
1. **Driver = `canonicalPos`** (already computed in JamGuide from
|
||
`findLoopPosition` + `match.rotation`). Expanded column = `canonicalPos`;
|
||
`position === -1` (loop known but playhead unknown) → station 0 expands, so
|
||
the rail is never all-collapsed. The active column auto-scrolls into view
|
||
(`scrollIntoView({inline:'center'})` — the existing strip pattern, prop-driven
|
||
off `position`, **no rAF, nothing tied to the audio thread**). Respect
|
||
`prefers-reduced-motion`: `behavior: 'auto'` instead of `'smooth'` (new —
|
||
today's strip smooth-scrolls unconditionally; fix while in there).
|
||
2. **Instrument tab** (existing) selects the gallery family — guitar shapes vs
|
||
piano styles. It's a mode preference, not a per-chord click; it keeps its
|
||
current default/behaviour.
|
||
3. **Loop change** (`match.id` / style / instrument change): rail re-derives from
|
||
the new `stationVoicings`, any pin (§4) resets — this is the *existing*
|
||
`selectedStation` reset effect, renamed. **No loop matched but chords are
|
||
committing** (`currentChord` set, `match.matched` false): the rail degrades to
|
||
a single "heard live" gallery — `parseChord(currentChord)` (the same
|
||
`src/lib/voicings.js` parser `VoicingsSection` uses) → one expanded
|
||
VoicingBrowser, re-aimed on every chord commit. **Nothing heard at all**: the
|
||
existing dashed empty state; no rail, no licks strip.
|
||
4. **Licks strip**: `kb[activeStyle].instruments.guitar.licks` (the defensive
|
||
read `LicksSection`'s local `licksFor` helper already implements), shared
|
||
level filter applied. Sort: licks whose `chordContext` matches the current
|
||
station's rn or quality first, with an accent ring + "fits G7 — now"
|
||
microcopy on those; re-sorts as the playhead advances (a reorder of ≤4 thumb
|
||
cards, cheap). `chordContext` is **free text** — the match MUST be
|
||
token-boundary on rn/quality (a naive substring makes rn "I" match "♭VII",
|
||
"Imaj7", "I7"); never ship "fits G7" on a ♭VII lick. The ring + "fits — now"
|
||
microcopy are **strip-owned chrome** rendered around the card — LickCard
|
||
itself shows `chordContext` only at `size="full"` and stays untouched, as §5
|
||
promises. Licks are **guitar-only in the KB**: when the instrument tab is
|
||
piano, the strip still shows the guitar licks (consistent with the existing
|
||
Licks & Techniques section, which does the same) and says so in its heading
|
||
("guitar licks"). Style has no licks → the strip **hides entirely**; an
|
||
empty state would steal glance space to say nothing.
|
||
5. **No auto-sound, ever.** Auto-follow never triggers `chordAudio` — speaker
|
||
output feeds the live mic and would poison the detection that drives the
|
||
playhead (the L-20 caveat becomes a feedback loop). Every ▶ stays a gesture.
|
||
The rail shows the mic-feedback microcopy once, not per gallery.
|
||
6. **`onFocusChord` (D-03 Fretboard link) stays gesture-driven.** Auto-follow
|
||
does NOT emit focus-chord — repainting the main fretboard every 2 s
|
||
uninvited would fight the player's own key view. Only a pin (§4) emits it,
|
||
preserving today's semantics exactly.
|
||
|
||
---
|
||
|
||
## 3. Density & space budget — honest math
|
||
|
||
Geometry sources: dock body = 70vh; RoadmapTrack station min-width 168 px + 34 px
|
||
rail; ChordDiagram thumb 75×72 px (+label); MiniPiano cropped thumb 142 px
|
||
(1-octave voicing) to 266 px (2-octave, e.g. most rootless voicings) × ~51 px
|
||
tall; D-30 gallery cells ≈ 90–120 px (guitar) / ≈ 140–280 px (piano) wide,
|
||
≈ 140 px (guitar) / ≈ 125 px (piano) tall including label + ▶.
|
||
|
||
### 1280 × 900 (the target the task names)
|
||
|
||
Vertical: dock body 630 px − section nav ≈ 49 − instrument/style row ≈ 47 −
|
||
content padding 32 − RoadmapTrack ≈ 230 − gap 16 = **≈ 256 px glance budget**.
|
||
|
||
- Guitar rail (heading + one row of cells ≈ 190 px): **fits**. ~66 px spare.
|
||
- Piano rail (≈ 175 px): **fits**.
|
||
- Licks strip (thumb LickCard ≈ 165 px; heading + thumb row ≈ 190 px): does
|
||
**NOT** also fit — 190 + 190 = 380 > 256. It sits just below the fold,
|
||
reachable by the dock's existing vertical scroll (a flick, not a click).
|
||
Fully above the fold only from ≈ 1070 px window height. Said plainly:
|
||
**at 1280×900 you get Roadmap + the full variations rail without scrolling;
|
||
licks are one scroll-flick down.**
|
||
|
||
Horizontal (~1200 px usable): collapsed guitar column ≈ 95 px; collapsed piano
|
||
column ≈ 160–280 px (D-24 crop width varies with voicing span); expanded guitar
|
||
gallery 3–5 cells ≈ 340–540 px. **Expanded piano gallery — the honest figure:**
|
||
root/shell cells are 1-octave crops ≈ 160 px, but **rootless A/B of any true-7th
|
||
chord span past one octave** (e.g. Dm7 rootlessA → notes [17, 21, 24, 28]), so
|
||
D-24's crop gives them a 2-octave keyboard = 266 px thumb ≈ 284 px cell. The
|
||
gallery is therefore 160 + 160 + 284 + 284 + gaps + section p-3 ≈ **940 px** —
|
||
not the ~640 px four 1-octave cells would suggest.
|
||
|
||
| Loop | Guitar rail width | Piano rail width | One row? |
|
||
|---|---|---|---|
|
||
| 3–4 stations | 2–3×95 + ~450 ≈ **640–740 px** | 2–3×~200 + ~940 ≈ **1350–1550 px** | guitar ✓ · piano ✗ (scrolls at ~1200 usable) |
|
||
| 8 stations (post-L-30) | 7×95 + ~450 ≈ **1115 px** | 7×~200 + ~940 ≈ **2300 px** | guitar ✓ (just) · piano ✗ |
|
||
|
||
**Cut order (what goes first):**
|
||
1. **Licks strip drops below the fold** (never cut, just deferred to scroll).
|
||
2. **Rail scrolls horizontally** with the active column auto-centred (the
|
||
existing strip pattern) — cells are **never shrunk** below the D-30 sizes;
|
||
a diagram you can't read is worth less than one you scroll to. This is the
|
||
piano rail's **normal state on any loop containing 7th chords — i.e. most
|
||
of them** (a rootless gallery is ~940 px on its own), not just long loops;
|
||
L-33's commit-5 viewport check must expect the piano rail to scroll, never
|
||
assert "one row fits".
|
||
3. **Narrow only (§ below): collapsed columns reduce to "next" only.**
|
||
|
||
### ~640 px wide (half-snapped window)
|
||
|
||
~576 px usable. The rail flips to the kb-plan §4 narrow rule — one thing per row:
|
||
- Row 1: the expanded gallery for the current station (guitar: 5 cells wrap to
|
||
2 rows ≈ 300 px; piano: 4 cells wrap to 2 rows ≈ 260 px).
|
||
- Row 2: a single **"next: Cmaj7"** collapsed thumb (lookahead is the one piece
|
||
of context worth its pixels at this width); other stations are dropped — the
|
||
Roadmap above still shows the whole loop.
|
||
- Licks strip: single-column, below, via vertical scroll.
|
||
Glance guarantee at narrow = **current chord's full gallery + the next chord's
|
||
thumb**, no interaction.
|
||
|
||
### 1280 × 800 (the older Electron-default figure in D-20)
|
||
|
||
Budget shrinks to ≈ 186 px — the guitar rail (~190 px) is 1 row of cells with the
|
||
heading merged into the rail's top edge (drop the standalone heading line,
|
||
−18 px) and fits; everything else as at 900.
|
||
|
||
---
|
||
|
||
## 4. Interaction stays optional (but stays)
|
||
|
||
Nothing essential is behind a click; everything useful still responds to one:
|
||
|
||
- **▶ per gallery cell** (D-30): audition that voicing. Keyboard-reachable,
|
||
descriptive `aria-label`s, one-at-a-time playback (D-30's stop-previous
|
||
contract).
|
||
- **Pin a station**: tapping a *collapsed* column pins its gallery open,
|
||
overriding auto-follow (the accordion stops moving) — this **replaces**
|
||
today's tap-to-enlarge and inherits its semantics: the pinned station emits
|
||
`{rootPc, quality}` via `onFocusChord` (D-03 Fretboard guide tones), and the
|
||
existing reset effect (loop/style/instrument change → null) clears it.
|
||
Unpin = tap again or tap the visible "follow the jam" chip that appears while
|
||
pinned. `aria-pressed` on columns, `aria-current` on the live one.
|
||
- **Tap a lick thumb** → the card enlarges inline (`size="full"`); tap again to
|
||
collapse. The thumb already shows the full tab shape — enlarging is comfort,
|
||
not information.
|
||
- **Chord label tap** → `onChordClick` → ChordDetailModal, as everywhere else.
|
||
|
||
Focus order follows DOM order (columns left→right, then licks); all targets keep
|
||
`focus-visible:ring-2 ring-accent`, min 32 px height. Tokens only — everything
|
||
here uses existing `surface/panel/border/accent` + amber; **no new colour**.
|
||
Contrast inherits the measured D-20 §8 commitments (accent small text on
|
||
`bg-surface` cards; gray-400 floor for load-bearing labels; gray-500 microcopy
|
||
only).
|
||
|
||
---
|
||
|
||
## 5. Migration order for L-33 (green at every commit)
|
||
|
||
**App.jsx contract: UNTOUCHED — zero changes, not even additive.** Every input
|
||
glance mode needs (`detectedProgression`, `keyInfo`, `chordHistory`, `bpm`,
|
||
`currentChord`, `onFocusChord`, `onChordClick`) already flows into JamGuide.
|
||
Audio callbacks are therefore untouched by construction. If L-33 finds a gap,
|
||
the rule is prop-addition-only on the existing mount, never a callback/ref edit.
|
||
|
||
Files (for Maestro to re-lock at promotion):
|
||
|
||
| File | Kind of change |
|
||
|---|---|
|
||
| `src/components/GlanceRail.jsx` | **NEW** — pure presentational: props `{ stations, activeIndex, pinnedIndex, onPin, instrument, keyRoot }`; composes collapsed thumbs (existing ChordDiagram/MiniPiano) + one `<VoicingBrowser show={instrument}/>` for the expanded column |
|
||
| `src/components/JamGuide.jsx` | **Restructured (jam section only)** — RoadmapAssembly's voicing strip + enlarge block replaced by GlanceRail + LicksStrip; `selectedStation` becomes `pinnedStation` (same reset effect, same `onFocusChord` wiring); `licksFor` — today a closure-local function inside `LicksSection` (JamGuide.jsx:489), not an export — is **lifted out during the restructure** and shared with the strip. Sections 2–4, the shell, nav, and collapsed bar untouched |
|
||
| `src/components/VoicingBrowser.jsx` | **Additive-optional** — a `dense` prop (trim section padding, suppress the per-mount mic microcopy since the rail shows it once). Skip entirely if the D-30 cells already sit within the §3 budget |
|
||
| `src/components/LickCard.jsx` | **Untouched** (`thumb`/`full` already exist) |
|
||
| `src/App.jsx`, `RoadmapTrack.jsx`, `MiniPiano.jsx`, `ChordDiagram.jsx`, all `src/lib/**` | **Untouched** |
|
||
|
||
Commit order:
|
||
|
||
1. **Extract `GlanceRail.jsx`** rendering *today's* strip behaviour verbatim
|
||
(thumbs + tap-to-enlarge), JamGuide mounts it — a pure move, zero visual diff.
|
||
2. **Accordion**: expanded column = `activeIndex` (auto-follow off
|
||
`canonicalPos`), pin/unpin replaces tap-to-enlarge, the VoicingBrowser mount
|
||
moves from the enlarge block into the expanded column; reduced-motion guard
|
||
on the auto-scroll.
|
||
3. **No-loop fallback**: `parseChord(currentChord)` single gallery.
|
||
4. **LicksStrip**: thumb cards, level filter, context-first sort + live ring,
|
||
hide-when-empty, tap-to-enlarge.
|
||
5. **Narrow reflow + cut rules** (§3): horizontal-scroll behaviour, ≤640 px
|
||
next-only collapse; check 1280×900, 1280×800, ~640 px.
|
||
|
||
Each commit: `npm run build` + `node scripts/smoke.mjs` green; Critic gates.
|
||
|
||
---
|
||
|
||
## 6. Rationale + rejected alternatives (no user gate this sprint)
|
||
|
||
**Chosen — playhead accordion**, because: (a) it is the kb-plan §4 grid
|
||
(columns = chords, active-column highlight, narrow → one-per-row) applied to
|
||
variations; (b) constant footprint that survives 8-station loops (L-30) instead
|
||
of degrading; (c) 100% of variations shown per loop cycle with zero clicks, and
|
||
always for the chord under the player's hands — the moment a grip is learnable;
|
||
(d) it *reuses* D-30's gallery as-is and App.jsx needs nothing.
|
||
|
||
**Rejected A — "everything expanded" grid** (every station × every variation at
|
||
once — the literal maximal reading of the directive). Killed by §3's math: piano
|
||
is 4 stations × 4 styles = 16 cells at the honest §3 sizes (rootless cells
|
||
≈ 284 px) ≈ **3,500+ px** wide, or 3+ wrapped rows ≈ 480 px
|
||
tall — both force scrolling *while playing*, which is worse than zero clicks:
|
||
it's continuous manual tracking. At 8-station loops it's hopeless on any
|
||
viewport. Guitar-only at exactly 3 stations barely fits — a layout that only
|
||
works for its demo case is not a design.
|
||
|
||
**Rejected B — "Now panel"** (full-width mega-view of only the current chord:
|
||
giant gallery + licks + scale, no station columns). Maximum per-chord detail,
|
||
but it discards lookahead — mid-jam the *next* chord matters more than the one
|
||
already sounding — and at 120 BPM a 4-chord loop swaps the entire panel every
|
||
~2 s: visually violent, nothing is on screen long enough to read. The accordion
|
||
keeps 80% of the detail and all of the context.
|
||
|
||
**Rejected C — side-by-side split** (Roadmap left, variations dock right).
|
||
A 4-station track already needs 4×168 + 3×34 ≈ 774 px; splitting 1280 gives it
|
||
~640 — the flagship Roadmap gains a permanent horizontal scrollbar to make room
|
||
for a panel that duplicates its station order 90° rotated. Collapses entirely at
|
||
narrow. Rejected on viewport economics.
|
||
|
||
**Rejected D — timed carousel** (auto-cycle variations for the current chord
|
||
every N seconds). Zero clicks, technically — but motion untied to the music is
|
||
the most distracting thing a stage display can do, and it invents a second clock
|
||
next to the playhead. The music already provides the rhythm of change; follow it.
|
||
|
||
---
|
||
|
||
## 7. Out of scope / flags for Maestro
|
||
|
||
- **Explore/Voicings/Licks sections**: unchanged; glance mode lives entirely in
|
||
the jam section. (The Voicings section already follows `currentChord` — that
|
||
behaviour is untouched.)
|
||
- **Piano collapsed-thumb width variance** (142–266 px per D-24's span-crop) is
|
||
the piano rail's main width pressure; if Critic finds real loops where it
|
||
reads badly, a future D-task could cap collapsed piano thumbs to a 1-octave
|
||
window around the bass — **not** in L-33's scope.
|
||
- **`dense` prop on VoicingBrowser** is optional and Muse-owned; L-33 should
|
||
attempt the rail with plain D-30 cells first.
|
||
- No new tokens, no new dependencies, no KB/theory/audio changes anywhere in
|
||
this design.
|