Skip to content

fix(desktop): notify with sound when a user returns to a stopped timer - #81

Merged
bladehell-ai merged 2 commits into
developfrom
fix/desktop-notify-on-return-from-break
Aug 21, 2026
Merged

fix(desktop): notify with sound when a user returns to a stopped timer#81
bladehell-ai merged 2 commits into
developfrom
fix/desktop-notify-on-return-from-break

Conversation

@bladehell-ai

Copy link
Copy Markdown
Collaborator

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.

  1. The idle detector fires and the idle alert beeps — into an empty room. The stop then calls dismissIdleAlert(), so the modal is gone too.
  2. autoStopTimerForPowerEvent() shows the "Timer auto-stopped" toast at the moment of the stop, long before anyone is back at the desk.
  3. notifyTrackingState() — the function whose entire job is telling the user their current state — is only called on wake, unlock, and startup. A machine that never slept and never locked emits none of those events.
  4. 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.

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:

  • Peak-tracked absence. The poll that catches the return reads systemIdleSec === 0 — the OS counter resets on the first keystroke — so a last-reading model would make every absence invisible.
  • Announce on the return, never during the absence. That is the whole point; the auto-stop toast already covers the moment of the stop.
  • Fails silent, not spurious. A NaN from getSystemIdleTime() (some Wayland sessions throw) must not read as "away for ages".
  • Defers to a live idle alert, never fires signed-out, one announcement per absence.

Three cues, deliberately

Each one alone is routinely swallowed:

  1. System notification, silent: false, with a unique id — Action Center would otherwise dedup it against the auto-stop toast from hours earlier.
  2. In-renderer WebAudio beep — macOS Focus and Windows Action Center drop notification sound with no fallback, the same reason the idle alert already carries its own. Three descending tones, distinct from the idle alert's two rising ones, so "you are not being tracked" never sounds like "are you still there?". No external resource, so the strict CSP is unchanged.
  3. The window itself, with a red banner that clears when tracking resumes — the cue no notification policy can suppress, and still there if the user was out of earshot.

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, NaN handling, 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 in bugs/README.md)
  • CLAUDE.md — the watcher, the peak-tracking requirement, and why all three cues exist

Reviewer notes

  • Threshold is getSleepGapThresholdSec() — the same org idle_timeout the watchdog and sleep-gap use, so one number still governs the whole "away" promise.
  • The interval is unref()'d and torn down in removeSessionListeners(), matching the watchdog.
  • Renderer changes stay behind the contextBridge; no new IPC surface beyond the one return-from-break event.

🤖 Generated with Claude Code

bladehell-ai and others added 2 commits August 20, 2026 21:08
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>
@bladehell-ai
bladehell-ai merged commit 38a183d into develop Aug 21, 2026
5 checks passed
@bladehell-ai
bladehell-ai deleted the fix/desktop-notify-on-return-from-break branch August 21, 2026 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant