Skip to content

fix(app): write Apple Reminders to default list instead of creating one per task#7287

Open
waffensam wants to merge 8 commits into
BasedHardware:mainfrom
waffensam:fix/apple-reminders-default-list
Open

fix(app): write Apple Reminders to default list instead of creating one per task#7287
waffensam wants to merge 8 commits into
BasedHardware:mainfrom
waffensam:fix/apple-reminders-default-list

Conversation

@waffensam
Copy link
Copy Markdown
Contributor

@waffensam waffensam commented May 13, 2026

Summary

On non-English iOS systems, exporting tasks to Apple Reminders created a brand-new "Reminders" list for every task instead of writing into the user's existing list. Result: a "提醒事项" sidebar full of duplicate "Reminders" lists with one task each.

Root cause is in the single-reminder add path. AppleRemindersService.addReminder() looked up the target calendar by exact title ("Reminders"), and on a miss fell into a "create a new list with that title" branch. The system list is localized (e.g. "提醒事项" on zh, "Erinnerungen" on de), so the lookup never matched and a fresh EKCalendar was saved for every call.

The FCM batch path (syncBatchFromJSON) already does the right thing — it writes directly to defaultCalendarForNewReminders(). This change brings the single-add path in line with that.

Fix

  • iOS bridge (AppleRemindersService.swift): treat listName as opt-in. If the caller passes a name AND a calendar with that exact title exists, use it; otherwise fall back to defaultCalendarForNewReminders(). Never auto-create a new list.
  • Dart: drop the hardcoded listName: 'Reminders' from all four call sites (apple_reminders_service.dart, apple_reminders_sync_service.dart, action_items_provider.dart, action_item_tile_widget.dart) and only forward the key to the platform channel when explicitly set.

There is no UI today for selecting a target list, so dropping the hardcoded value is safe — no existing user configuration depends on it.

Verification

  • Code review only — no on-device run yet. Needs a quick smoke on a Chinese-locale device to confirm new reminders land in the system "提醒事项" list and no extra lists are created. English-locale behavior is unchanged (default list is titled "Reminders", which the existing match still finds).

🤖 Generated with Claude Code

waffensam added 5 commits May 13, 2026 19:16
Title matching ("Reminders") fails on non-English systems
(e.g. "提醒事项"), so the lookup branch never hit and every
addReminder call created a brand-new list. Fall back to
defaultCalendarForNewReminders() when no exact match exists,
matching the behavior of the FCM batch path.
Previously the wrapper always sent listName: 'Reminders' to the
iOS side, forcing the title-match path. Send the key only when
the caller passes one so iOS can use the user's default list.
Falls through to defaultCalendarForNewReminders() on iOS instead
of asking for a 'Reminders'-titled list that may not exist.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 13, 2026

Greptile Summary

This PR fixes a long-standing localization bug where exporting tasks to Apple Reminders on non-English iOS devices created a new duplicate list for every task, because addReminder looked up the target calendar by the hardcoded English name "Reminders" and fell into a "create new list" branch on a miss.

  • iOS (AppleRemindersService.swift): addReminder no longer auto-creates a calendar. If listName is supplied and matches an existing calendar it is used; otherwise the method falls back to defaultCalendarForNewReminders(), consistent with the existing FCM batch path.
  • Dart (4 call sites): Removes the hardcoded listName: 'Reminders' argument everywhere addReminder is called, and the service layer now only forwards the listName key to the platform channel when it is explicitly set.

Confidence Score: 3/5

The addReminder path is correctly fixed, but getExistingReminders and completeReminder retain the same title-match-against-"Reminders" pattern in both Dart and Swift, leaving those operations silently broken on non-English devices.

The PR fixes the most visible symptom (duplicate list creation on every addReminder call) correctly and consistently across all four Dart call sites. However, two sibling methods — getExistingReminders and completeReminder — still pass listName ?? 'Reminders' to Swift handlers that look up a calendar by that exact English title. On a non-English device getExistingReminders always returns an empty list (so duplicate-detection via reminderExists is always wrong) and completeReminder silently fails.

apple_reminders_service.dart lines 184 and 206, and AppleRemindersService.swift getReminders/completeReminder handlers (lines 345 and 379) — all still carry the hardcoded "Reminders" fallback that breaks on non-English systems.

Important Files Changed

Filename Overview
app/ios/Runner/AppleRemindersService.swift Fixes addReminder to use defaultCalendarForNewReminders() instead of creating new calendars; however getReminders and completeReminder still fall back to the hardcoded "Reminders" title, leaving them broken on non-English systems.
app/lib/services/apple_reminders_service.dart Correctly removes hardcoded listName: 'Reminders' from addReminder callers, but getExistingReminders (line 184) and completeReminder (line 206) still pass listName ?? 'Reminders', preserving the same localization bug for those methods.
app/lib/pages/action_items/widgets/action_item_tile_widget.dart Removes the hardcoded listName: 'Reminders' call-site argument — straightforward and correct.
app/lib/providers/action_items_provider.dart Removes the hardcoded listName: 'Reminders' call-site argument — straightforward and correct.
app/lib/services/apple_reminders_sync_service.dart Removes the hardcoded listName: 'Reminders' from the sync-service call site — straightforward and correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Dart: addReminder(title, notes, dueDate, listName?)"] --> B{listName != null?}
    B -- Yes --> C["Include 'listName' in platform channel args"]
    B -- No --> D["Omit 'listName' from args"]
    C --> E["Swift: addReminder()"]
    D --> E
    E --> F{listName arg present and non-empty?}
    F -- Yes --> G["Search calendars by title"]
    G --> H{Match found?}
    H -- Yes --> I["Use matched calendar"]
    H -- No --> J["defaultCalendarForNewReminders()"]
    F -- No --> J
    J --> K{nil?}
    I --> L["Save EKReminder → return calendarItemIdentifier"]
    K -- No --> L
    K -- Yes --> M["FlutterError: NO_CALENDAR"]
Loading

Comments Outside Diff (2)

  1. app/lib/services/apple_reminders_service.dart, line 184 (link)

    P1 Same localization bug left in getExistingReminders and completeReminder

    getExistingReminders still passes listName ?? 'Reminders' to the platform channel (line 184), and completeReminder does the same at line 206. On a non-English device the Swift getReminders/completeReminder handlers look up a calendar by exact title "Reminders", find nothing, and return [] / false. In practice this means reminderExists always returns false (could cause duplicate-creation logic to misbehave) and completing a reminder silently fails for non-English users — the exact same class of bug that motivated this PR.

  2. app/ios/Runner/AppleRemindersService.swift, line 345-353 (link)

    P1 getReminders still uses a hardcoded "Reminders" title match

    getReminders (and completeReminder at line 379) still fall back to "Reminders" when no listName is passed, then look up the calendar by that exact title. On non-English systems this lookup will always fail, so getReminders returns [] and completeReminder returns false. The same pattern was already fixed for addReminder in this PR; these two handlers need the same treatment — fall back to defaultCalendarForNewReminders() instead of a title match when no listName is supplied.

Reviews (1): Last reviewed commit: "app: drop hardcoded listName from action..." | Re-trigger Greptile

@waffensam waffensam changed the title ios: stop creating duplicate Reminders lists for each task fix(app): write Apple Reminders to default list instead of creating one per task May 13, 2026
@waffensam
Copy link
Copy Markdown
Contributor Author

Thanks @greptile-apps — good catch on the sibling methods. getReminders and completeReminder had no Dart callers today (reminderExists/getExistingReminders/completeReminder are unused in the codebase), so they weren't actively broken — but leaving the same fragile pattern in place would just queue up the bug for whoever wires them up next.

Pushed two follow-up commits applying the same defaultCalendarForNewReminders() fallback to both handlers, on Swift and Dart sides:

  • c154343 ios: apply same default-calendar fallback to getReminders/completeReminder
  • 263ccb4 app: stop forwarding hardcoded 'Reminders' to getReminders/completeReminder

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 13, 2026

Both commits look good. The pattern is applied consistently across all three handlers now:

Swift (c154343): getReminders and completeReminder both use the same two-step lookup — named list if provided and found, defaultCalendarForNewReminders() otherwise. Matches addReminder exactly.

Dart (263ccb4): getExistingReminders and completeReminder both use the same if (listName != null) args['listName'] = listName guard, so listName is only forwarded when explicitly set by the caller. No hardcoded 'Reminders' string remains anywhere.

The whole file is internally consistent now. The original bug and the sibling latent issues are all resolved. LGTM.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

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