From f706336e58a68cc8791c569342a552eb037c1c01 Mon Sep 17 00:00:00 2001 From: Frank Stellmacher <171614930+Psychotoxical@users.noreply.github.com> Date: Fri, 5 Jun 2026 18:44:14 +0200 Subject: [PATCH] fix(waveform): repaint on Vite HMR so theme CSS var edits show live (#1005) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(waveform): repaint on Vite HMR so theme CSS var edits show live The seekbar caches its colours and repaints the canvas on a data-theme MutationObserver, so a manual theme switch picks up new waveform colours. Editing a theme's CSS variables via Vite HMR changes the stylesheet but not data-theme, so the observer never fires and the canvas keeps the old palette until a theme switch — annoying during theme dev. Add a dev-only effect that drops the colour cache and repaints on every HMR update (vite:afterUpdate). import.meta.hot is undefined in production builds, so the effect is stripped there — no runtime cost. * fix(waveform): guard HMR effect against partial import.meta.hot in tests vitest's SSR env exposes a partial import.meta.hot (has .on, no .off), so the cleanup threw and failed all WaveformSeek/PlayerBar tests. Feature-detect both .on and .off before wiring the vite:afterUpdate listener. --- src/components/WaveformSeek.tsx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/components/WaveformSeek.tsx b/src/components/WaveformSeek.tsx index 3542a445..021cfca3 100644 --- a/src/components/WaveformSeek.tsx +++ b/src/components/WaveformSeek.tsx @@ -219,6 +219,27 @@ export default function WaveformSeek({ trackId }: Props) { return () => observer.disconnect(); }, [seekbarStyle]); + // Dev-only: a theme's CSS variables can change via Vite HMR without the + // `data-theme` attribute changing, so the observer above never fires and the + // cached colours + canvas keep the old palette until a manual theme switch + // (annoying during theme dev — issue: waveform doesn't reflect CSS var edits). + // On every HMR update, drop the colour cache and repaint. `import.meta.hot` is + // undefined in production builds, so this whole effect is stripped there. + useEffect(() => { + // `import.meta.hot` is undefined in prod builds and only a partial stub in + // the test/SSR env (has `.on` but not `.off`), so feature-detect both before + // wiring — otherwise the cleanup throws under vitest. + const hot = import.meta.hot; + if (!hot || typeof hot.on !== 'function' || typeof hot.off !== 'function') return; + const repaint = () => { + invalidateColorCache(); + const canvas = canvasRef.current; + if (canvas) drawSeekbar(canvas, seekbarStyle, heightsRef.current, progressRef.current, bufferedRef.current, animStateRef.current); + }; + hot.on('vite:afterUpdate', repaint); + return () => { hot.off('vite:afterUpdate', repaint); }; + }, [seekbarStyle]); + const trackIdRef = useRef(trackId); trackIdRef.current = trackId; const seekRef = useRef(seek);