macsync/docs/known-limitations.md

131 lines
6.4 KiB
Markdown
Raw Permalink Normal View History

# Known limitations
Honest list of where `@mac-sync` is fragile or incomplete. Verified against
the code as of the current tree.
## iMessage `attributedBody` decoder is heuristic, not a full typedstream parser
`Reader.swift:371` (`extractTextFromAttributedBody`) is now wired into the row
extraction loop: when chat.db's `text` column is null/empty AND
`attributedBody` is non-nil, the byte-scanning heuristic recovers plain text
from the NSKeyedArchiver typedstream and uses it as the message body. This
covers most URL bubbles, reactions, and expressive sends.
What it does NOT do:
- Decode rich formatting (bold/italic/colors). Only the raw text run is
extracted.
- Handle multi-run NSAttributedStrings — only the first contiguous text run
with an `NSString` marker is returned. Multi-paragraph rich messages may
lose later paragraphs.
- Decode binary attachments embedded in the blob.
The full typedstream decoder on the server side is a stub at
`src/server/src/shared/typedstream/decode.ts` that returns `null`. When
implemented, it will populate `icloud.messages.body_decoded` for messages
where the Mac-side heuristic failed or where historical data needs a backfill
pass. The schema (`attributed_body BYTEA` + `body_decoded TEXT` columns +
combined FTS tsvector) is already in place.
## iPhoto has no Sender
By design (`~/.claude/plans/magical-tumbling-peach.md:184`): there is no
2-way Photos sync. iPhoto is Mac-to-server only. The dashboard cannot
upload photos to the Mac library. Photos.framework's write API is awkward and
the user explicitly scoped this out.
## AppleScript fragility
Three modules talk to macOS apps via AppleScript: iMessage (Messages.app),
iMail (Mail.app via ScriptingBridge), and iNotes (Notes.app). Known failure
modes:
- **iNotes attachments do not round-trip.** AppleScript's note `body` is HTML,
but inline images and Apple Pencil drawings are stored out-of-band. The
Notes Sender writes plain HTML only. Existing attachments stay; new ones
cannot be created from the web.
- **Notes formatting fidelity is lossy.** Bulleted lists, code blocks, and
checkboxes survive a round-trip; pinned/locked state, tags, and per-paragraph
styles may not.
- **Permission prompts.** First run of each AppleScript module triggers
System Settings > Privacy > Automation. If the user denies, the module's
`onAuthorizationDenied()` hook sets the per-module error (e.g.
`noteAccessRequired`, `automationDenied`) and the cycle bails out silently
(`@packages/inotes/Sources/INoteSync/SyncManager.swift:91-94`).
- **iMessage AppleScript can hit Messages.app stalls.** The `SendService`
layer (`@packages/imessage/Sources/IMessageSync/Sender.swift`) wraps the
AppleScript call with rate limits and delivery tracking but cannot recover
from a hung `Messages` process — manual restart is required.
## Watermark is cycle-start time, not record time
`BaseSyncManager.runCycle` sets `lastSync = Date()` at the end of a successful
cycle (`@packages/shared/Sources/MacSyncShared/Sync/BaseSyncManager.swift:126-127`).
If the OS produces records faster than the cycle runs, edge-of-window records
can be missed. iMail's Reader compensates with a ±1-minute tolerance
(`@packages/imail/Sources/IMailSync/SyncManager.swift:40-43`); other modules
do not. In practice this only matters for high-frequency streams, which only
iMessage really has (30s tick mitigates it).
## Conflict resolution: last-write-wins
Per the plan
(`~/.claude/plans/magical-tumbling-peach.md:182`):
> iCloud-side conflict resolution for simultaneous Mac+web edits — last-write-wins
> via `modifiedAt`. Document in code, not a feature.
If you edit the same event from Calendar.app and from the web within the same
cycle, whichever write completes second wins. There is no operational
transformation, no CRDT, no merge UI.
## iMessage send queue is on a bespoke schema
The legacy `icloud.send_queue` table predates the generic `createSendQueueRepo`
factory (`src/server/src/entities/send-queue/schema.ts:5-23`). It has extra
columns (`send_queue_id`, custom status enum) that the factory does not model.
This is intentional — see plan line 12 and architecture invariant 2 — but it
means tooling (admin dashboards, retry scripts) must special-case the iMessage
queue.
## `IPhoto.Stats.uploadRate` is per-session only
`IPhotoSyncStats.uploadStartTime` resets on every cold start
(`@packages/iphoto/Sources/IPhotoSync/SyncManager.swift:14-46`). The dashboard
shows live throughput during a session but cannot report historical upload
speeds.
## iMail `/my/mail` exposes no folder browser
Only `/conversations`, `/conversations/:id/messages`, and `/last-sync` exist
(`surfaces/my/mail.ts:10-25`). The web tab cannot drill into folders by name.
## Server has no auth bootstrap for fresh devices
`POST /client/devices/register` is exempt from `deviceTokenAuth`
(`src/server/src/app/server.ts:69-71`). Anyone on the LAN who can reach
`:3201` can register a device. The SSO middleware on `/my/*` protects the web
side, but `/client/*` relies on network isolation. If you expose `:3201` to
the public internet, register-spam is a real risk.
## iCalls limitations (Mac CallHistory only)
- **WAL freshness**: recent calls live in the `-wal` sidecar and may not be
visible to a plain readonly connection to `CallHistory.storedata`. The
`CallHistoryReader` uses a per-cycle snapshot (`makeSnapshot` copies
`.storedata` + `-wal` + `-shm` to a temp dir and opens read-write so SQLite
replays frames). See `Reader.swift` and the WAL integration test in
`ICallsSyncTests`.
- **iPhone cellular calls missing**: the Mac DB only ever sees FaceTime +
Continuity-relayed calls (`ZSERVICE_PROVIDER` = `com.apple.FaceTime` or
Telephony relay). Plain cellular calls that ring only on the iPhone do not
appear. No Mac-only fix exists; an iOS-side reader or CallKit event stream
would be required. Documented in calls-for-triage handoff.
- **0-duration semantics**: `durationSeconds == 0` + unanswered is common for
short rings, missed FaceTime requests, or connection attempts. Real
conversations have positive duration.
- **Local `/api/calls`**: the Mac app's `LocalWebServer` exposes
`http://localhost:8765/api/calls` (and `callHistoryDb` in diagnostics) for
direct agent/tool access. This is in addition to the server `/my/calls`.
- **MCP / triage**: `recent_calls` tool (in mcp/) and quinn-api consumers
should use the server surface (`/my/calls?since=...`), not poke the local DB.