From 5cce94edba9e15b7c3125ea4e7816e6a226fca79 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Fri, 10 Jul 2026 20:43:08 -0400 Subject: [PATCH] refactor(notifications): quiet-hours uses shared tested time-window helper isInQuietHours was a hand-rolled, untested duplicate of the overnight-window logic. Replace it with the shared, unit-tested isWithinTimeWindow (utils/ timeWindow.ts) - identical behavior for valid HH:MM inputs, more robust on malformed ones (returns false rather than doing NaN math), and now covered by timeWindow.test.ts. One implementation instead of two. Co-Authored-By: Claude Opus 4.8 --- src/app/pages/client/ClientNonUIFeatures.tsx | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/app/pages/client/ClientNonUIFeatures.tsx b/src/app/pages/client/ClientNonUIFeatures.tsx index 36d0660ad..6ff73b64c 100644 --- a/src/app/pages/client/ClientNonUIFeatures.tsx +++ b/src/app/pages/client/ClientNonUIFeatures.tsx @@ -15,6 +15,7 @@ import { import { focusAssistActiveAtom } from '../../state/focusAssist'; import { manualDndAtom } from '../../state/manualDnd'; import { isSnoozeActive, notificationSnoozeUntilAtom } from '../../state/notificationSnooze'; +import { isWithinTimeWindow } from '../../utils/timeWindow'; import { roomToUnreadAtom } from '../../state/room/roomToUnread'; import LogoSVG from '../../../../public/res/lotus.png'; import LogoUnreadSVG from '../../../../public/res/lotus-unread.png'; @@ -69,18 +70,6 @@ import { // the async invite-atom population lands first and isn't mistaken for new invites. const INVITE_NOTIFY_ARM_DELAY_MS = 3000; -function isInQuietHours(start: string, end: string): boolean { - const now = new Date(); - const [sh, sm] = start.split(':').map(Number); - const [eh, em] = end.split(':').map(Number); - const cur = now.getHours() * 60 + now.getMinutes(); - const s = sh! * 60 + (sm ?? 0); - const e = eh! * 60 + (em ?? 0); - // start===end means zero-length window → treat as disabled (no quiet hours) - if (s === e) return false; - return s < e ? cur >= s && cur < e : cur >= s || cur < e; -} - function SystemEmojiFeature() { const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); @@ -259,7 +248,7 @@ function InviteNotifications() { focusAssistActive || manualDnd || isSnoozeActive(snoozeUntil) || - (quietHoursEnabled && isInQuietHours(quietHoursStart, quietHoursEnd)); + (quietHoursEnabled && isWithinTimeWindow(quietHoursStart, quietHoursEnd)); if (quietActive) return; if (showNotifications && notificationPermission('granted')) { @@ -539,7 +528,7 @@ function MessageNotifications() { focusAssistActive || manualDnd || isSnoozeActive(snoozeUntil) || - (quietHoursEnabled && isInQuietHours(quietHoursStart, quietHoursEnd)); + (quietHoursEnabled && isWithinTimeWindow(quietHoursStart, quietHoursEnd)); if (quietActive) return; if (showNotifications && notificationPermission('granted')) {