Problem
src/lib/components/RouteProgress.svelte schedules four cascade setTimeout / setInterval timers (startDelayTimer, trickleTimer, completeTimer, fadeTimer) inside $effect reactions to navigating, but it does not clean them up on component unmount. If the component is destroyed mid-navigation (e.g. user logs out, sub-tree re-mounts, top-level layout swap), the timers keep firing against detached state until they self-resolve.
The clearTimers() helper exists but is only called from startProgress() / completeProgress() entry points. There is no onDestroy(clearTimers).
Repro
- Trigger navigation that takes >100ms (so
startDelayTimer fires).
- Before navigation completes, unmount the layout containing
<RouteProgress /> (e.g. force a full reload, switch auth state).
- Observe in DevTools that
trickleTimer continues writing to detached progress state.
Suggested fix
Two-line cleanup + a runed simplification of the three setTimeouts:
<script lang="ts">
import { useDebounce } from 'runed';
import { onDestroy } from 'svelte';
// Replace startDelayTimer/completeTimer/fadeTimer with cancellable useDebounce
// calls; keep trickleTimer as setInterval since it needs the recurring tick.
const scheduleShow = useDebounce(showProgress, START_DELAY_MS);
const scheduleFadeStart = useDebounce(() => {
opacity = 0;
scheduleFadeFinish();
}, SPEED);
const scheduleFadeFinish = useDebounce(() => {
showBar = false; opacity = 1; progress = 0;
}, SPEED);
function clearTimers() {
scheduleShow.cancel();
scheduleFadeStart.cancel();
scheduleFadeFinish.cancel();
if (trickleTimer) { clearInterval(trickleTimer); trickleTimer = null; }
}
onDestroy(clearTimers);
</script>
Eliminates 3 local timer variables, gets auto-cleanup of the debounce timers on unmount, and the explicit onDestroy(clearTimers) handles the interval.
Working diff verified in a downstream fork; happy to send a PR if useful.
Problem
src/lib/components/RouteProgress.svelteschedules four cascadesetTimeout/setIntervaltimers (startDelayTimer,trickleTimer,completeTimer,fadeTimer) inside$effectreactions tonavigating, but it does not clean them up on component unmount. If the component is destroyed mid-navigation (e.g. user logs out, sub-tree re-mounts, top-level layout swap), the timers keep firing against detached state until they self-resolve.The
clearTimers()helper exists but is only called fromstartProgress()/completeProgress()entry points. There is noonDestroy(clearTimers).Repro
startDelayTimerfires).<RouteProgress />(e.g. force a full reload, switch auth state).trickleTimercontinues writing to detachedprogressstate.Suggested fix
Two-line cleanup + a runed simplification of the three setTimeouts:
Eliminates 3 local timer variables, gets auto-cleanup of the debounce timers on unmount, and the explicit
onDestroy(clearTimers)handles the interval.Working diff verified in a downstream fork; happy to send a PR if useful.