# iReminders (`IReminderSync`)

## Purpose

Sync Reminders.app lists and items with the server, and apply web-initiated
edits back via EventKit.

## Direction

Bidirectional. Inbound: EventKit -> server. Outbound:
`icloud.reminder_send_queue` -> `EKEventStore.save(EKReminder, commit:)` /
`.remove(_, commit:)`.

## OS surface

EventKit (`EKEntityType.reminder`). `Package.swift:84-86` links EventKit.
Reader uses `EKEventStore.fetchReminders(matching:completion:)`
(`@packages/ireminders/Sources/IReminderSync/Reader.swift:127`).

## Files

- `Reader.swift` — `class ReminderReader` (line 66) with
  `requestAuthorization()` (line 80), `fetchCalendars()` (line 110),
  `fetchReminders(since:)` (line 124).
- `APIClient.swift`:
  - `syncReminders(_:)` -> `POST /client/ireminders/sync`
    (`@packages/ireminders/Sources/IReminderSync/APIClient.swift:115`)
  - `getStats()` -> `GET /client/ireminders/stats` (line 128)
  - `getPendingSends()` -> `GET /client/ireminders/send-queue/pending` (line 142)
  - `reportSendResult(...)` -> `POST /client/ireminders/send-queue/:id/result` (line 168)
- `Sender.swift` — reminder applier wired into a `SendQueueClient` at
  `@packages/ireminders/Sources/IReminderSync/SyncManager.swift:57`.
- `SyncManager.swift` — read interval 300s (line 67), send interval 60s
  (line 57); persistence key `"ireminders"` (line 66).

## Timing

- Read interval: **300s** (`SyncManager.swift:67`).
- Outbound poll interval: **60s** (`SyncManager.swift:57`).

## Server surface

- Entity tables: `icloud.reminders`, `icloud.reminder_send_queue`
  (`src/server/src/app/server.ts:45-46`).
- Allowed actions: `create_reminder`, `update_reminder`, `delete_reminder`
  (`src/server/src/entities/reminderSendItem/types.ts:16-18`).
- Client routes (`src/server/src/surfaces/client/ireminders.ts`):
  - `POST /client/ireminders/sync` (line 32)
  - `GET /client/ireminders/stats` (line 38)
  - `GET /client/ireminders/send-queue/pending` (line 42)
  - `POST /client/ireminders/send-queue/:id/result` (line 60)
- Web routes (`src/server/src/surfaces/my/reminders.ts`):
  - `GET /my/reminders/stats` (line 45)
  - `GET /my/reminders/` (line 53)
  - `POST /my/reminders/` (line 73)
  - `PUT /my/reminders/:id` (line 87)
  - `DELETE /my/reminders/:id` (line 103)
- Admin enqueue: `POST /admin/reminder-send-queue/enqueue`
  (`src/server/src/surfaces/admin/reminder-send-queue.ts:16`).

## Web surface

- Tab: `/reminders` (`web/src/App.tsx:61`).
- API helpers: `web/src/api/reminders.ts`.

## EventKit snippets

```swift
let predicate = store.predicateForReminders(in: nil)
store.fetchReminders(matching: predicate) { reminders in /* ... */ }
// outbound:
try store.save(reminder, commit: true)
try store.remove(reminder, commit: true)
```

(Source: `@packages/ireminders/Sources/IReminderSync/Reader.swift:124-145`.)

## Known limitations

- Last-write-wins, same as iCal.
- Subtasks: EventKit exposes hierarchical reminders via `parent`/`children`
  but the schema flattens; check `entities/reminder/` if you need recursion.

## Tests (`@packages/ireminders/Tests/IReminderSyncTests/`)

- `ReaderTests.swift` — Reader helpers that don't need EventKit data.
- `SenderTests.swift` — applier dispatch (hermetic).

Not covered: actual `fetchReminders` / `save` / `remove` (needs the OS),
authorization prompts.
