Layout: tuner + metronome now stack vertically everywhere (was phrase-only).
Phrase metronome bug: clicks were gated on rhythm.active, but phrase mode
has no on/off switch (the sync checkbox replaces it) and the default is
active:false — so a synced run stayed silent with no in-mode remedy.
Now: phrase mode is always armed (sync checkbox = free vs synced,
sound checkbox = clicks), and phrase clickSound reads rhythm.sound only.
Single-note modes keep the rhythm.active on/off semantics.
- Phrase mode: Setup panel now sits between the title row and the
sheet music (was below the transport). It hosts the material options
plus the shared TargetNoteControls (key/strings/fret window).
- TargetNoteControls: in Phrase Mode the Key section binds to the
generator key (tonic+mode always applied, no enable toggle) and the
display-signature sync checkbox is hidden — phrase notation takes
its signature from the score's key, so a display-signature select
there would be a no-op.
- Footer filter button hidden in Phrase Mode (lives in Setup now);
still present in single-note modes.
- Remove leftover '&& null' placeholder blocks.
- Filter (key/strings/fret window) collapsed behind Filter button with
active-count badge; opens the unified TargetNoteControls panel
- Display key signature moved into the filter panel (all modes)
- Instrument + Tuning selects moved into Settings modal (Instrument
section); removed from footer
- Settings modal restructured into collapsible sections: Instrument,
Appearance, Audio, Microphone
- Phrase setup panel: removed cross-reference hint texts
- PracticeFilter is now the single source of truth: removed mirrored
keyTonic/keyMode/fretWindow fields from PhraseSettings
- Dark mode: neutral gray with slight blue tint (was warm brown)
- MetronomeWidget: the Phrase Mode switch now lives IN the widget as a
Free/Sync toggle. Free = independent metronome (own clock, starts when
toggled, with or without clicks). Sync = silent while idle, starts with
the exercise (count-in included), pendulum mirrors the run's beats.
- Fixed double phraseBeatBus.emit (leftover from an earlier edit) that
flipped the pendulum twice per beat — animation appeared frozen.
- Click decision unified: one sound checkbox governs both the widget and
(in sync mode) the run's click scheduler. 'Click 1 after count-in' is
musically the last count-in click (suppressed duplicate) — now with a
working pendulum the offset is visible rather than confusing.
- Giuliani figures extended: p-i-m-a-m-i, p-a-p-i-m-a, a-m-i-p-i-m,
m-i-a-m-i-m (six-note rolling/bass-skip/tremolo-adjacent figures).
- Arpeggio chord pool: togglable degree list (empty = all 7); functional
and random progressions honor the pool; first bar draws a random chord
from the pool.
- Tests 174/174.
- Phrase Mode (sync to metronome): the run's scheduler is the single click
source (with count-in); the metronome widget is muted and its pendulum
mirrors the run's beats via the new phraseBeatBus (audio-timestamp
scheduled). No more double clicks; clicks only while the run is active
(phraseRunBus gates the widget's own ticking).
- Schedule entries now carry continuous beat index + accent flag (downbeats
accented, count-in beat 1 accented).
- 'At your pace': optional click via the metronome toggle (kept explicit).
- Metronome widget: gate prop; in gated mode it listens to the beat bus
instead of running its own scheduler.
- Note Set dropdown and custom-fret Fret Range inputs removed from Controls:
the unified target-note filter (strings + fret window + key) fully
supersedes them; difficulty stays as internal default (clef/transpose
resolution) but is no longer user-facing.
- Pendulum: drop the beat-parity key that remounted the dot each beat
(killed the CSS transition); the same element now glides left<->right.
- Strings filter: per-string toggle buttons (replaces fret-window-only
semantics as a third 'limits notes' control); combines with fret window
and key filter in targetPool (fretted instruments).
- Key sync checkbox: display key signature can follow the target key
(major/minor mapping), default off — both remain independently settable.
- LiveNoteStaff: optional fixed clef — guitar/bass now keep their usual
clef instead of flipping at middle C; octave markers handle range.
- Unified metronome widget (MetronomeWidget) with animated linear pendulum
synced to the audible click; shown in ALL modes next to the tuner.
Phrase BPM now reads from settings.rhythm.bpm (single source); the
phrase-local BPM control was removed from the setup panel.
- Tests: 160/160; tsc + eslint clean; build ok.
- New shared PracticeFilter settings (keyTonic/keyMode/fretWindow), persisted
via AppSettings.practice, defaults off (behavior unchanged).
- music/targetPool.ts: single source for the effective target pool —
base playable range ∩ fret window ∩ key/mode pitch classes (9 modes incl.
pentatonic, same scale machinery as Phrase Mode).
- useGameLogic now builds validNotes via computeTargetPool.
- TargetNoteControls component (key on/off + tonic/mode, fret window on/off +
min/max) rendered in Controls for all game modes; key is independent of the
displayed key signature.
- Phrase Mode binds its key/fret-window setup to the same shared filter
(read/write mirror) and drops the duplicate controls in its setup panel.
- Tests: targetPool.test.ts (7 cases); suite 156/156 green, build ok.
- color-scheme per theme so native controls (selects, checkboxes, number
inputs, sliders) render dark-native instead of black-on-black.
- Themed .control-select text color; new .control-input class replaces
hardcoded white-on-black inline input styles.
- Tuner widget stays mounted when enabled: idle state (dimmed, needle parked,
'—') instead of appearing/disappearing. Fully theme-var based (was
hardcoded white — invisible in light mode).
- Remove CSS transitions on per-frame values (tuner needle, level meter):
transitions interrupted 60x/sec are left stuck by WebKit/Safari; the
display EMA from the detection loop replaces them.
- VexFlow notation: resolve CSS var() to concrete colors (SVG attributes
can't resolve var(), silently fell back to black) and override VexFlow's
hardcoded #444 ledger-line default; re-render on theme changes
(useLayoutEffect so children read the updated attribute in time).
- Themed switch toggles (were hardcoded #ddd / white — glowed in dark mode).
- Fixed-height pitch readout so the layout stops jumping when a note starts
or stops sounding.
- Split SettingsContext into context/Provider/hook files (fast-refresh);
memoized provider functions; typed webkitAudioContext; prefer-const;
removed 'as any' casts. ESLint now passes clean.