Quiet hours don't start (or end) while the app stays open: the window is only checked when the notifier happens to re-render #325

Closed
opened 2026-10-05 18:03:58 -04:00 by jared · 1 comment
Owner

useNotificationsQuiet() (src/app/hooks/useNotificationsQuiet.ts) evaluates isWithinTimeWindow(quietHoursStart, quietHoursEnd) at render time, with no clock tick. MessageNotifications and InviteNotifications (src/app/pages/client/ClientNonUIFeatures.tsx:232, 474) are long-lived listeners that rarely re-render, so their quietActive freezes at whatever it was at the last render. (The night-light overlay in App.tsx:145 has a 60 s forceTick for exactly this reason. The incoming-call ringtone is fine because its components mount fresh for each ring.)

Measured (headed Chromium so notification permission is really granted; page focused; Bob DMs while I'm in another room):

quiet hours 18:03–18:41 set at 18:02, then the app left alone
  18:02 (before window)                        → toast + sound   (expected)
  18:03:20 (inside window, app idle since 18:02) → toast + sound   ✗ quiet hours ignored
  after a reload, still inside the window      → nothing         (expected)

In practice someone who leaves Lotus open overnight with quiet hours 22:00–07:00 gets every message sound all night. In the morning the reverse can happen: still "quiet" until something re-renders.

Fix: give useNotificationsQuiet a minute tick (useState + setInterval(…, 60_000), like App.tsx's night light). Or, simpler for the listeners, call the pure isNotificationsQuiet({...now: new Date()}) inside deliverNotification / the invite handler at delivery time, reading the current settings from a ref. Add a unit test with fake timers that crosses the start boundary without re-rendering.


Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny 30e2dbcd, cinny-desktop 8f67ec7, element-call 3b0050e8.

`useNotificationsQuiet()` (src/app/hooks/useNotificationsQuiet.ts) evaluates `isWithinTimeWindow(quietHoursStart, quietHoursEnd)` **at render time**, with no clock tick. `MessageNotifications` and `InviteNotifications` (src/app/pages/client/ClientNonUIFeatures.tsx:232, 474) are long-lived listeners that rarely re-render, so their `quietActive` freezes at whatever it was at the last render. (The night-light overlay in `App.tsx:145` has a 60 s `forceTick` for exactly this reason. The incoming-call ringtone is fine because its components mount fresh for each ring.) **Measured** (headed Chromium so notification permission is really granted; page focused; Bob DMs while I'm in another room): ``` quiet hours 18:03–18:41 set at 18:02, then the app left alone 18:02 (before window) → toast + sound (expected) 18:03:20 (inside window, app idle since 18:02) → toast + sound ✗ quiet hours ignored after a reload, still inside the window → nothing (expected) ``` In practice someone who leaves Lotus open overnight with quiet hours 22:00–07:00 gets every message sound all night. In the morning the reverse can happen: still "quiet" until something re-renders. **Fix:** give `useNotificationsQuiet` a minute tick (`useState` + `setInterval(…, 60_000)`, like App.tsx's night light). Or, simpler for the listeners, call the pure `isNotificationsQuiet({...now: new Date()})` inside `deliverNotification` / the invite handler at delivery time, reading the current settings from a ref. Add a unit test with fake timers that crosses the start boundary without re-rendering. --- _Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny `30e2dbcd`, cinny-desktop `8f67ec7`, element-call `3b0050e8`._
jared added the bugpriority: mediumarea: notifications labels 2026-10-05 18:03:58 -04:00
Author
Owner

The same hook also evaluates isSnoozeActive(snoozeUntil) at render time. So Pause Notifications → 30 minutes likely has the mirror-image problem: when the 30 minutes are up, message and invite sounds/toasts stay suppressed until the notifier re-renders (a settings change, a room switch, etc.). This follows from the code; I didn't time it. The minute tick fixes both.

The same hook also evaluates `isSnoozeActive(snoozeUntil)` at render time. So **Pause Notifications → 30 minutes** likely has the mirror-image problem: when the 30 minutes are up, message and invite sounds/toasts stay suppressed until the notifier re-renders (a settings change, a room switch, etc.). This follows from the code; I didn't time it. The minute tick fixes both.
jared closed this issue 2026-10-17 17:51:40 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#325