Skip to content

Support setting flagged status (add/edit --flagged) #63

Open

Description

@udondan

Summary

Follow-up to #51. The spike there showed a reminder's flagged state can be read, and the CLI now ships read-only support: isFlagged in JSON, a (flagged) suffix in plain output, and a --flagged filter on show/show-all. Setting the flag isn't possible through EventKit. This issue tracks write support and records why it was left out.

Proposed CLI surface

  • add --flagged
  • edit --flagged / edit --clear-flagged, mutually exclusive and checked in validate(). Either one counts as a change for the "at least one change is required" rule.
  • If the chosen mechanism isn't available on the running macOS version, throw invalid_argument with a message saying flagging isn't supported there.

Why EventKit can't write the flag

The following was checked by runtime introspection on macOS 26.6.2 (25G83):

  1. EventKit has no public flag property. EKReminder, EKCalendarItem and EKObject have no flagged/isFlagged property, method or KVC key, and instancesRespond(to:) is false for both names. The public SDK (EventKit.framework headers, .swiftinterface, .tbd) doesn't mention "flagged" anywhere. setValue(_:forKey: "flagged") on an EKReminder would raise NSUndefinedKeyException, which Swift can't catch, so the process would crash.

  2. The flag lives in a private framework. Since macOS 10.15, Reminders data is stored by ReminderKit.framework (/System/Library/PrivateFrameworks), not by the Calendar database. EventKit's reminder objects are thin wrappers around it. The flag appears only on ReminderKit classes:

    • REMReminderStorage has flagged / setFlagged:
    • REMReminder has flaggedContext, which returns a REMReminderFlaggedContext with a flagged getter
    • REMReminderFlaggedContextChangeItem has flagged / setFlagged:
    • REMAccountCapabilities has supportsFlagged, so some account types don't support flags at all
  3. Reading works only by walking into those private objects. The read path in the CLI is EKReminder.backingObject (an EKFrozenReminderReminder) → _reminder (a REMReminder) → flaggedContext → flagged. Every step is an undocumented selector. The CLI guards each step with responds(to:) and treats any missing link as "not flagged". That fallback is acceptable for a read, but there is no safe equivalent for a write.

  4. EventKit's save path never carries the flag. The REMReminder behind an EKReminder is an immutable snapshot ("frozen" object). On EKEventStore.save(_:commit:), EventKit translates its own change set (EKChangeSet → EKObject.prepareReminderKitObjectForSaveWithUpdatedBackingObjectProvider: → EKReminderStore) into ReminderKit change items, and it only translates the properties EventKit models. Setting a value somewhere on the snapshot therefore wouldn't be persisted. It would be silently discarded, or overwritten on the next save.

  5. Writing through ReminderKit directly means calling private API. It would require building a REMSaveRequest, getting a REMReminderChangeItem for the reminder, setting flaggedContext / REMReminderFlaggedContextChangeItem.flagged, and committing through a REMStore. The problems with that:

    • No stability contract. Class names, selectors and the save flow can change in any macOS update, including minor ones, with no deprecation. A write that half-works is worse than a read that falls back to false, because it can lose or corrupt user data.
    • It isn't guarded the way the read is. Reading can fall back to false. A write has to either succeed or fail loudly, and verifying that it succeeded would itself rely on more private API.
    • Entitlements and TCC. remindd may check that clients talking to ReminderKit directly have private entitlements, which a notarized third-party binary can't have. Access granted through EventKit's requestFullAccessToReminders isn't guaranteed to cover the private XPC path.
    • Distribution. Linking or dlopen-ing a private framework conflicts with Spike: expose and filter by flagged status #51's "no private frameworks" constraint and is a liability for the Homebrew-distributed binary.

Most promising alternative: AppleScript / Apple Events

Reminders.app's scripting dictionary (sdef /System/Applications/Reminders.app) publicly documents the flag:

<property name="flagged" code="flgd" type="boolean" description="Whether the reminder is flagged">
    <cocoa key="isFlagged" />
</property>

It has no access="r", so it should be writable. This was verified read-only on macOS 26.6.2:

  • tell application "Reminders" to get {id, name, flagged} of reminders of list "Test" returned the correct flags.
  • The AppleScript id is x-apple-reminder://<UUID>, and the UUID equals EventKit's calendarItemIdentifier. For iCloud reminders it was also equal to calendarItemExternalIdentifier, but that may not hold for other account types.

The write was not tested. Open questions before choosing this route:

  • Does set flagged of reminder id "x-apple-reminder://…" to true persist and sync through iCloud?
  • Mapping: the CLI resolves reminders by calendarItemExternalIdentifier. Is the AppleScript id always derived from calendarItemIdentifier, including for non-iCloud accounts?
  • Permissions: this needs a separate Automation (Apple Events) TCC grant for the terminal or parent app, and possibly an NSAppleEventsUsageDescription. How should the CLI report a denied Automation prompt (a new CLIError code?)
  • It launches Reminders.app if it isn't running. Is that acceptable for a CLI?
  • How far back does the flagged scripting property go (which macOS versions)?
  • Should reads also move to AppleScript so read and write use the same public mechanism, or stay on the fast in-process path?

Other options

  • Wait for Apple to add isFlagged to EKReminder, and file a Feedback Assistant request for it.
  • Shortcuts: check whether the "Edit Reminder" action can set the flag and whether it can be driven from the CLI (shortcuts run). This would need a user-installed shortcut, so it's probably a poor fit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions