fix(lyrics): keep highlight + auto-scroll alive after tab switch (#454)

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.
This commit is contained in:
Frank Stellmacher
2026-05-04 20:47:15 +02:00
committed by GitHub
parent a6cc2e2ad4
commit 5913d20cc2
+12 -5
View File
@@ -38,6 +38,7 @@ export default function LyricsPane({ currentTrack }: Props) {
const lineRefs = useRef<(HTMLDivElement | null)[]>([]);
const wordRefs = useRef<HTMLSpanElement[][]>([]);
const prevActive = useRef({ line: -1, word: -1 });
const prevTrackId = useRef<string | null | undefined>(undefined);
const isUserScroll = useRef(false);
const scrollTimer = useRef<ReturnType<typeof setTimeout> | 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.