From 0945a0849776615d07e342c656fd8ce9d8a3784a Mon Sep 17 00:00:00 2001 From: vadimwit Date: Fri, 10 Jul 2026 00:50:33 +0100 Subject: [PATCH] =?UTF-8?q?feat(voicings):=20VoicingBrowser=20gallery=20?= =?UTF-8?q?=E2=80=94=20every=20shape=20and=20piano=20style=20side=20by=20s?= =?UTF-8?q?ide,=20no=20chips=20(task=20D-30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All placeable guitar shapes and all four pianoVoicing styles render simultaneously, each cell with its own play button; one-at-a-time playback and the show prop semantics preserved. Critic PASS (SSR-verified cell counts, zero aria-pressed, rootPc badge guard intact). Co-Authored-By: Claude Fable 5 --- src/components/VoicingBrowser.jsx | 235 +++++++++++++----------------- 1 file changed, 98 insertions(+), 137 deletions(-) diff --git a/src/components/VoicingBrowser.jsx b/src/components/VoicingBrowser.jsx index 9a51562..d5a2a51 100644 --- a/src/components/VoicingBrowser.jsx +++ b/src/components/VoicingBrowser.jsx @@ -1,38 +1,48 @@ -// VoicingBrowser — a standalone, playable voicing browser for ONE chord (task D-21). +// VoicingBrowser — a playable voicing GALLERY for ONE chord (task D-30; was the +// chip-switched browser of D-21/D-23). // // For a given { rootPc, quality } it shows every way the KB knows to voice that -// chord, switchable via chips, each auditionable through the speakers: +// chord — ALL AT ONCE, no chips, no selection state (user directive 2026-07-10: +// "see all the variations G shape, C shape, etc in one view without having to +// push a button. so they all line up next to each other"): // -// Guitar row — all `GUITAR_SHAPES[quality]` entries from src/lib/voicings.js -// that are placeable for this root (open shapes only in their native key, -// movable shapes only when the whole grip fits under fret 15), rendered via -// the existing ({rootStr, offsets} / {frets, onlyRoot} + rootPc). -// Piano row — the four src/lib/piano.js `pianoVoicing` styles -// (root / shell / rootlessA / rootlessB) rendered via , -// chips carrying each voicing's honest label (e.g. "rootless A (3-5-7-9)"). +// Guitar section — every placeable `GUITAR_SHAPES[quality]` entry from +// src/lib/voicings.js (open shapes only in their native key, movable shapes +// only when the whole grip fits under fret 15), each cell = shape label + +// + its own ▶. +// Piano section — all four src/lib/piano.js `pianoVoicing` styles +// (root / shell / rootlessA / rootlessB), each cell = the voicing's honest +// label (e.g. "rootless A (3-5-7-9)") + + +// its own ▶. // -// Playback: src/lib/chordAudio.js (L-20). Each row's ▶ plays the SELECTED -// voicing; the previous sound is always stopped via the returned {stop} handle -// before a new one starts (switching chips also stops it), so previews never -// layer. First ▶ click is the user gesture that lazily creates the AudioContext. +// Playback: src/lib/chordAudio.js (L-20). ONE live {stop} handle for the whole +// gallery — any ▶ stops the previous sound before starting (chord change and +// unmount also stop it), so previews never layer. First ▶ click is the user +// gesture that lazily creates the AudioContext. // -// Mount points (docs/design/knowledge-center.md §3 — wired by L-21/L-22, NOT here): -// Knowledge Center Voicings section, ChordDetailModal Guitar/Piano tabs, and the -// Jam Guide station-enlarge view. This component stays pure & prop-driven. +// Mount points (wired by L-21/L-22, NOT here): Knowledge Center Voicings +// section, ChordDetailModal Guitar/Piano tabs (show="guitar"/"piano", L-25), +// and the Jam Guide station-enlarge view. This component stays pure & prop-driven. +// +// Layout: each instrument section is a flex-wrap gallery of fixed-content-width +// cells, so it reflows to fewer columns (down to one cell per row) inside a +// narrow modal or the Jam Guide dock — no horizontal scroll needed except the +// per-cell guard around the widest MiniPiano thumbs (~266px for 2-octave +// rootless voicings). // // Props: // rootPc — chord root pitch class 0–11 (default 0 = C) // quality — CHORD_TYPES key; unknown values fall back to 'maj' // (matching voicings.js / piano.js behaviour) // show — 'guitar' | 'piano' | 'both' (default 'both', task D-23): which -// instrument row(s) to render. Any other value falls back to both, -// so every pre-existing mount renders identically with no prop. +// instrument section(s) to render. Any other value falls back to +// both, so every pre-existing mount renders identically with no prop. -import { useEffect, useMemo, useRef, useState } from 'react' +import { useEffect, useMemo, useRef } from 'react' import ChordDiagram from './ChordDiagram' import MiniPiano from './MiniPiano' import { GUITAR_SHAPES } from '../lib/voicings' -import { pianoVoicing, hasTrueSeventh } from '../lib/piano' +import { pianoVoicing } from '../lib/piano' import { playVoicing, guitarShapeToNotes } from '../lib/chordAudio' import { NOTES, CHORD_TYPES } from '../lib/theory' @@ -72,29 +82,8 @@ function chordName(rootPc, quality) { // ─── Small presentational atoms ─────────────────────────────────────────────── -// Selection chip. Active state puts small accent text on bg-surface (#0f0f0f), -// where accent #a855f7 measures ≈4.8:1 — AA for small text (surface-background -// rule); inactive text is gray-300 on surface (AA comfortable). -function Chip({ active, onClick, title, children }) { - return ( - - ) -} - +// Per-cell ▶. Small accent text sits on bg-surface (#0f0f0f), where accent +// #a855f7 measures ≈4.8:1 — AA for small text (surface-background rule). function PlayButton({ ariaLabel, onClick }) { return (