macsync/docs/modules/inotes.md

4.3 KiB

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.swiftpublic 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):

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):

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.
  • 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.swiftNoteSender action routing against a fake NoteApplying (hermetic).

Not covered: real AppleScript execution, Notes.app side effects, Automation prompts.