# Data flow Three diagrams trace bytes through the system: inbound (Mac to server), outbound (web to Mac via send queue), and bootstrap (cold start). ## Inbound: Mac to server How a new iMessage, photo, calendar event etc. reaches the database. iCal is shown; every other module follows the same shape. ```mermaid sequenceDiagram autonumber participant Timer as Timer (interval s) participant SM as ICalSync.SyncManager participant Reader as CalendarReader participant API as ICalSync.APIClient participant Hono as Hono /client/ical/* participant PG as PostgreSQL icloud Timer->>SM: fire (BaseSyncManager.syncNow) SM->>SM: gatedRunCycle -> isAuthorized? SM->>Reader: fetchCalendars(), fetchEvents(since: lastSync) Reader-->>SM: [SyncCalendarPayload], [SyncEventPayload] SM->>API: syncCalendars(payloads) API->>Hono: POST /client/ical/calendars (device token) Hono->>PG: INSERT ... ON CONFLICT UPDATE PG-->>Hono: row count Hono-->>API: { synced: N } SM->>API: syncEvents(payloads) API->>Hono: POST /client/ical/events Hono->>PG: upsert + tsvector update via trigger Hono-->>API: { synced: N } SM->>SM: setLastSync(now), lastSyncCompletedAt = now ``` Sources: - `BaseSyncManager.runCycle` updates the watermark at the end of every cycle: `@packages/shared/Sources/MacSyncShared/Sync/BaseSyncManager.swift:123-131`. - Calendar reader is invoked from `@packages/ical/Sources/ICalSync/SyncManager.swift:116-` (the `performSync()` override; see Module Reader for the Reader contract). - API methods: `@packages/ical/Sources/ICalSync/APIClient.swift:106-130`. - Server route registration: `src/server/src/surfaces/client/ical.ts:59-69`. Other modules' inbound endpoints: - `POST /client/imessage/sync` (`surfaces/client/imessage.ts:71`) - `POST /client/imessage/contacts` (`surfaces/client/imessage.ts:77`) - `POST /client/iphoto/sync` (`surfaces/client/iphoto.ts:63`) - `POST /client/iphoto/albums` (`surfaces/client/iphoto.ts:69`) - `POST /client/iphoto/upload/:localIdentifier` (`surfaces/client/iphoto.ts:75`) — binary upload - `POST /client/imail/sync` (`surfaces/client/imail.ts:40`) - `POST /client/ireminders/sync` (`surfaces/client/ireminders.ts:32`) - `POST /client/inotes/sync` (`surfaces/client/inotes.ts:29`) ## Outbound: web to Mac via SendQueueClient How a user-initiated write in the React SPA reaches Calendar.app / Reminders.app / Notes.app. iCal shown; reminders and notes are structurally identical. ```mermaid sequenceDiagram autonumber participant SPA as React SPA participant My as Hono /my/calendar/* participant Send as features/ical/sendService participant Admin as Hono /admin/calendar-send-queue participant Repo as createSendQueueRepo
(icloud.calendar_send_queue) participant PG as PostgreSQL participant Client as Hono /client/ical/send-queue participant SQC as Mac SendQueueClient participant Sender as CalendarSender (EventKit) participant EK as EKEventStore SPA->>My: POST /my/calendar/events { ... } My->>Send: enqueueEvent(action, payload, deviceId) Send->>Repo: enqueue(...) Repo->>PG: INSERT INTO icloud.calendar_send_queue (status='queued') PG-->>Repo: { id } Repo-->>SPA: 200 { id } Note over SQC: Timer fires every 60s SQC->>Client: GET /client/ical/send-queue/pending (device token) Client->>Repo: listPending(deviceId) Repo->>PG: SELECT ... WHERE status='queued' ORDER BY created_at Repo-->>Client: rows Client-->>SQC: [PendingItem] loop each item SQC->>Sender: apply(item) Sender->>EK: save(_:span:) / remove(_:span:) EK-->>Sender: result Sender-->>SQC: .sent | .failed(reason) SQC->>Client: POST /client/ical/send-queue/:id/result Client->>Repo: markResult({ id, status, error? }) Repo->>PG: UPDATE status='sent'|'failed', sent_at=now() end ``` Sources: - `/my/calendar/events` write routes: `src/server/src/surfaces/my/calendar.ts:80, 90, 102`. - `sendService.enqueueEvent` and the repo wiring: `src/server/src/features/ical/sendService.ts:16-19` (factory call). - `SendQueueRepo` SQL: `src/server/src/shared/sendQueue/SendQueueRepo.ts:69-127`. - Mac-side polling: `SendQueueClient.drainOnce()` (`@packages/shared/Sources/MacSyncShared/Sync/SendQueueClient.swift:93-125`). - Calendar transport adapter: `@packages/ical/Sources/ICalSync/SyncManager.swift:56-65`. - Sender: `@packages/ical/Sources/ICalSync/Sender.swift` (`CalendarSender.apply`). The iMessage outbound flow is the same loop but talks to the legacy `icloud.send_queue` table via `IMessageSendTransport` (`@packages/imessage/Sources/IMessageSync/SendQueueAdapter.swift`) and applies via AppleScript instead of EventKit (`@packages/imessage/Sources/IMessageSync/Sender.swift`). ## Bootstrap: cold start What happens when `MacSyncApp` launches for the first time on a new Mac. ```mermaid sequenceDiagram autonumber participant App as MacSyncApp participant Cfg as ConfigFile (~/.config/com.lilith.mac-sync/config.json) participant Dev as DeviceRegistration
(MacSyncShared) participant Hono as Hono /client/devices/* participant PG as icloud.devices participant Mgrs as SyncManager.shared (x6) participant LWS as LocalWebServer :8765 participant Browser as Browser App->>Cfg: load() — read serverURL, deviceName, etc. Cfg-->>App: config App->>Dev: register(deviceName, model, osVersion) Dev->>Hono: POST /client/devices/register (no auth) Hono->>PG: INSERT device, return token Hono-->>Dev: { deviceId, token } Dev->>Dev: persist token to Keychain loop poll until ready App->>Dev: status check Dev->>Hono: GET /client/devices/:id/status (no auth) Hono-->>Dev: { ready } end App->>Mgrs: startSync() on each of the six Mgrs->>Mgrs: gatedRunCycle -> isAuthorized? -> performSync Mgrs->>Mgrs: setLastSync(now); persist to UserDefaults Mgrs->>Mgrs: didStartSync -> sendQueueClient.start() (iMessage/iCal/iRem/iNotes) App->>LWS: start() on port 8765 Browser->>LWS: GET / (SPA) Browser->>LWS: GET /api/settings LWS-->>Browser: { serverURL, deviceName, ... } ``` Sources: - `LocalWebServer` route registration: `@packages/shared/Sources/MacSyncShared/WebServer/LocalWebServer.swift:42-110`. Settings endpoints live on lines 43-57; static SPA fallback on 94-109. - Device registration is exempt from the device-token middleware: `src/server/src/app/server.ts:69-75`. - The base manager loads watermarks in `init` from `UserDefaults`: `@packages/shared/Sources/MacSyncShared/Sync/BaseSyncManager.swift:67-69`. - `didStartSync()` is the hook each bidirectional manager overrides to start its `SendQueueClient`. See iMessage (`@packages/imessage/Sources/IMessageSync/SyncManager.swift:170-172`), iCal (`@packages/ical/Sources/ICalSync/SyncManager.swift:67-69`), iReminders, and iNotes (`@packages/inotes/Sources/INoteSync/SyncManager.swift:66-72`). - The web SPA is served from the Mac binary's `webapp/` resource directory in production builds, or `/web/dist/` in dev (`LocalWebServer.swift:137-153`). ## Watermark, time, and idempotency notes - `lastSync` is the **start time of the previous cycle**, not the timestamp of the latest record. Modules apply tolerance (iMail uses ±1 min, see `@packages/imail/Sources/IMailSync/SyncManager.swift:42-43`) to catch late arrivals. - Inbound upserts use module-specific external IDs (iMessage `guid`, iMail `Message-ID`, iCal `eventIdentifier`, iNotes `noteIdentifier`) so retrying a cycle is safe. - Outbound send-queue items are idempotent on the server side (`status='queued'` -> `'sent'|'failed'` is a one-shot transition); the Mac client is responsible for not double-applying inside a single drain. The `isProcessing` flag in `SendQueueClient` (`@packages/shared/Sources/MacSyncShared/Sync/SendQueueClient.swift:54, 93-96`) guarantees only one drain runs at a time.