fix(web): classic timer ran at 2x (idempotent startTimer) #96

Merged
funman300 merged 1 commits from fix/classic-timer-double-count into master 2026-06-24 00:18:53 +00:00
Owner

Problem

The /play-classic game timer counted at 2× speed, and it was the cause of the last 2 failing web-e2e tests (16/18 after #95).

startTimer() always did timerInterval = setInterval(...) without clearing any existing interval. The visibilitychange handler calls startTimer() on "visible", so a load-time visibilitychange (while startGame's timer is already running) stacks a second interval. From then on elapsedSecs increments twice per second, and stopTimer() only clears one of the two — so the leak persists.

This exactly reproduced the e2e numbers:

  • runFor(3000) → expected 0:03, got 0:06 (two intervals)
  • runFor(2000)+hide+runFor(5000)+show+runFor(2000) → expected 0:04, got 0:13 (2×2 + 5_leaked + 2×2)

Fix

Make startTimer() idempotent — no-op if an interval is already running. One line, fixes both the user-visible 2× timer and the two timer tests.

After merge

web-e2e triggers (touches solitaire_server/web/) and should be 18/18. No wasm rebuild needed (game.js isn't a wasm-feeding crate).

🤖 Generated with Claude Code

## Problem The `/play-classic` game timer counted at **2× speed**, and it was the cause of the last 2 failing `web-e2e` tests (16/18 after #95). `startTimer()` always did `timerInterval = setInterval(...)` without clearing any existing interval. The `visibilitychange` handler calls `startTimer()` on "visible", so a load-time visibilitychange (while `startGame`'s timer is already running) **stacks a second interval**. From then on `elapsedSecs` increments twice per second, and `stopTimer()` only clears one of the two — so the leak persists. This exactly reproduced the e2e numbers: - `runFor(3000)` → expected `0:03`, got `0:06` (two intervals) - `runFor(2000)+hide+runFor(5000)+show+runFor(2000)` → expected `0:04`, got `0:13` (2×2 + 5_leaked + 2×2) ## Fix Make `startTimer()` idempotent — no-op if an interval is already running. One line, fixes both the user-visible 2× timer and the two timer tests. ## After merge `web-e2e` triggers (touches `solitaire_server/web/`) and should be **18/18**. No wasm rebuild needed (game.js isn't a wasm-feeding crate). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
funman300 added 1 commit 2026-06-24 00:18:26 +00:00
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) <noreply@anthropic.com>
funman300 merged commit 923a67dc7b into master 2026-06-24 00:18:53 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: funman300/Ferrous-Solitaire#96