fix(desktop): notify with sound when a user returns to a stopped timer - #81
Merged
Merged
Conversation
Employees came back from a break, saw nothing, and carried on working after the idle watchdog had auto-stopped the timer -- so the work after their return was never recorded either. It takes one completely ordinary setup: a machine that never sleeps and never locks (on charger, external display, screensaver off). There, every existing path misses the user. - the idle alert beeps into an empty room, and the stop then dismisses it - the "Timer auto-stopped" toast fires at the moment of the stop, long before anyone is back at the desk - `notifyTrackingState()` -- whose entire job is telling the user their current state -- only runs on wake/unlock/startup, and such a machine emits none of those events - the idle watchdog self-gates on `isTimerRunning`, so the instant it stops the timer it also stops looking Nothing was watching for the user coming BACK. Adds a return-from-break watcher: the mirror image of the watchdog, running while the timer is STOPPED. It polls `getSystemIdleTime()` every 15s and fires when the user has been away past the org idle threshold and is active again. Absence is peak-tracked, because the poll that catches the return reads 0 -- the OS counter resets on the first keystroke -- so a last-reading model would make every absence invisible. An unreadable counter fails silent rather than inventing an absence. Three cues, because each one alone is routinely swallowed: - a `silent:false` notification with a unique id, so Action Center cannot dedup it against the auto-stop toast from hours earlier - an in-renderer WebAudio beep -- macOS Focus and Windows Action Center drop notification sound with no fallback, the same reason the idle alert carries its own. Three descending tones, distinct from the idle alert's two rising ones, and no external resource so the strict CSP is unchanged - the window itself, with a red banner that clears when tracking resumes Decision logic is pure in desktop/src/main/return-to-work.js. 24 regression tests in desktop/test/return-to-work.test.js (46 suites / 808 tests pass). Full write-up in bugs/desktop-no-notification-on-return-from-break.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
Employees came back from a break, saw nothing, and carried on working after the idle watchdog had auto-stopped the timer — so the work after their return went unrecorded too.
Why every existing path missed them
It takes one completely ordinary setup to reproduce: a machine that never sleeps and never locks — on charger, external display, screensaver off. The normal office desktop.
dismissIdleAlert(), so the modal is gone too.autoStopTimerForPowerEvent()shows the "Timer auto-stopped" toast at the moment of the stop, long before anyone is back at the desk.notifyTrackingState()— the function whose entire job is telling the user their current state — is only called onwake,unlock, andstartup. A machine that never slept and never locked emits none of those events.isTimerRunning, so the instant it stops the timer it also stops looking.Nothing was watching for the user coming back.
Fix
A return-from-break watcher — the mirror image of the idle watchdog, with the opposite gate. It runs while the timer is stopped, polls
powerMonitor.getSystemIdleTime()every 15s, and fires when the user has been away past the org's idle threshold and is active again.Decision logic is pure and unit-tested in
desktop/src/main/return-to-work.js:systemIdleSec === 0— the OS counter resets on the first keystroke — so a last-reading model would make every absence invisible.NaNfromgetSystemIdleTime()(some Wayland sessions throw) must not read as "away for ages".Three cues, deliberately
Each one alone is routinely swallowed:
silent: false, with a unique id — Action Center would otherwise dedup it against the auto-stop toast from hours earlier.Tests
desktop/test/return-to-work.test.js— 24 new tests covering the decision function (return vs still-away, short break, idle-alert deference, signed-out, cooldown,NaNhandling, org threshold), peak-idle tracking, the notification copy, and the wiring (stopped-gate, session lifecycle, all three cues, contextBridge-only channel, CSP-safe beep, banner cleared on start).46 suites / 808 tests pass.
Docs
bugs/desktop-no-notification-on-return-from-break.md(+ index row inbugs/README.md)CLAUDE.md— the watcher, the peak-tracking requirement, and why all three cues existReviewer notes
getSleepGapThresholdSec()— the same orgidle_timeoutthe watchdog and sleep-gap use, so one number still governs the whole "away" promise.unref()'d and torn down inremoveSessionListeners(), matching the watchdog.contextBridge; no new IPC surface beyond the onereturn-from-breakevent.🤖 Generated with Claude Code