macsync/docs/modules/inotes.md

124 lines
4.3 KiB
Markdown
Raw Permalink Normal View History

# iNotes (`INoteSync`)
## Purpose
Sync Notes.app notes with the server, and apply web-initiated note edits back
via AppleScript.
## Direction
Bidirectional via AppleScript — accepted fragility per
`~/.claude/plans/magical-tumbling-peach.md:108-117`.
## OS surface
Notes.app via AppleScript (`NSAppleScript`). No linker entry needed; the
target has no `linkerSettings` (`Package.swift:90-94`). Requires
Automation > Notes.
## Files
- `Reader.swift``public final class NotesReader` (line 49):
`requestAuthorization()` (line 59), `fetchAllNotes()` (line 70). The fetch
script reads `id, name, body, folder, modification date` from every note in
every account
(`@packages/inotes/Sources/INoteSync/Reader.swift:78-95`).
- `APIClient.swift`:
- `syncNotes(_:)` -> `POST /client/inotes/sync`
(`@packages/inotes/Sources/INoteSync/APIClient.swift:79`)
- `getStats()` -> `GET /client/inotes/stats` (line 94)
- `getPendingSends()` -> `GET /client/inotes/send-queue/pending` (line 109)
- `reportSendResult(...)` -> `POST /client/inotes/send-queue/:id/result` (line 132)
- `Sender.swift`:
- `NoteApplying` protocol (line 65) + production
`AppleScriptNoteApplier` (line 110) and a `NoteSender`
facade wrapper (line 76).
- Scripts assembled by `scriptForCreate`, `scriptForUpdate`,
`scriptForDelete` (lines 149, 163, 181) using
`AppleScriptEscape.quote` for all interpolation.
- `NoteSendTransport` adapts the typed APIClient calls into
`SendQueueTransport` (line 231).
- `SyncManager.swift` — slow interval (600s) because AppleScript is
expensive; `lazy var sendQueueClient: SendQueueClient<NoteSendTransport>`
drains every 60s (`SyncManager.swift:54-72`). Batch size 100 (line 51).
## Timing
- Read interval: **600s**
(`@packages/inotes/Sources/INoteSync/SyncManager.swift:79`).
- Outbound poll interval: **60s**
(`@packages/inotes/Sources/INoteSync/SyncManager.swift:60`).
- Note batch size: 100.
## Server surface
- Entity tables: `icloud.notes`, `icloud.note_send_queue`
(`src/server/src/app/server.ts:47-48`).
- Allowed actions: `create_note`, `update_note`, `delete_note`
(`src/server/src/entities/noteSendItem/types.ts:12`).
- Client routes (`src/server/src/surfaces/client/inotes.ts`):
- `POST /client/inotes/sync` (line 29)
- `GET /client/inotes/stats` (line 35)
- `GET /client/inotes/send-queue/pending` (line 39)
- `POST /client/inotes/send-queue/:id/result` (line 57)
- Web routes (`src/server/src/surfaces/my/notes.ts`):
- `GET /my/notes/stats` (line 41)
- `GET /my/notes/` (line 49)
- `POST /my/notes/` (line 66)
- `PUT /my/notes/:id` (line 76)
- `DELETE /my/notes/:id` (line 88)
- Admin enqueue: `POST /admin/note-send-queue/enqueue`
(`src/server/src/surfaces/admin/note-send-queue.ts:13`).
## Web surface
- Tab: `/notes` (`web/src/App.tsx:62`).
- API helpers: `web/src/api/notes.ts`.
## AppleScript snippets
Create (`@packages/inotes/Sources/INoteSync/Sender.swift:149-160`):
```applescript
tell application "Notes"
set targetFolder to first folder whose name is "<folder>"
set newNote to make new note at targetFolder with properties ¬
{name:"<name>", body:"<html body>"}
return id of newNote
end tell
```
Delete (`Sender.swift:181-189`):
```applescript
tell application "Notes"
delete (every note whose id is "<id>")
end tell
```
All interpolated values pass through
`AppleScriptEscape.quote(_)`
(`@packages/shared/Sources/MacSyncShared/Util/AppleScriptEscape.swift:13`).
## Known limitations
- Attachments and Apple Pencil drawings don't round-trip — title, body,
folder, and modification date only. See
[known-limitations](../known-limitations.md#applescript-fragility).
- Formatting fidelity is partial; basic HTML survives, advanced styles can
flatten.
- Pinned / locked notes: the AppleScript surface does not expose locking
state; locked notes may be unreadable to the reader.
## Tests (`@packages/inotes/Tests/INoteSyncTests/`)
- `AppleScriptTests.swift` — exercises `Reader.parse(_:)` against canned
AppleScript output (`@packages/inotes/Sources/INoteSync/Reader.swift:104`),
and validates the script strings emitted by `scriptForCreate` /
`scriptForUpdate` / `scriptForDelete`.
- `SenderDispatchTests.swift``NoteSender` action routing against a fake
`NoteApplying` (hermetic).
Not covered: real AppleScript execution, Notes.app side effects, Automation
prompts.