Skip to main content

Siri & Shortcuts

How LucidPal integrates with Siri using the AppIntents framework.

Overview

LucidPal registers nine Siri intents via the AppIntents framework. Calendar and AI intents use a handoff pattern: they store a pending query in UserDefaults, tell Siri a brief spoken confirmation, and open the app. The app picks up the pending query when its scene becomes active. The three background-action intents — SaveNoteIntent, FindContactIntent, and LogHabitIntent — run entirely without opening the app (openAppWhenRun: false) and write directly to the app's shared document storage.

User: "Add dentist Friday at 10am to LucidPal"

AddCalendarEventIntent.perform()

UserDefaults["pm_siri_pending_query"] = "Add dentist Friday at 10am to my calendar"

return .result(dialog: "Opening LucidPal to add dentist Friday at 10am.")

App foregrounds → LucidPalApp reads UserDefaults key

SessionListViewModel.handleSiriQuery(_:) → new session → ChatViewModel.sendMessage()

LLM generates CALENDAR_ACTION block → CalendarEventPreview shown

Intent Inventory

IntentPatternTrigger phrasesPre-seeded queryUser parameter
AskLucidPalIntenthandoff"Ask LucidPal [question]"User-provided query@Parameter query: String
CheckCalendarIntenthandoff"Check my LucidPal calendar""What's on my calendar today?"
AddCalendarEventIntenthandoff"Add [event] to LucidPal""Add [event] to my calendar"@Parameter event: String
FindFreeTimeIntenthandoff"Find free time in LucidPal""Find a free 1-hour slot today"
DeleteCalendarEventIntenthandoff"Delete [event] in LucidPal"@Parameter eventName: String
UndoLastDeletionIntenthandoff"Undo my last LucidPal action", "Undo what I just did in LucidPal", "Undo last LucidPal change"
SaveNoteIntentbackground"Save note to LucidPal", "Add note to LucidPal", "Jot down in LucidPal"@Parameter title: String, @Parameter content: String
FindContactIntentbackground"Find contact in LucidPal", "Look up contact in LucidPal", "Get phone number from LucidPal"@Parameter name: String
LogHabitIntentbackground"Log habit in LucidPal", "Track habit with LucidPal", "Log my workout in LucidPal"@Parameter habitName: String, @Parameter value: Double

Handoff Key

All intents write to the same UserDefaults key:

UserDefaults.standard.set(query, forKey: "pm_siri_pending_query")

LucidPalApp reads and clears this key when the scene activates. The query is cleared immediately after forwarding to prevent replaying on subsequent launches.

Audio Feedback (ProvidesDialog)

Every intent conforms to ProvidesDialog, giving Siri a spoken response:

func perform() async throws -> some IntentResult & ProvidesDialog {
// ...store pending query...
return .result(dialog: "Opening LucidPal.")
}

Without ProvidesDialog, Siri would show a generic "Done" card with no audio confirmation.

AppShortcutsProvider

LucidPalShortcuts registers suggested phrases with the system (iOS 16.4+). Phrases use .applicationName interpolation so they survive app renames:

AppShortcut(
intent: CheckCalendarIntent(),
phrases: [
"Check my \(.applicationName) calendar",
"What's on my \(.applicationName) calendar",
"Show my \(.applicationName) schedule"
],
shortTitle: "Check Calendar",
systemImageName: "calendar"
)

On iOS < 16.4, the intents still work but users must add the shortcuts manually via the Shortcuts app.

SiriContextStore

SiriContextStore is a lightweight persistence layer that records the last calendar action taken — whether triggered by Siri or performed inside the app. UndoLastDeletionIntent reads from this store to know what to reverse.

Data model

struct SiriLastAction: Codable {
let type: ActionType // created | deleted | updated | rescheduled
let eventTitle: String
let eventStart: Date?
let eventEnd: Date?
let calendarName: String
let calendarIdentifier: String
let isAllDay: Bool
let location: String?
let notes: String?
let eventIdentifier: String // EKEvent.eventIdentifier used for undo
let timestamp: Date
}

ActionType is a String raw-value enum with four cases: created, deleted, updated, rescheduled.

Storage

SiriContextStore is a caseless enum with three static methods backed by UserDefaults key "pm_siri_last_action". JSON encoding/decoding uses JSONEncoder / JSONDecoder.

MethodSignatureDescription
writewrite(_ action: SiriLastAction)Encodes and persists the action
readread() -> SiriLastAction?Decodes and returns the last action, or nil
clearclear()Removes the stored value

Write sites

Call siteAction type written
CalendarActionController.createEvent().created (after EKEvent is saved)
ChatViewModel+CalendarConfirmation.confirmDeletion().deleted (after user confirms delete card)
ChatViewModel+CalendarConfirmation.confirmUpdate().updated or .rescheduled (after user confirms update card)

UndoLastDeletionIntent behaviour

UndoLastDeletionIntent.perform() reads SiriContextStore.read() then branches on type:

TypeBehaviour
.deletedRecreates the event via CalendarService; asks user to confirm first
.createdDeletes the event using eventIdentifier; asks user to confirm first
.updated / .rescheduledReturns a dialog informing the user that undo of edits is not yet supported
nil (nothing stored)Returns a dialog stating there is no recent action to undo