Files
webmail/lib/recurrence-overrides.ts
T
Stefan Hildebrandt b5e71e7b84 fix(calendar): edit a single recurring occurrence via a one-shot override patch
The "This event only" edit path built nested JMAP patch pointers
(recurrenceOverrides/<recurrenceId>/<field>). On the first edit of an
occurrence the override object doesn't exist yet, and JMAP pointers can't
create a missing intermediate object, so Stalwart returned notUpdated and
the edit failed with two error toasts.

Build the override as a single pointer recurrenceOverrides/<recurrenceId>
whose value is the occurrence's field set (dropping identity/series keys),
mirroring the working "delete this occurrence" path. Extracted as the pure,
tested helper buildRecurrenceOverridePatch.

Closes #774.
2026-08-16 21:24:52 +02:00

33 lines
1.0 KiB
TypeScript

import type { CalendarEvent } from '@/lib/jmap/types';
/** Identity / whole-series keys that must never go into a single-occurrence override. */
export const RECURRENCE_OVERRIDE_IMMUTABLE_KEYS = [
'id',
'uid',
'@type',
'calendarIds',
'recurrenceRules',
'recurrenceOverrides',
'excludedRecurrenceRules',
] as const;
/**
* Build the "This event only" override patch for a recurring master.
*
* Why one pointer: a JMAP nested pointer can't create a missing intermediate,
* so the override object has to be set whole at `recurrenceOverrides/<id>`, not
* field-by-field (#774).
*/
export function buildRecurrenceOverridePatch(
updates: Partial<CalendarEvent>,
recurrenceId: string,
): Partial<CalendarEvent> {
const immutable = new Set<string>(RECURRENCE_OVERRIDE_IMMUTABLE_KEYS);
const override: Record<string, unknown> = {};
for (const [key, value] of Object.entries(updates)) {
if (immutable.has(key)) continue;
override[key] = value;
}
return { [`recurrenceOverrides/${recurrenceId}`]: override } as Partial<CalendarEvent>;
}