Say which property a JMAP SetError rejected

"Send failed: Invalid property or value." is Stalwart's description for
invalidProperties, and on its own it says nothing about what to fix. The
SetError also carries a `properties` array naming the offending fields, which
every call site was discarding.

setErrorMessage appends them, and the 35 places that surfaced a SetError -
send, save draft, mailboxes, calendars, contacts, sieve, files, signature
images, sharing - now go through it.
This commit is contained in:
2026-08-23 13:51:02 -07:00
parent 55af4eab8f
commit e05880eefc
10 changed files with 75 additions and 43 deletions
+7 -7
View File
@@ -1,5 +1,5 @@
import { create } from "zustand";
import { CAP, client } from "@/jmap/client";
import { CAP, client, setErrorMessage } from "@/jmap/client";
import type { BusyPeriod, Calendar, CalendarEvent, GetResponse, Id, ParticipantIdentity, QueryResponse, SetResponse } from "@/jmap/types";
import { toUTCDate, toLocalDateTime, zonedToDate, parseDuration, DAY_MS, browserTimeZone } from "@/lib/dates";
import { settings } from "./settings";
@@ -168,7 +168,7 @@ export const useCalendar = create<CalendarState>((set, get) => ({
const obj = { "@type": "Event", uid: crypto.randomUUID(), ...event, calendarIds: { [calendarId]: true } };
const res = await client.call<SetResponse<CalendarEvent>>("CalendarEvent/set", { accountId, create: { e: obj }, sendSchedulingMessages: sendInvites });
const err = res.notCreated?.e;
if (err) throw new Error(err.description ?? err.type);
if (err) throw new Error(setErrorMessage(err));
get().invalidate();
return res.created!.e!.id;
},
@@ -177,7 +177,7 @@ export const useCalendar = create<CalendarState>((set, get) => ({
const accountId = get().accountId!;
const res = await client.call<SetResponse>("CalendarEvent/set", { accountId, update: { [id]: patch }, sendSchedulingMessages: sendInvites });
const err = res.notUpdated?.[id];
if (err) throw new Error(err.description ?? err.type);
if (err) throw new Error(setErrorMessage(err));
get().invalidate();
},
@@ -185,7 +185,7 @@ export const useCalendar = create<CalendarState>((set, get) => ({
const accountId = get().accountId!;
const res = await client.call<SetResponse>("CalendarEvent/set", { accountId, destroy: [id], sendSchedulingMessages: sendInvites });
const err = res.notDestroyed?.[id];
if (err) throw new Error(err.description ?? err.type);
if (err) throw new Error(setErrorMessage(err));
set((s) => {
const events = { ...s.events };
delete events[id];
@@ -212,7 +212,7 @@ export const useCalendar = create<CalendarState>((set, get) => ({
const accountId = get().accountId!;
const res = await client.call<SetResponse<Calendar>>("Calendar/set", { accountId, create: { c: { name: "Calendar", ...data } } });
const err = res.notCreated?.c;
if (err) throw new Error(err.description ?? err.type);
if (err) throw new Error(setErrorMessage(err));
await get().loadCalendars();
return res.created!.c!.id;
},
@@ -221,7 +221,7 @@ export const useCalendar = create<CalendarState>((set, get) => ({
const accountId = get().accountId!;
const res = await client.call<SetResponse>("Calendar/set", { accountId, update: { [id]: patch } });
const err = res.notUpdated?.[id];
if (err) throw new Error(err.description ?? err.type);
if (err) throw new Error(setErrorMessage(err));
await get().loadCalendars();
},
@@ -229,7 +229,7 @@ export const useCalendar = create<CalendarState>((set, get) => ({
const accountId = get().accountId!;
const res = await client.call<SetResponse>("Calendar/set", { accountId, destroy: [id], onDestroyRemoveEvents: true });
const err = res.notDestroyed?.[id];
if (err) throw new Error(err.description ?? err.type);
if (err) throw new Error(setErrorMessage(err));
await get().loadCalendars();
get().invalidate();
},