diff --git a/artifacts/tx-os/src/hooks/use-notifications-socket.ts b/artifacts/tx-os/src/hooks/use-notifications-socket.ts index 08337bb8..d7d5b409 100644 --- a/artifacts/tx-os/src/hooks/use-notifications-socket.ts +++ b/artifacts/tx-os/src/hooks/use-notifications-socket.ts @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { io } from "socket.io-client"; import { useQueryClient } from "@tanstack/react-query"; import { @@ -19,17 +19,31 @@ import { notificationPlayer } from "@/lib/notification-sounds"; const BASE = import.meta.env.BASE_URL.replace(/\/$/, ""); +const SOCKET_WARMUP_MS = 3000; +const nowMs = () => + typeof performance !== "undefined" ? performance.now() : Date.now(); + export function useNotificationsSocket() { const { user } = useAuth(); const queryClient = useQueryClient(); + const connectedAtRef = useRef(0); useEffect(() => { if (!user) return; + // Suppress chimes for the first few seconds after the socket + // connects so any pending notifications that flush in on page + // load don't all play sounds back-to-back. + connectedAtRef.current = nowMs(); + const socket = io(window.location.origin, { path: `${BASE}/api/socket.io`, }); + socket.on("connect", () => { + connectedAtRef.current = nowMs(); + }); + socket.on("notification_created", (payload?: { type?: string }) => { queryClient.invalidateQueries({ queryKey: getListNotificationsQueryKey() }); queryClient.invalidateQueries({ queryKey: getGetHomeStatsQueryKey() }); @@ -52,6 +66,10 @@ export function useNotificationsSocket() { if (isOrder && !user.notifyOrdersEnabled) return; if (isMeeting && !user.notifyMeetingsEnabled) return; if (!isOrder && !isMeeting) return; + // Skip audio/vibration during the warmup window — UI/badge + // counts (above) still update, but we stay silent so a + // page-load flush doesn't sound like an alarm. + if (nowMs() - connectedAtRef.current < SOCKET_WARMUP_MS) return; const soundId = isOrder ? user.notificationSoundOrder : user.notificationSoundMeeting; diff --git a/artifacts/tx-os/src/lib/notification-sounds.ts b/artifacts/tx-os/src/lib/notification-sounds.ts index f78029d6..996f86f8 100644 --- a/artifacts/tx-os/src/lib/notification-sounds.ts +++ b/artifacts/tx-os/src/lib/notification-sounds.ts @@ -73,11 +73,12 @@ class NotificationPlayer { soundId: NotificationSoundId, opts: { vibrate?: boolean } = {}, ): void { - // Throttle: at most one sound every 600ms to avoid cacophony when - // many events arrive at once. + // Throttle: at most one sound every 3s to coalesce bursts of + // notifications (e.g. several orders arriving in quick succession) + // into a single chime instead of a string of overlapping beeps. const now = typeof performance !== "undefined" ? performance.now() : Date.now(); - if (now - this.lastPlayAt < 600) return; + if (now - this.lastPlayAt < 3000) return; this.lastPlayAt = now; const entry = SOUND_BY_ID.get(soundId) ?? SOUND_BY_ID.get("ding");