Add meeting app exclusions and a Settings window - #16
Merged
Conversation
Meeting detection prompts for any external app on the mic, which is the right default — an allowlist misses meeting apps nobody has heard of. But there was no way to tell it "not this one, ever". Now there is: an "Ignore <App>" button on the meeting prompt, an "Ignore" on the auto-record toast, and a manageable list of them under Meetings in a new Settings window. Excluded apps are invisible to every part of meeting logic, not merely unprompted. The list stores bundle identifiers rather than names or paths: stable across renames and localization, and resolvable back to an icon and a name through NSWorkspace. A capture process with no .app around it has no identity to store, so it gets no Ignore button — Dismiss still covers it. "Edit config…" becomes "Settings…", a lazily-created window covering every config key, with "Open Config File" preserved at the bottom. config.json stays the single store; the window is a GUI over it and the existing watcher turns each change into a live reload. Config grows a write path for this — update() plus a serializer that emits the file in template key order and round-trips keys yap has never heard of. The dropdown itself gets a header card (mark, state line, model) in place of two disabled text lines, and SF Symbols on the actions. Measured: none of this touches AudioCapture, TextInjector or the placement path. The exclusion check runs inside onMeetingStart, which is event-driven, not the once-a-second detector poll, and it runs before the accessibility title lookup — so an excluded app costs strictly less than one that prompts.
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.
What
Meeting detection prompts for any external app on the mic. That default stays — an allowlist silently misses meeting apps nobody has heard of — but there is now a way to say "not this one, ever":
meeting_excluded_apps, ends any recording that toast just started, and yap says nothing about that app again.Excluded apps are invisible to every part of meeting logic — the check runs first in
onMeetingStart, before the accessibility title lookup and before the back-to-back-session stop — not merely unprompted.The list stores bundle identifiers, not names or paths: stable across renames and localization, and resolvable back to an icon and a name through
NSWorkspace. A capture process with no.apparound it has no identity to store, so it gets no Ignore button; Dismiss still covers it.Settings window
"Edit config…" becomes "Settings…", a lazily-created window covering every config key, with "Open Config File" preserved at the bottom.
config.jsonstays the single store: the window is a GUI over that exact file, and the existingConfigWatcherturns each change into a live reload — no Apply button, no restart.Configgrows the write path this needs:update(_:)plusserialized(_:), which emits the file in template key order and round-trips keys yap has never heard of, at both levels. A malformed file is left untouched rather than clobbered — somebody may be mid-edit.The dropdown gets a header card (mark, state line, model id) in place of two disabled text lines, plus SF Symbols and Title Case on the actions.
The one rule
No new dependencies (SwiftUI is a system framework), no new processes, nothing new running while idle — the window is not built until the menu item is first clicked, and the exclusion check runs inside
onMeetingStart, which is event-driven, not the once-a-second detector poll. None of this touchesAudioCapture,TextInjectoror the placement path, so the latency table is untouched; an excluded app now costs strictly less than one that prompts, because it returns before the AX title lookup.Verified
Local CI gate (
swift test,swift build -c release --arch arm64, the three smoke subcommands, embedded__info_plist) green, then a signed binary run in the foreground on an M4:"meeting_excluded_apps": []on start, values untouched."tap_to_toggle": truein template key order with unrelated keys preserved, stderr logsconfig reloaded, and the menu header flips toidle · tap fn to dictate.Dismiss · Ignore Google Chrome · Record; clicking Ignore writes["com.google.Chrome"]and logs◇ Google Chrome added to the ignore list. The next capture logs◇ Google Chrome is excluded — ignoringand shows no prompt.Stop · Ignore; Ignore adds the exclusion and stops the session it just started (○ stopped · 0:01).yap/● recording · 0:02/ model id live,Stop Recording · 0:02swapsrecord.circleforstop.circle,Settings…opens in front from an.accessoryapp.New unit tests cover
Config.serialized(_:): template round-trip with template key order, and unknown keys surviving at both levels.