4.3 KiB
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.swift—public final class NotesReader(line 49):requestAuthorization()(line 59),fetchAllNotes()(line 70). The fetch script readsid, name, body, folder, modification datefrom 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:NoteApplyingprotocol (line 65) + productionAppleScriptNoteApplier(line 110) and aNoteSenderfacade wrapper (line 76).- Scripts assembled by
scriptForCreate,scriptForUpdate,scriptForDelete(lines 149, 163, 181) usingAppleScriptEscape.quotefor all interpolation. NoteSendTransportadapts the typed APIClient calls intoSendQueueTransport(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— exercisesReader.parse(_:)against canned AppleScript output (@packages/inotes/Sources/INoteSync/Reader.swift:104), and validates the script strings emitted byscriptForCreate/scriptForUpdate/scriptForDelete.SenderDispatchTests.swift—NoteSenderaction routing against a fakeNoteApplying(hermetic).
Not covered: real AppleScript execution, Notes.app side effects, Automation prompts.