Adapts the mobile client's finalized, twice-reviewed JMAP delta-sync design
(vncmail-native's docs/DELTA-SYNC-ENGINE-DESIGN.md, revision 3) to Electron's
runtime rather than re-deriving JMAP sync theory. Every section is tagged
[reused] / [adapted] / [new] so a reader can tell which is which; the
protocol-level parts (three state machines, cursor provenance with branded
types, error taxonomy, pinned reconcile sweep floor, I1-I13, F1-F49) are
reused by citation, not restated.
Three decisions were genuinely open here and are resolved with evidence:
1. Process placement: the engine + SQLite live in the standalone Next.js
server process, on a worker thread. The per-account credentials are
already there in httpOnly AES-GCM cookies, so nothing secret crosses a
process boundary - and a Node process can put an Authorization header on
a WebSocket upgrade, which is exactly what makes RFC 8887 push
unreachable from the renderer today (lib/jmap/client.ts:6038-6059).
Hosting it in main.ts was rejected because it can only be built by
moving credentials into a process that currently holds none - the change
that same comment explicitly declined. A WASM/OPFS renderer engine was
rejected because it needs 'wasm-unsafe-eval' added to the product-wide
CSP in proxy.ts, and its only encrypted backends are small third-party
WASM builds.
2. SQLCipher ships on day one, via @signalapp/sqlcipher (N-API prebuilds,
verified loading in Electron 43.2.0 in both process modes with no
rebuild; real SQLCipher 4.10.0; encrypted header, wrong key rejected,
FTS5 present; AGPL-3.0-only like this repo). The mobile design's
plaintext-first phase existed only because Expo Go cannot load
SQLCipher, and that constraint has no Electron analogue. node:sqlite is
rejected (no encryption - PRAGMA key is a SILENT no-op that leaves the
mailbox in cleartext - and stability 1.2/RC in the Node 24 that Electron
43 bundles); better-sqlite3-multiple-ciphers is rejected (Electron
prebuilds stop at ABI 146, Electron 43 needs 148, so a C++ toolchain on
every machine, and that lag recurs at every Electron major).
3. Keys use Electron's built-in safeStorage, not keytar, with a mandatory
getSelectedStorageBackend() check: on Linux without a keyring,
isEncryptionAvailable() returns true while using a public hardcoded
password, which is worse than an honest failure.
Also records what this repo has that the mobile one doesn't (a real Stalwart
integration fixture, so the highest-value tests are cheap) and what it
lacks (no /changes wrappers, no offline cache, no outbox - so v1 desktop
offline is read-only by decision, and the mobile design's D1-D8 defects are
not inherited).
Everything not verifiable in this environment is flagged for a Stage A
verify-first gate rather than presented as fact - notably whether an
unsigned build keeps its macOS Keychain item across an electron-updater
upgrade, and whether Next's output file tracing carries the native
prebuilds into .next/standalone.
No source file is touched by this commit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>