feat(detection): auto-detect meetings via microphone activity (macOS) - #35
Merged
Conversation
Scaffolds the notification plumbing for mic-activity meeting detection (landing in the next commit). Adds `MeetingDetected(String)` and `MeetingEnded(String)` enum variants alongside their settings toggles (`show_meeting_detected` / `show_meeting_ended`, default on), manager helpers, and the "Meeting detected — tap to start recording" / "Meeting ended — tap to stop recording" body text. Surfaces both in the About debug dropdown so they can be fired manually.
Observes which non-Meetily apps hold the default input device. When a known meeting app (Zoom, Teams, FaceTime, Discord, Slack, any browser) sustains mic activity for 10s, fires a "Meeting detected — <app>" banner. Unknown apps wait 30s. During an active Meetily recording, a 30s silence window after mic release fires "Meeting ended — tap to stop recording". Matches the approach used by `char` (fastrepl/char) and Granola: no browser URL reading, no window title parsing, no new permissions. Implementation: - `state.rs`: pure state machine (`Idle → Sustaining → Detected → Ending → Idle`) parameterized over `Instant`; 23 unit tests cover priority upgrades, flicker, reacquire debouncing, dismissal cooldowns, and the "not recording" suppression path. - `matcher.rs`: hybrid allowlist + blocklist. Blocklist filters Meetily itself, Apple dictation/voice-memos, third-party whisper apps, and screen recorders. Allowlist maps bundle IDs to a cross- platform `App` enum (preserved for future Windows/Linux enablement). - `signals/mic_activity/macos.rs`: CoreAudio via `cidre`. Cheap gate on `kAudioDevicePropertyDeviceIsRunningSomewhere`, per-process enumeration only when hot. - `signals/mic_activity/stub.rs`: no-op fallback. Used on non-macOS targets so the detection service compiles and runs idle — no banners ever fire, but the task loop is harmless. - `service.rs`: 1s poll loop, `DetectionService` handle registered in Tauri state; shutdown gated on `RunEvent::Exit`. - `audio::recording_commands`: pushes recording state into the service via `try_state` so detection knows whether to fire meeting-ended banners without depending on audio internals. - Tauri command surface: `dismiss_detected_meeting`, `get_detection_state` for UI / agent observation. Events emitted as `meeting-detected` / `meeting-ended`. Scope: macOS only. Windows (WASAPI) and Linux (PulseAudio) samplers are deferred to follow-up PRs so they can be validated on real hardware and shipped behind a settings-level kill switch. The `App` enum and stub-sampler factory shape make adding those platforms a matter of slotting in platform files, not rearchitecting.
- set_recording moved to Arc<AtomicBool> on DetectionService; poll loop
syncs it into state before each advance. Removes the try_lock/spawn
fallback that could reorder rapid start/stop calls and wrongly gate
the MeetingEnded banner.
- DetectorPhaseSnapshot.bundle_id exposed for non-idle phases so agents
can act on get_detection_state alone (dismiss_detected_meeting
requires the raw bundle_id).
- meeting-detected / meeting-ended Tauri events gated on the matching
notification preference and emit DetectedMeetingEvent { display_name }
only. bundle_id stays Rust-side; agents read it via the command
surface, not the event bus.
- NSMicrophoneUsageDescription updated to cover the new process-
enumeration capability introduced by CoreAudio mic-holder detection.
The NotificationManager OnceCell migration (003) was dropped after
review: the race it addressed is narrow (racing first-callers during
startup eager-init) and the fallout benign (duplicate settings-file
write, idempotent delegate re-registration). Not worth ~100 LOC of
churn across every notification command handler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AzimovS
force-pushed
the
feat/meeting-detection-macos
branch
from
April 21, 2026 09:23
025451a to
951ea4b
Compare
AzimovS
added a commit
that referenced
this pull request
Apr 24, 2026
Prepares a release containing the work merged since v0.1.15: - feat(detection): mic-activity meeting auto-detection on macOS (#35) - feat(transcription): retry with backoff + in-transcript failure placeholder (#39) - feat(summary): TownHall template (#30), current template name in dropdown (#31), specificity prompt tweak (#37) - feat(remote): test-connection button + model selection surface improvements (#28, #29) - fix(notifications): migrate to UNUserNotificationCenter (#32), SAFETY + fallback race fixes (#33), unbundled-dev crash guard (#38), drop OS recording banners and flip auto_save default to false (#36) - chore(about): drop Zackriya services CTA (#40) Behavior change to call out in release notes: fresh installs (and users with no stored recording_preferences.json) now default auto_save to false — audio files are not written to disk unless the user opts in via Recording Settings. Existing users with saved preferences are unaffected. Co-authored-by: Claude Opus 4.7 (1M context) <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.
Summary
Adds meeting auto-detection via microphone activity on macOS. When a non-Meetily app holds the default input device past a sustain threshold, a "Meeting detected — " banner fires. When the mic is released during an active Meetily recording, a "Meeting ended — tap to stop recording" banner fires.
Matches the approach used by
char(fastrepl/char) and Granola — no browser URL reading, no window title parsing, no new permissions.Scope & rationale
macOS only. Windows (WASAPI) and Linux (PulseAudio) samplers are deferred to follow-up PRs. The
Appenum and stub-sampler factory shape make adding those platforms a matter of slotting in platform files, not rearchitecting.Why not all three platforms in one PR (see #34 history and the
phase2-drafttag for the record):show_meeting_detected/show_meeting_endedprefs only suppress the banner (the poll loop keeps running)phase2-drafttag (git show phase2-draft) preserves the Win/Linux work for when / if it picks upDetection thresholds
After natural meeting-end the state machine returns cleanly to Idle — no automatic 10-min dismissal. Dismissal exists as an internal API for a future "ignore this app" banner action.
Implementation
state.rs— pure state machine (Idle → Sustaining → Detected → Ending → Idle) parameterized overInstant; 23 unit tests cover priority upgrades, flicker, reacquire debouncing, dismissal cooldowns, and the "not recording" suppression path.matcher.rs— hybrid allowlist + blocklist. Blocklist filters Meetily itself (incl..devand.debugvariants), Apple dictation / voice-memos, third-party Whisper apps (superwhisper, macwhisper, Wispr Flow), and screen recorders (OBS, Loom, ScreenFlow). Allowlist maps bundle IDs to a cross-platformAppenum preserved for future enablement.signals/mic_activity/macos.rs— CoreAudio viacidre.kAudioDevicePropertyDeviceIsRunningSomewhereas a cheap gate; per-processkAudioProcessPropertyIsRunningInputenumeration only when hot. Bundle IDs fromkAudioProcessPropertyBundleID(macOS 14.2+).signals/mic_activity/stub.rs— no-op fallback used on non-macOS targets. Detection task still runs but never sees mic activity, so the state machine stays idle and nothing user-visible happens. Zero platform-specific code on Win/Linux means zero new crash surface.service.rs— 1s poll loop;DetectionServiceregistered in Tauri state;shutdown()gated onRunEvent::Exitso the poll task exits before state drop.audio::recording_commands— pushes recording state into the service viatry_stateso detection knows whether to fire meeting-ended banners without depending on audio internals.Tauri surface
dismiss_detected_meeting(bundle_id),get_detection_state()meeting-detected,meeting-ended(payload:{ display_name, bundle_id })Testing
Post-deploy monitoring
infolevel):detection: Idle → Sustaining(— candidate crossed observation thresholddetection: Sustaining(...) → Detected— banner fireddetection: Ending(...) → Idle— natural meeting-end (with / without banner based on recording state)mic-activity snapshot failed— CoreAudio sampler errorMeeting detection disabled — failed to init mic-activity sampler— fallback to stubFollow-up work
Phase 2a: Windows enablement (deferred to a later PR)
detection_enabledkill switch (settings-file gate checked beforespawn)IAudioSessionManager2-based sampler (draft available inphase2-drafttag)Phase 2b: Linux enablement (deferred)
application.process.binarykey semantics differ)libpulse-binding-based sampler (draft available inphase2-drafttag)Kill switch design (blocker for Phase 2)
detection_enabled: booltoNotificationSettings(defaulttrueon mac,falseon Win/Linux until validated per-user)detection::spawnand short-circuit if disablednotifications.json, setdetection_enabled: false, relaunch"Supersedes
Closes #34 — this PR is a clean-cut of the macOS-only subset. The Win/Linux work from that PR is preserved in the
phase2-drafttag.Plan documents
docs/plans/2026-04-20-feat-detect-meeting-start-and-end-plan.md— overall plan (Phase 1 + scoping decisions)docs/plans/2026-04-20-feat-meeting-detection-phase-2-windows-linux-plan.md— detailed Phase 2 plan (roadmap for follow-up PRs)docs/brainstorms/2026-04-20-meeting-auto-detect-brainstorm.md— origin discussion🤖 Generated with Claude Code