Files
multica/packages/core/notification-preferences/patch.ts
Jiayuan Zhang 19e52e007c MUL-4798: make Inbox notification preference updates atomic (#5451)
* fix(notifications): make preference updates atomic

Co-authored-by: multica-agent <github@multica.ai>

* fix(notifications): serialize preference mutations

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-15 17:52:10 +08:00

93 lines
2.2 KiB
TypeScript

import type {
NotificationGroupKey,
NotificationGroupValue,
NotificationPreferences,
} from "../types";
const NOTIFICATION_GROUP_KEYS: readonly NotificationGroupKey[] = [
"assignments",
"status_changes",
"comments",
"updates",
"agent_activity",
"system_notifications",
];
function preferenceValue(
preferences: NotificationPreferences,
key: NotificationGroupKey,
): NotificationGroupValue {
return preferences[key] ?? "all";
}
/**
* Convert the full preference object produced by the settings UI into the
* smallest atomic patch. Missing keys mean the default value ("all").
*/
export function deriveNotificationPreferencePatch(
previous: NotificationPreferences,
next: NotificationPreferences,
): NotificationPreferences {
const patch: NotificationPreferences = {};
for (const key of NOTIFICATION_GROUP_KEYS) {
const previousValue = preferenceValue(previous, key);
const nextValue = preferenceValue(next, key);
if (previousValue !== nextValue) {
patch[key] = nextValue;
}
}
return patch;
}
/** Apply a preference patch while keeping default "all" values sparse. */
export function applyNotificationPreferencePatch(
current: NotificationPreferences,
patch: NotificationPreferences,
): NotificationPreferences {
const next = { ...current };
for (const key of NOTIFICATION_GROUP_KEYS) {
const value = patch[key];
if (value === "muted") {
next[key] = value;
} else if (value === "all") {
delete next[key];
}
}
return next;
}
/**
* Roll back only values that still match this mutation's optimistic patch.
* A later toggle that touched the same key wins.
*/
export function rollbackNotificationPreferencePatch(
current: NotificationPreferences,
patch: NotificationPreferences,
previous: NotificationPreferences,
): NotificationPreferences {
const next = { ...current };
for (const key of NOTIFICATION_GROUP_KEYS) {
const patchedValue = patch[key];
if (
patchedValue === undefined ||
preferenceValue(current, key) !== patchedValue
) {
continue;
}
const previousValue = preferenceValue(previous, key);
if (previousValue === "all") {
delete next[key];
} else {
next[key] = previousValue;
}
}
return next;
}