perf: move meeting-prompt EKEventStore queries off the main actor#1070
Merged
Conversation
MeetingPromptDetector ran synchronous EKEventStore.events(matching:) queries on the main actor: every 20s poll, every workspace-app activation, and again on each runtime-prompt dismiss via nextRuntimePromptResumeDate. Large calendars caused recurring main-thread jank in the menubar app. Each poll cycle now does a single broad fetch (now-5min .. now+12h) on a background utility queue via MeetingPromptCalendarReader, converting events to Sendable snapshots on that queue (EKEvent objects must not cross threads), then hops back to the main actor with plain values. The synchronous paths — dismiss/remindSoon/markAccepted backoff decisions and the recording-start title hint — read the cached snapshots instead of querying live, so overlay callbacks and the MeetingSessionController title closure keep their sync signatures. Prompt scoring, snooze/pending bookkeeping, and heuristics are unchanged: the old narrow query window was strictly looser than the shouldOfferCalendarPrompt filter, so cache-derived candidates are identical, and dismiss resume dates use a cache at most one 20s poll stale (a prompt can only be dismissed after the evaluate cycle that refreshed the cache). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
MeetingPromptDetectorran synchronousEKEventStore.events(matching:)queries on the main actor: every 20s poll, every supported-app activation, and again on each runtime-prompt dismiss vianextRuntimePromptResumeDate. On large calendars each query blocks the main thread, causing recurring jank in a menubar app that should be invisible.Product Impact
meetingsmeeting reliabilityWhat changed
MeetingPromptCalendarReaderowns theEKEventStoreand runs queries on a background utility queue, bridged with a checked continuation. The store is never queried on the main thread.now-5min .. now+12h, covering both the prompt window and the 12h dismiss-resume lookahead) instead of a narrow query per poll plus ad-hoc 12h queries on dismiss/accept.MeetingPromptCalendarEventSnapshots on the reader's queue (EKEvent objects must not cross threads); only Sendable values hop back to the main actor. This also moves the NSDataDetector meeting-URL scan off main.dismiss/remindSoon/markAcceptedbackoff decisions andcurrentSuggestedTranscriptTitle()(consumed byMeetingSessionController's sync title closure) — read the main-actor snapshot cache instead of querying live.Semantics are preserved: the old narrow query window was strictly looser than the
shouldOfferCalendarPromptscoring filter, so cache-derived candidates are identical, and prompt scoring, snooze/pending bookkeeping, andMeetingPromptHeuristicsare untouched. Dismiss resume dates read a cache at most one 20s poll stale — a prompt can only be dismissed after the evaluate cycle that refreshed the cache.How I checked it
scripts/dev/agent-preflight.sh.agents/test-matrix.ymlfor the files changed (Sources/Meeting/**rule:bash build-deps.sh --force+ build + fast tests + integration smoke, all run bare)bash build.sh --no-openbash run-tests.sh— 4655/4655 passed, warning-free compile;MeetingPromptDetectorTestsandMeetingPromptHeuristicsTestsgreen and unmodifiedbash build.sh --no-openbundle gate)bash run-integration-smoke.shif I touchedSources/Meeting/orSources/TranscriptedCore/swift test— not required (noPackage.swift/Sources/TranscriptedCore/changes)Risk Review
.agent-review/visuals/evidence — n/aNotes
One behavior nuance:
currentSuggestedTranscriptTitle()now reads the latest polled snapshot rather than doing a fresh blocking query, so a recording started within the first second after launch (before the first poll lands) could miss the title hint. In exchange, recording start no longer pays a synchronous calendar query.Agent handoff
COORD_DONE: GREEN | PR URL below | moved MeetingPromptDetector EKEventStore queries to a background reader with a main-actor snapshot cache | none | none | build-deps --force, build.sh --no-open, run-tests.sh (4655 pass), run-integration-smoke.sh | human review + merge🤖 Generated with Claude Code