From db7520331733b49205e8c492a0cdcb4117d1250d Mon Sep 17 00:00:00 2001 From: Sara Hentzel Date: Fri, 29 May 2026 14:56:35 -0500 Subject: [PATCH] TT-7376 audio editing zoom and position fixes --- src/renderer/src/components/WSAudioPlayer.tsx | 18 +++++++++++++++++- .../src/components/WSAudioPlayerZoom.tsx | 8 ++++++-- src/renderer/src/crud/useWaveSurfer.tsx | 3 ++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/components/WSAudioPlayer.tsx b/src/renderer/src/components/WSAudioPlayer.tsx index 4632b719..df58b81a 100644 --- a/src/renderer/src/components/WSAudioPlayer.tsx +++ b/src/renderer/src/components/WSAudioPlayer.tsx @@ -409,6 +409,9 @@ function WSAudioPlayer(props: IProps) { useContext(HotKeyContext).state; const [pxPerSec, setPxPerSecx] = useState(maxZoom); const pxPerSecRef = useRef(maxZoom); + /** When set, the zoom to re-apply after an edit reload (snip/undo) so the + * waveform doesn't snap back to fit-to-width. Cleared once consumed. */ + const preserveZoomOnReloadRef = useRef(undefined); const insertingRef = useRef(false); /** Bumped when user stops recording so in-flight preview inserts are ignored after await. */ const recordPreviewGenerationRef = useRef(0); @@ -1171,7 +1174,18 @@ function WSAudioPlayer(props: IProps) { setDuration(duration); if (loadingAnother) return; setReady(true); - if (!recordingRef.current) setPxPerSec(wsFillPx()); + if (!recordingRef.current) { + // After an edit reload (snip/undo) restore the prior zoom instead of + // snapping to fit-to-width. Only restore when it was zoomed in past fit. + const preserved = preserveZoomOnReloadRef.current; + preserveZoomOnReloadRef.current = undefined; + if (preserved !== undefined && preserved > wsFillPx()) { + setPxPerSec(preserved); + wsZoom(preserved); + } else { + setPxPerSec(wsFillPx()); + } + } if (segmentsRef.current) loadRegions(); if (setBusy) setBusy(false); @@ -1275,12 +1289,14 @@ function WSAudioPlayer(props: IProps) { const handleDeleteRegion = () => { setPlaying(false); + preserveZoomOnReloadRef.current = pxPerSecRef.current; wsRegionDelete().then(() => { handleChanged(); }); }; const handleUndo = useCallback(() => { + preserveZoomOnReloadRef.current = pxPerSecRef.current; wsUndo().then(() => { handleChanged(); }); diff --git a/src/renderer/src/components/WSAudioPlayerZoom.tsx b/src/renderer/src/components/WSAudioPlayerZoom.tsx index 70ba2b43..eb1fc965 100644 --- a/src/renderer/src/components/WSAudioPlayerZoom.tsx +++ b/src/renderer/src/components/WSAudioPlayerZoom.tsx @@ -53,11 +53,15 @@ function WSAudioPlayerZoom(props: IProps) { }; useEffect(() => { setZoomMin(fillPx); - // Only sync zoom to fillPx when fillPx actually changes (e.g. resize), not on mount. + // fillPx is the fit-to-width minimum. When it changes (resize, or duration + // change after a snip), only pull the zoom up if the current zoom is now + // below that minimum. Otherwise keep the user's zoom so editing audio + // doesn't reset how far they were zoomed in. // On mount, curPx effect sets zoom; avoid overwriting with fillPx so menu reopen keeps zoom. if ( prevFillPxRef.current !== undefined && - prevFillPxRef.current !== fillPx + prevFillPxRef.current !== fillPx && + zoomRef.current < fillPx ) { setZoom(fillPx); } diff --git a/src/renderer/src/crud/useWaveSurfer.tsx b/src/renderer/src/crud/useWaveSurfer.tsx index 436741a1..a31fc530 100644 --- a/src/renderer/src/crud/useWaveSurfer.tsx +++ b/src/renderer/src/crud/useWaveSurfer.tsx @@ -786,7 +786,8 @@ export function useWaveSurfer( }; const wsUndo = async () => { - if (undoBuffer) await loadDecoded(undoBuffer, 0); + // wsGoto clamps the position to the restored duration on load, so no harm if past end. + if (undoBuffer) await loadDecoded(undoBuffer, progress()); else { wsClear(); }