fix(notifications): fetch system_notifications pref lazily

Settings is the only mounted reader of notificationPreferenceOptions,
so a fresh app start (or any session that never visits Settings) left
the cache empty and the muted preference silently fell back to default
"all". Switch the inbox:new handler to ensureQueryData so the value is
fetched on first use and cached for subsequent events.

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
Jiayuan Zhang
2026-05-06 16:59:01 +08:00
parent 56b71d62ce
commit a6c80d65d3

View File

@@ -28,8 +28,7 @@ import {
} from "../issues/ws-updaters";
import { onInboxNew, onInboxInvalidate, onInboxIssueStatusChanged, onInboxIssueDeleted } from "../inbox/ws-updaters";
import { inboxKeys } from "../inbox/queries";
import { notificationPreferenceKeys } from "../notification-preferences/queries";
import type { NotificationPreferenceResponse } from "../types";
import { notificationPreferenceOptions } from "../notification-preferences/queries";
import { workspaceKeys, workspaceListOptions } from "../workspace/queries";
import { chatKeys } from "../chat/queries";
import { useChatStore } from "../chat";
@@ -270,7 +269,7 @@ export function useRealtimeSync(
if (wsId) onIssueLabelsChanged(qc, wsId, issue_id, labels ?? []);
});
const unsubInboxNew = ws.on("inbox:new", (p) => {
const unsubInboxNew = ws.on("inbox:new", async (p) => {
const { item } = p as InboxNewPayload;
if (!item) return;
const wsId = getCurrentWsId();
@@ -280,14 +279,21 @@ export function useRealtimeSync(
// styling is enough — no need to interrupt with a banner. `desktopAPI`
// is injected by the preload script; its absence (web app) skips silently.
if (typeof document !== "undefined" && document.hasFocus()) return;
// Respect the user's system-notification preference. Read from the
// Query cache so we don't have to plumb it through hook params; if the
// pref hasn't loaded yet the default ("all") fires the banner.
// Respect the user's system-notification preference. The Settings page
// owns the only `useQuery` for this resource, so on a fresh app start
// (or any session that hasn't visited Settings) the React Query cache
// is empty — using `getQueryData` would silently default to "all" and
// ignore the user's saved choice. `ensureQueryData` resolves to the
// cached value if present and otherwise fetches once, populating the
// cache for subsequent events. On network failure we fall through to
// the default ("all") rather than swallow the banner entirely.
if (wsId) {
const prefData = qc.getQueryData<NotificationPreferenceResponse>(
notificationPreferenceKeys.all(wsId),
);
if (prefData?.preferences?.system_notifications === "muted") return;
try {
const prefData = await qc.ensureQueryData(notificationPreferenceOptions(wsId));
if (prefData?.preferences?.system_notifications === "muted") return;
} catch {
// Fall through with default behavior.
}
}
// Capture the source workspace slug at emit time. The user may switch
// workspaces before clicking the banner (macOS Notification Center