From 5913d20cc29c0ac269a0947fc45c1502cf00e2d0 Mon Sep 17 00:00:00 2001 From: Frank Stellmacher <171614930+Psychotoxical@users.noreply.github.com> Date: Mon, 4 May 2026 20:47:15 +0200 Subject: [PATCH] fix(lyrics): keep highlight + auto-scroll alive after tab switch (#454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The track-change reset wiped lineRefs/wordRefs in its effect body, which ran *after* the commit phase that just populated them on remount. The tracker effect then saw an empty refs array, no-op'd the DOM update, but still advanced prevActive — so every following progress tick early-returned on `prev.line === lineIdx` and the highlight froze. Track the previous track id in a ref and skip the reset on initial mount, so refs only get cleared on an actual track change. --- src/components/LyricsPane.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/LyricsPane.tsx b/src/components/LyricsPane.tsx index fac4a17d..415598fa 100644 --- a/src/components/LyricsPane.tsx +++ b/src/components/LyricsPane.tsx @@ -38,6 +38,7 @@ export default function LyricsPane({ currentTrack }: Props) { const lineRefs = useRef<(HTMLDivElement | null)[]>([]); const wordRefs = useRef([]); const prevActive = useRef({ line: -1, word: -1 }); + const prevTrackId = useRef(undefined); const isUserScroll = useRef(false); const scrollTimer = useRef | null>(null); @@ -65,12 +66,18 @@ export default function LyricsPane({ currentTrack }: Props) { } }, [sidebarLyricsStyle]); - // Reset refs when track changes. + // Reset refs on actual track change. Skip the initial mount so we don't wipe + // the callback-refs that the commit phase just populated — that would leave + // the tracker effect with an empty lineRefs and freeze the highlight. useEffect(() => { - lineRefs.current = []; - wordRefs.current = []; - prevActive.current = { line: -1, word: -1 }; - scrollerRef.current?.jump(0); + const id = currentTrack?.id ?? null; + if (prevTrackId.current !== undefined && prevTrackId.current !== id) { + lineRefs.current = []; + wordRefs.current = []; + prevActive.current = { line: -1, word: -1 }; + scrollerRef.current?.jump(0); + } + prevTrackId.current = id; }, [currentTrack?.id]); // Imperative tracker — subscribes directly to the store, zero React re-renders per tick.