From 168b36d419601a193f2d005a46d81ea2bd103989 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:26:28 +0200 Subject: [PATCH] fix: add calendarAddress and replyTo to calendar participants for Stalwart compatibility #189 #192 --- app/[locale]/calendar/page.tsx | 10 +++++++ components/calendar/event-modal.tsx | 2 ++ lib/calendar-participants.ts | 2 ++ lib/jmap/client.ts | 41 ++++++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/app/[locale]/calendar/page.tsx b/app/[locale]/calendar/page.tsx index 9b47ecda..ab1f5d06 100644 --- a/app/[locale]/calendar/page.tsx +++ b/app/[locale]/calendar/page.tsx @@ -112,6 +112,16 @@ export default function CalendarPage() { // Swipe navigation ref (handlers defined after navigatePrev/navigateNext) const touchStartRef = useRef<{ x: number; y: number; time: number } | null>(null); + // Keep detailEvent in sync with store events (e.g. after update + refetch) + useEffect(() => { + if (detailEvent) { + const updated = events.find(e => e.id === detailEvent.id); + if (updated && updated !== detailEvent) { + setDetailEvent(updated); + } + } + }, [events, detailEvent]); + // Check auth on mount useEffect(() => { checkAuth().finally(() => { diff --git a/components/calendar/event-modal.tsx b/components/calendar/event-modal.tsx index 51bcb835..c2ff0830 100644 --- a/components/calendar/event-modal.tsx +++ b/components/calendar/event-modal.tsx @@ -380,8 +380,10 @@ export function EventModal({ { name: organizerName, email: organizerEmail }, attendees ) as Record; + data.replyTo = { imip: `mailto:${organizerEmail}` }; } else if (attendees.length === 0 && event?.participants) { data.participants = null; + data.replyTo = null; } const shouldSendScheduling = attendees.length > 0 && sendInvitations; diff --git a/lib/calendar-participants.ts b/lib/calendar-participants.ts index 8c0b4406..c94c9d99 100644 --- a/lib/calendar-participants.ts +++ b/lib/calendar-participants.ts @@ -111,6 +111,7 @@ export function buildParticipantMap( '@type': 'Participant', name: organizer.name, email: organizer.email, + calendarAddress: `mailto:${organizer.email}`, roles: { owner: true, attendee: true }, participationStatus: 'accepted', scheduleAgent: 'server', @@ -124,6 +125,7 @@ export function buildParticipantMap( '@type': 'Participant', name: a.name, email: a.email, + calendarAddress: `mailto:${a.email}`, roles: { attendee: true }, participationStatus: 'needs-action', scheduleAgent: 'server', diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index 03203c27..f07aa5c1 100644 --- a/lib/jmap/client.ts +++ b/lib/jmap/client.ts @@ -3462,9 +3462,24 @@ export class JMAPClient implements IJMAPClient { } } - return allEvents + const filtered = allEvents .filter((event) => !isTaskObject(event)) .map((event) => normalizeCalendarEventLike(event)); + + const eventsWithParticipants = filtered.filter(e => e.participants && Object.keys(e.participants).length > 0); + debug.log('calendar', 'queryCalendarEvents participant summary', { + totalEvents: filtered.length, + eventsWithParticipants: eventsWithParticipants.length, + details: eventsWithParticipants.map(e => ({ + id: e.id, + title: e.title, + participantCount: Object.keys(e.participants!).length, + participants: e.participants, + replyTo: e.replyTo, + })), + }); + + return filtered; } catch (error) { console.error('Failed to query calendar events:', error); return []; @@ -3505,6 +3520,10 @@ export class JMAPClient implements IJMAPClient { accountId, sendSchedulingMessages, eventKeys: Object.keys(cleanEvent), + hasParticipants: !!cleanEvent.participants, + participantCount: cleanEvent.participants ? Object.keys(cleanEvent.participants).length : 0, + participants: cleanEvent.participants || null, + replyTo: cleanEvent.replyTo || null, }); const setArgs: Record = { @@ -3543,7 +3562,13 @@ export class JMAPClient implements IJMAPClient { if (createdId) { const created = await this.getCalendarEvent(createdId, targetAccountId); - debug.log('calendar', 'CalendarEvent/create fetched created event', getCalendarEventDebugSnapshot(created)); + debug.log('calendar', 'CalendarEvent/create fetched created event', { + ...getCalendarEventDebugSnapshot(created), + hasParticipants: !!created?.participants, + participantCount: created?.participants ? Object.keys(created.participants).length : 0, + participants: created?.participants || null, + replyTo: created?.replyTo || null, + }); if (created?.uid) { try { @@ -3666,7 +3691,16 @@ export class JMAPClient implements IJMAPClient { setArgs.sendSchedulingMessages = sendSchedulingMessages; } - debug.log('calendar', 'CalendarEvent/set update request', { eventId, accountId, cleanUpdateKeys: Object.keys(cleanUpdates), sendSchedulingMessages }); + debug.log('calendar', 'CalendarEvent/set update request', { + eventId, + accountId, + cleanUpdateKeys: Object.keys(cleanUpdates), + sendSchedulingMessages, + hasParticipants: !!cleanUpdates.participants, + participantCount: cleanUpdates.participants ? Object.keys(cleanUpdates.participants).length : 0, + participants: cleanUpdates.participants || null, + replyTo: (cleanUpdates as Record).replyTo || null, + }); const response = await this.request([ ["CalendarEvent/set", setArgs, "0"] @@ -3688,6 +3722,7 @@ export class JMAPClient implements IJMAPClient { debug.error('CalendarEvent/set notUpdated', { eventId, error }); throw new Error(error.description || "Failed to update calendar event"); } + debug.log('calendar', 'CalendarEvent/set update full response', { methodName, result }); debug.log('calendar', 'CalendarEvent/set update success', { eventId, updated: result.updated ? Object.keys(result.updated) : null }); return; }