3.6 KiB
iCal (ICalSync)
Purpose
Sync the user's calendars and events with the server, and apply web-initiated event edits back into Calendar.app.
Direction
Bidirectional. Inbound: EventKit -> server. Outbound:
icloud.calendar_send_queue -> EventKit save/remove.
OS surface
EventKit (EKEventStore). Package.swift:73-77 adds
linkedFramework("EventKit"). Requires Calendars (full access) — module
surfaces .calendarAccessRequired if denied
(@packages/ical/Sources/ICalSync/SyncManager.swift:103-105).
Files
Reader.swift—CalendarReader.shared; owns the sharedEKEventStore(consumed by the Sender atSyncManager.swift:57).APIClient.swift:syncCalendars(_:)->POST /client/ical/calendars(line 108)syncEvents(_:)->POST /client/ical/events(line 122)getPendingSends()->GET /client/ical/send-queue/pending(line 137)reportSendResult(...)->POST /client/ical/send-queue/:id/result(line 165)getStats()->GET /client/ical/stats(line 173)
Sender.swift—CalendarSender(eventStore:); theapply(item)closure isSyncManager.swift:62-64.SyncManager.swift— overridesdidStartSync/willStopSyncto start / stop the send queue (SyncManager.swift:67-73). Authorization hooks (lines 97-106) gate cycles. Event batch size 200 (line 52).
Timing
- Read interval: 300s (
@packages/ical/Sources/ICalSync/SyncManager.swift:80). - Outbound poll interval: 60s (
@packages/ical/Sources/ICalSync/SyncManager.swift:61). - Event batch size: 200.
Server surface
- Entity tables:
icloud.calendars,icloud.events,icloud.calendar_send_queue(app/server.ts:44-45, 51). - Allowed send-queue actions:
create_event,update_event,delete_event(src/server/src/entities/calendarSendItem/types.ts:24). - Client routes (
src/server/src/surfaces/client/ical.ts):GET /client/ical/stats(line 55)POST /client/ical/calendars(line 59)POST /client/ical/events(line 65)GET /client/ical/send-queue/pending(line 71)POST /client/ical/send-queue/:id/result(line 89)
- Web routes (
src/server/src/surfaces/my/calendar.ts):GET /my/calendar/stats(line 46)GET /my/calendar/calendars(line 49)GET /my/calendar/events(line 57)POST /my/calendar/events(line 80)PUT /my/calendar/events/:id(line 90)DELETE /my/calendar/events/:id(line 102)
- Admin enqueue:
POST /admin/calendar-send-queue/enqueue(src/server/src/surfaces/admin/calendar-send-queue.ts:16).
Web surface
- Tab:
/calendar(web/src/App.tsx:60). - API helpers:
web/src/api/calendar.ts. - UI is intentionally minimal (list + form) per
~/.claude/plans/magical-tumbling-peach.md:180.
EventKit snippets
Reader fetches via EKEventStore.events(matching:) over a date predicate;
writer saves with EKEventStore.save(_:span:) /
EKEventStore.remove(_:span:). The Sender layer maps the JSON payload's
recurrence/alarm fields onto EKRecurrenceRule / EKAlarm.
Known limitations
- Last-write-wins on simultaneous edits (see known-limitations).
- Calendar source (iCloud / on-device / Exchange) is captured but the web UI does not let the user pick which source to create new events on; it uses the default.
Tests (@packages/ical/Tests/ICalSyncTests/)
ICalSyncTests.swift— Codable round-trips, payload shape (hermetic).SenderTests.swift—CalendarSender.applydispatch logic against a fake EventKit interface (hermetic).
Not covered: real EventKit save/remove (needs the OS), authorization prompts.