Crafting a Drift-Free Wall-Clock Pomodoro Timer That Survives Tab Sleep and Reloads
3D Engineering · October 24, 2026 · 5 min read
Why naive setInterval timers fail in background browser tabs, and how we built a deterministic wall-clock state machine with local persistence.
Why setInterval(1000) Breaks in Modern Browsers
A surprising number of web timers decrement a counter inside `setInterval(() => seconds--, 1000)`. On a desktop or mobile browser, the moment you switch away from the study room tab to read a PDF or write code in VS Code, browser power-saving heuristics throttle background tab timers to once per minute—or freeze them entirely on iOS Safari and Android Chrome.
If a student starts a 25-minute focus session, switches to their lecture slides, and comes back 25 minutes later only to find their timer still says 19:42, trust in the app is broken immediately.
Pure Wall-Clock Timestamps and Phase Stepping
In Hushroom (`src/lib/timer.ts`), the timer state never stores a mutable remaining-seconds counter. Instead, it stores immutable wall-clock anchor timestamps (`startedAt`, `phaseEndsAt`), the current phase (`focus` or `break`), the completed session count for long-break cycles, and whether the timer is waiting for manual confirmation (`held`).
Every tick—and immediately upon tab visibility restoration or page reload—our pure function `stepUserTimer(timer, Date.now())` computes the exact progress and remaining seconds from the system clock. If a focus phase expired while the tab was backgrounded, Hushroom detects the exact transition, records the completed focus minutes to your subject analytics, rings the completion chime, and fires a system notification if enabled.
Flexible Rhythms for Every Study Style
Not every task fits a rigid 25/5 Pomodoro. We built four one-tap presets (25/5, 50/10, 15/3, and 45/15) alongside a custom rhythm drawer where you can configure focus blocks up to 120 minutes, short and long breaks, long-break frequency, and optional auto-start between phases.