diff --git a/web/src/lib/AutoCountdown.svelte b/web/src/lib/AutoCountdown.svelte index b13a4d1..9d090ae 100644 --- a/web/src/lib/AutoCountdown.svelte +++ b/web/src/lib/AutoCountdown.svelte @@ -24,12 +24,15 @@ // Re-baseline whenever the parent bumps tickKey, running flips on, or // duration changes mid-run (e.g. host moves the speed slider). + // Use a local snapshot so we don't read `tickStart` after writing it — + // that would make this effect depend on its own write and infinite-loop. $effect(() => { tickKey; // subscribe duration; // subscribe — keeps the contract explicit, not parent-coupled if (running) { - tickStart = performance.now(); - now = tickStart; + const t = performance.now(); + tickStart = t; + now = t; } }); diff --git a/web/src/lib/MasterPanel.svelte b/web/src/lib/MasterPanel.svelte index 6cd463f..eb4cf3a 100644 --- a/web/src/lib/MasterPanel.svelte +++ b/web/src/lib/MasterPanel.svelte @@ -123,12 +123,10 @@ autoRunning = false; return; } - // Re-baseline countdown on every (re-)arm: rising edge of autoRunning, - // speed change, autoCallEnabled toggle. handleDrawNext bumps it again - // per tick so the ring re-fills before the next interval elapses. - // Safe self-write: this effect doesn't read tickCount, so the bump - // can't re-trigger it. - tickCount++; + // No tickCount bump here on purpose: `tickCount++` would read tickCount + // and turn this effect into its own dependency (effect_update_depth_exceeded). + // AutoCountdown's reset effect already re-baselines on `running` rising + // edge and on `duration` change, so re-arms are covered without our help. const ms = settings.autoCallSpeed * 1000; const id = setInterval(() => { if (!state || state.remaining.length === 0) {