diff --git a/src/components/Controls.tsx b/src/components/Controls.tsx index 185eb7a..0eab364 100644 --- a/src/components/Controls.tsx +++ b/src/components/Controls.tsx @@ -68,8 +68,8 @@ export const Controls: React.FC = ({ currentPitch }) => { )} {shouldShowFilters && } - {/* Bottom Section: Tools Grid (2 Columns now) */} -
+ {/* Bottom Section: Tools Grid (stacked in all modes) */} +
{/* Tool 1: Tuner */}
diff --git a/src/components/MetronomeWidget.tsx b/src/components/MetronomeWidget.tsx index 77ae164..e344866 100644 --- a/src/components/MetronomeWidget.tsx +++ b/src/components/MetronomeWidget.tsx @@ -55,10 +55,15 @@ export const MetronomeWidget: React.FC = ({ const isSync = syncMode === 'option' && !!rhythm.syncToExercise; const runActive = gate === true; + // Phrase mode (syncMode 'option') has no on/off switch — the sync + // checkbox replaces it (unchecked = free metronome, checked = synced to + // the exercise), so the metronome is always armed there. Gating on + // rhythm.active would leave phrase users with no way to enable clicks. + const armed = syncMode === 'option' ? true : rhythm.active; // Sync: pendulum/click only while the run is active (the run's scheduler // is the click source; the widget's own scheduler stays off). // Free/plain: the widget's own scheduler runs whenever armed. - const running = isSync ? runActive : rhythm.active; + const running = isSync ? runActive : armed; const { restart } = useMetronome({ bpm: rhythm.bpm, @@ -78,7 +83,6 @@ export const MetronomeWidget: React.FC = ({ }, [isSync, runActive]); const beatMs = 60000 / Math.max(1, rhythm.bpm); - const armed = rhythm.active; return (
diff --git a/src/components/PhraseTrainer.tsx b/src/components/PhraseTrainer.tsx index b3c958f..b4958f4 100644 --- a/src/components/PhraseTrainer.tsx +++ b/src/components/PhraseTrainer.tsx @@ -85,13 +85,15 @@ export const PhraseTrainer = forwardRef( const pace: PhraseSettings['pace'] = settings.rhythm.syncToExercise ? 'tempo' : 'step'; phrase.pace = pace; // Click decision — ONE source: the metronome widget's sound checkbox. - // - Sync (tempo): run clicks when armed && sound (count-in incl.); - // widget silent while idle. - // - Free metronome: widget clicks freely; the run stays silent to - // avoid doubling (sync off in tempo = free widget during runs). - phrase.clickSound = pace === 'tempo' - ? settings.rhythm.active && settings.rhythm.sound - : settings.rhythm.active && settings.rhythm.sound && !settings.rhythm.syncToExercise; + // Phrase mode has no metronome on/off switch (the sync checkbox + // replaces it, see MetronomeWidget), so rhythm.active must NOT gate + // clicks here — with the default active:false a synced run would + // stay silent with no way to fix it from this mode. + // - Sync (tempo): run clicks when sound is on (count-in included); + // the widget stays silent while idle. + // - Free metronome (step): the widget clicks freely; the run stays + // silent to avoid doubling. + phrase.clickSound = settings.rhythm.sound; const setPhrase = useCallback((updates: Partial) => { updateSettings(s => ({ ...s, phrase: { ...getPhraseSettings(s), ...updates } })); }, [updateSettings]);