From 5f62a0ed1bdd7bde7901fd78f92c657be4affd85 Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Fri, 24 Apr 2026 20:35:17 -0700 Subject: [PATCH] fix(captures+chord): Stop button stops, ChordPicker accepts shorter chords MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two unrelated correctness bugs caught in PR review: - The Play As "Stop" button was wired to handlePlayAs() unconditionally, so clicking it during playback kicked a fresh generation instead of halting. Now pauses the player when the click came from the main button while playbackState is 'playing'. Picking a different voice from the dropdown still kicks a new generation as before. - ChordPicker tracked the peak set of held keys but seeded the peak from initialKeys, so a user who opened the picker with a 3-key chord saved couldn't replace it with a 2-key chord — the candidate length never beat the seed. The peak now resets on the first press of a fresh sequence (when no keys were held immediately prior), then grows monotonically within that hold. --- app/src/components/CapturesTab/CapturesTab.tsx | 8 ++++++++ app/src/components/ChordPicker/ChordPicker.tsx | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/src/components/CapturesTab/CapturesTab.tsx b/app/src/components/CapturesTab/CapturesTab.tsx index 525cdb39..65c66103 100644 --- a/app/src/components/CapturesTab/CapturesTab.tsx +++ b/app/src/components/CapturesTab/CapturesTab.tsx @@ -154,6 +154,8 @@ export function CapturesTab() { const playerIsPlaying = usePlayerStore((s) => s.isPlaying); const isPlayerVisible = !!audioUrl; + const setIsPlaying = usePlayerStore((s) => s.setIsPlaying); + const addPendingGeneration = useGenerationStore((s) => s.addPendingGeneration); const pendingGenerationIds = useGenerationStore((s) => s.pendingGenerationIds); @@ -410,6 +412,12 @@ export function CapturesTab() { const handlePlayAs = (voice?: VoiceProfileResponse) => { if (!selected) return; + // Stop the current playback when the button is in its 'playing' state + // and the user clicked the main button without picking a new voice. + if (!voice && playbackState === 'playing') { + setIsPlaying(false); + return; + } const target = voice ?? playAsVoice; if (!target) { toast({ diff --git a/app/src/components/ChordPicker/ChordPicker.tsx b/app/src/components/ChordPicker/ChordPicker.tsx index 933bac2e..23150e23 100644 --- a/app/src/components/ChordPicker/ChordPicker.tsx +++ b/app/src/components/ChordPicker/ChordPicker.tsx @@ -95,11 +95,12 @@ export function ChordPicker({ if (prev.has(canonical)) return prev; const next = new Set(prev); next.add(canonical); - // Update peak whenever the live set grows. Comparing against - // the captured chord (which may be the previous saved value) - // would lose the user's first new keypress. setCaptured((prevCaptured) => { const candidate = sortChordKeys(Array.from(next)); + // First key in a fresh sequence replaces the peak — otherwise a + // user trying to swap a longer saved chord for a shorter one is + // stuck because their candidate never beats the seed length. + if (prev.size === 0) return candidate; return candidate.length >= prevCaptured.length ? candidate : prevCaptured; }); return next;