Calendar EventKit Integration
Relevant source files
The following files were used as context for generating this wiki page:
The Calendar EventKit Integration in MacParakeet is designed to provide automated meeting reminders and recording triggers by leveraging Apple's EventKit framework. The system functions on a local-first principle, reading calendar events directly from the macOS Calendar app without requiring external cloud sync or OAuth flows for third-party providers like Google or Microsoft. This integration ensures that user data remains private and on-device while solving the common issue of users forgetting to initiate a recording.
The integration is primarily managed through the MeetingAutoStartCoordinator, which orchestrates polling, notification scheduling, and the UI countdown for automatic recording starts. It operates in three distinct modes: off, notify, and autoStart, allowing users to choose their preferred level of automation.
System Architecture
The architecture consists of a core service layer for interacting with EventKit, a logic layer for evaluating meeting triggers, and an app-level coordinator that handles side effects such as notifications and UI displays.
The diagram shows the data flow from the system EventKit store through core services and evaluation logic to the final application side effects.
Sources: spec/adr/017-calendar-meeting-auto-start.md, Sources/MacParakeetCore/Calendar/CalendarService.swift:7-14
Core Components
| Component | Description |
|---|---|
CalendarService | An actor-based wrapper for EKEventStore that handles permissions and fetches events on a background executor. |
MeetingMonitor | A pure, stateless state machine that evaluates fetched events against the current time and configuration to determine if reminders or auto-starts are due. |
MeetingLinkParser | A utility that extracts conference URLs (Zoom, Meet, Teams, etc.) from event locations, notes, and URL fields. |
MeetingAutoStartCoordinator | A @MainActor class that owns the poll timer and manages the execution of actions based on monitor events. |
Sources: spec/adr/017-calendar-meeting-auto-start.md, Sources/MacParakeetCore/Calendar/CalendarService.swift:15-20
Event Lifecycle and Evaluation
The system evaluates calendar events through a polling mechanism that adjusts its cadence based on the proximity of upcoming meetings. The baseline polling interval is 60 seconds, which accelerates to 5 seconds when an event is within a 2-minute window to ensure accurate countdowns.
Behavior Modes
The integration behavior is governed by the CalendarAutoStartMode enum:
- Off: No calendar integration active.
- Notify: Displays a macOS notification $N$ minutes before a matching event.
- AutoStart: Displays the reminder and initiates a 5-second cancellable countdown toast to start recording at $T-0$.
Sources: spec/adr/017-calendar-meeting-auto-start.md:42-56, plans/completed/granola-meeting-completion.md:126-130
Event Evaluation Flow
The sequence diagram illustrates the polling loop where fetched events are evaluated by the monitor to trigger notifications or UI countdowns.
Sources: spec/adr/017-calendar-meeting-auto-start.md:113-132, Sources/MacParakeetCore/Calendar/CalendarService.swift:101-118
Calendar Service Implementation
CalendarService is implemented as a Swift actor to ensure thread safety when interacting with EKEventStore, which Apple documents as not thread-safe.
Key Functions
requestPermission(): Asynchronously requests full access to the user's calendar. On success, it resets the store to ensure the next fetch is not stale.fetchUpcomingEvents(from:days:): Queries theEKEventStoreusing a predicate for a specific date range (defaulting to 7 days).fetchCurrentAndUpcoming(withinMinutes:): Specifically looks back 2 hours and forward a specified number of minutes to capture mid-meeting starts.
Configuration and Filtering
The system uses MeetingTriggerFilter to determine which events should trigger actions:
.withLink: Default. Triggers only for events with a recognized video conference URL..withParticipants: Triggers for events with 2 or more attendees..allEvents: Triggers for any non-all-day event.
Sources: Sources/MacParakeetCore/Calendar/CalendarService.swift:69-87, spec/adr/017-calendar-meeting-auto-start.md:58-69
Data Structures
CalendarEvent
A Sendable and Codable struct representing a flattened view of an EKEvent, stripped of GRDB dependencies to maintain in-memory-only persistence.
| Field | Type | Description |
|---|---|---|
id | String | Unique identifier for the event. |
title | String | The event title (defaults to "Untitled"). |
startTime | Date | Start time of the meeting. |
endTime | Date | End time (must be greater than start time). |
meetUrl | URL? | Extracted conference URL. |
userStatus | ParticipantStatus? | The RSVP status of the current user. |
Sources: Sources/MacParakeetCore/Calendar/CalendarService.swift:135-184, spec/adr/017-calendar-meeting-auto-start.md:167-172
UI and User Interaction
Countdown Toast
When in .autoStart mode, the MeetingAutoStartCoordinator invokes the MeetingCountdownToastController. This provides a 5-second cancellable "safety valve" to prevent unwanted recordings if the user has declined a meeting or is in a fake calendar block. The toast is a non-activating NSPanel (KeylessPanel) to avoid stealing focus from the user's work.
Sources: spec/adr/017-calendar-meeting-auto-start.md:71-76, plans/completed/granola-meeting-completion.md:155-163
Settings Integration
Calendar settings are integrated into the Meeting Recording card, appearing only when AppFeatures.calendarEnabled is true.
- RSVP Guard: Auto-start is gated on RSVP; declined or pending events are excluded.
- Include List: Users can select specifically which calendars (e.g., Work vs. Personal) the app should monitor.
Sources: spec/adr/017-calendar-meeting-auto-start.md:86-96, spec/README.md:68-75
Technical Constraints and Invariants
- Local-Only: No data leaves the device;
EventKitaccess stays within the system boundary. - Polling Hygiene: Timers are torn down when the app is idle or a recording is already active to preserve CPU.
- Zero-Duration Events: Inverted or zero-duration events are explicitly dropped during conversion.
- No SQLite Cache: Events are never stored in the application database to ensure privacy and simplicity.
Sources: spec/adr/017-calendar-meeting-auto-start.md:78-84, Sources/MacParakeetCore/Calendar/CalendarService.swift:142-145
Conclusion
The Calendar EventKit Integration provides a critical automation layer for MacParakeet meeting recording. By utilizing a stateless evaluation model and local system APIs, it balances user convenience with strict privacy standards. The use of actor-based services ensures high performance and thread safety on modern macOS versions.