Skip to content

Suppress Calendar AX capture to prevent event popover dismissal#628

Open
akramj13 wants to merge 1 commit into
FuJacob:mainfrom
akramj13:fix/544-apple-calendar-bug
Open

Suppress Calendar AX capture to prevent event popover dismissal#628
akramj13 wants to merge 1 commit into
FuJacob:mainfrom
akramj13:fix/544-apple-calendar-bug

Conversation

@akramj13
Copy link
Copy Markdown

@akramj13 akramj13 commented Jun 7, 2026

Summary

  • Added a focused capture-suppression policy for Apple Calendar so Cotabby stops deep Accessibility enumeration before it destabilizes the event editor popover.
  • Wired the policy into the app environment ahead of the normal availability checks so the rest of Cotabby keeps running, but Calendar’s transient editing UI is left alone.
  • Added unit coverage for Calendar, ordinary apps, and the nil-bundle case, and updated the Xcode project to include the new files.

Testing

  • Passed targeted unit tests for AccessibilityCaptureSuppressionPolicy.
  • Passed a local macOS debug build.
  • Verified in Calendar that the date/time editor stays open while Cotabby is running.
  • Not run (not requested): full app test suite.

Solves #544

Greptile Summary

Introduces AccessibilityCaptureSuppressionPolicy, a hard-coded allowlist of apps whose AX trees Cotabby must not enumerate, and wires it into FocusTrackingModel's capture gate ahead of the normal user-preference pipeline. The immediate motivation is Apple Calendar's event-editor popover, which dismisses itself when Cotabby polls its transient AX hierarchy.

  • New policy enum (AccessibilityCaptureSuppressionPolicy) statically suppresses com.apple.iCal; the check fires before isGloballyEnabled or per-app toggle checks, so it cannot be overridden by user settings.
  • CotabbyAppEnvironment inserts the policy call at the top of isCaptureSuppressedForBundle; however, the companion shouldProcessEventsProvider closure on inputMonitor does not consult the policy, leaving Cotabby's keyboard event tap active when Calendar is frontmost.
  • Unit tests cover Calendar, a non-suppressed app, and a nil bundle; project file correctly adds both new sources to their respective build phases.

Confidence Score: 4/5

The Calendar-specific fix is narrow and well-targeted; the two observations are about design trade-offs rather than broken behaviour on the changed path.

The AX suppression path itself is correct, and the existing test coverage confirms the policy's basic contract. The gap between isCaptureSuppressedForBundle and shouldProcessEventsProvider means Cotabby's event tap stays alive in Calendar — harmless if the tap is always passthrough-safe with no live suggestion, but that assumption is implicit rather than enforced. The lack of a user override for the hard-coded suppression is a deliberate design choice that's documented in the source, but it silently takes away Calendar support for users who might want it.

CotabbyAppEnvironment.swift — the relationship between isCaptureSuppressedForBundle and shouldProcessEventsProvider deserves a closer look to confirm the event tap is passthrough-safe in Calendar.

Important Files Changed

Filename Overview
Cotabby/Support/AccessibilityCaptureSuppressionPolicy.swift New policy enum that hard-codes com.apple.iCal as a permanently suppressed bundle; no user override path exists.
Cotabby/App/Core/CotabbyAppEnvironment.swift Policy is wired into isCaptureSuppressedForBundle ahead of user-preference checks, but shouldProcessEventsProvider on the same inputMonitor does not consult the policy, leaving the event tap active in suppressed apps.
CotabbyTests/AccessibilityCaptureSuppressionPolicyTests.swift Covers the three expected paths (Calendar, ordinary app, nil bundle); straightforward and correct.
Cotabby.xcodeproj/project.pbxproj Adds both new Swift files to the correct build phases and groups; no issues.
.agents/skills/ship/SKILL.md New CI agent skill for squash-merging PRs with admin bypass; unrelated to the Calendar fix but ships as part of this PR.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[AX poll fires] --> B{isCaptureSuppressedForBundle?}
    B --> C[AccessibilityCaptureSuppressionPolicy\n.shouldSuppressCapture]
    C --> D{bundle in\nunsafeBundleIdentifiers?}
    D -->|yes — e.g. com.apple.iCal| E[return true\nSkip AX walk]
    D -->|no| F{isGloballyEnabled?}
    F -->|false| E
    F -->|true| G{isApplicationDisabled\nfor bundle?}
    G -->|true| E
    G -->|false| H[return false\nProceed with AX walk]

    I[Keystroke event] --> J{shouldProcessEventsProvider}
    J --> K{isGloballyEnabled?}
    K -->|false| L[drop event]
    K -->|true| M{isTerminal or\napp-disabled?}
    M -->|yes| L
    M -->|no| N[process event\n⚠️ policy NOT consulted]
    N --> O[suggestion pipeline\nno AX snapshot available]
Loading

Comments Outside Diff (1)

  1. Cotabby/App/Core/CotabbyAppEnvironment.swift, line 95-104 (link)

    P2 Suppression policy not mirrored in shouldProcessEventsProvider

    isCaptureSuppressedForBundle now returns true for Calendar, stopping the AX walk, but shouldProcessEventsProvider on inputMonitor never consults AccessibilityCaptureSuppressionPolicy. When Calendar is the frontmost app, Cotabby's event tap remains fully active: acceptance-key, word-acceptance, and global-toggle key presses are still evaluated. If any of those taps swallow the event before passing it through (even with no active suggestion), Calendar's own keyboard shortcuts could silently lose keystrokes. The fix intent—"leaving keyboard monitoring … untouched"—may be fine if the event tap is always passthrough-safe with no live suggestion, but that assumption isn't enforced here.

    Fix in Codex Fix in Claude Code

Fix All in Codex Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix calendar popover dismissal by suppre..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Comment on lines +17 to +19
private static let unsafeBundleIdentifiers: Set<String> = [
"com.apple.iCal"
]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hard-coded suppression has no user opt-out

unsafeBundleIdentifiers is a private constant, so there is no way for a user to re-enable AX capture for Calendar (e.g., when they are typing in a plain text area outside the event-editor popover). The policy fires before all user-preference checks in isCaptureSuppressedForBundle, meaning even an explicit per-app user preference cannot override it. The doc comment acknowledges this is "conservative," but unlike the isApplicationDisabled path, no corresponding UI toggle exists. Consider surfacing this as a per-app override in the accessibility settings pane, or at minimum document the intentional no-override behaviour in a follow-up issue so the decision is tracked.

Fix in Codex Fix in Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant