From d4ad184324c6d095a3fc843a3ddd2e9cf6028c23 Mon Sep 17 00:00:00 2001 From: funman300 Date: Tue, 23 Jun 2026 17:18:10 -0700 Subject: [PATCH] fix(web): make classic timer idempotent to stop 2x double-counting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /play-classic timer ran at double speed. startTimer() always created a new setInterval and overwrote timerInterval without clearing the old one, so any extra call leaked a second interval that also incremented elapsedSecs. The visibilitychange handler calls startTimer() on "visible", and a load-time visibilitychange (while startGame's timer is already running) stacks a second interval — after which stopTimer() only clears one, so the leak persists and the clock counts ~2x forever. Guard startTimer() to no-op when an interval is already running. This fixes the two e2e timer tests that surfaced it once the suite actually ran (they were asserting 0:03 / 0:04 but seeing 0:06 / 0:13), and the user-visible 2x timer. Co-Authored-By: Claude Opus 4.8 (1M context) --- solitaire_server/web/game.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/solitaire_server/web/game.js b/solitaire_server/web/game.js index dec4e95..7270052 100644 --- a/solitaire_server/web/game.js +++ b/solitaire_server/web/game.js @@ -270,6 +270,11 @@ function startGame(seed) { // ── Timer ──────────────────────────────────────────────────────────────────── function startTimer() { + // Idempotent: never stack a second interval. The visibilitychange handler + // and startGame can both call this, and a stray call (e.g. a load-time + // visibilitychange while a timer is already running) would otherwise leak + // the old interval and make elapsedSecs increment twice per second. + if (timerInterval) return; timerInterval = setInterval(() => { elapsedSecs++; updateTimerDisplay();