Documentation

How sync works

The envelope, the keyring, conflict handling, and what the server can see.

⚠️ Sections 2C and 3 are superseded by ACCOUNTS-AND-SYNC.md. They refused accounts and hosted sync on a reading of the product that was wrong: accounts and a paid sync service are the intended shape of Slate. The rest of this document — §0 in particular — still stands, and §0 is now a prerequisite of the hosted service rather than an alternative to it.

What remains useful here: the external-change bug (§0), what does and does not need syncing (§1), and folder sync and git as the free options that still make sense for users who never sign in (§2A, §2B).

Status: Draft v1 · Date: 2026-08-26


0. The bug that comes first — ✅ fixed (2026-08-27)

Slate now compares mtime on window focus and tab visibility, and again inside flush() before any write. A clean buffer reloads; a dirty one stops and asks, with "keep both" as the default. See vault/vaultStore.ts and vault/external.test.ts.

Slate cannot see a file change it did not make. There is no watcher, no poll, no re-read on window focus. VaultFile.mtime is captured at read time (vault/types.ts:8) and never compared to anything again.

So today, on a vault in iCloud or Dropbox:

  1. You edit notes/plan.md on your laptop. It syncs.
  2. Your desktop has Slate open on the same note, showing the old text.
  3. You type one character there. Autosave writes the whole buffer.
  4. The laptop's edit is gone. Nothing reported it. There is no conflict file, because from Slate's side there was no conflict — it never looked.

Every sync strategy below rests on this, including the one that needs no code at all. Folder sync is not a strategy until Slate reloads a note that changed underneath it, and right now it doesn't.

The fix is small and it is the highest-value thing in this document:

Piece Cost
Compare mtime on window focus and on tab visibility; reload untouched notes ~60 lines
If the open note changed and the buffer is dirty, stop and ask — never merge silently ~50 lines
Rescan the file list on the same trigger, so notes added elsewhere appear ~30 lines
Tauri: real FS watching, so this happens live instead of on focus ~80 lines, M5

Steps 1–3 work in the browser build today and make "put your vault in iCloud" a true sentence. Step 4 makes it feel instant instead of polite.


1. What actually needs to sync

Three different things, and conflating them is how note apps grow accounts.

Where it lives now Should it sync?
Notes .md files in your folder Yes — and whatever syncs the folder already does it
Settings — theme, daily-note folder, plugin config slate.md and .slate/plugins/*.md, frontmatter in notes Yes, and it already does
Machine state — pane layout, float rects, frecency, which plugins are on localStorage No. It is derived, it is per-machine, and it is fine to lose
Secrets — a plugin's API key Session memory in the browser; keychain on desktop Never

The second row is worth pausing on. PLATFORM.md decision #3 put plugin settings in frontmatter instead of a settings screen, for reasons that had nothing to do with sync — it was about refusing to build a settings UI. The side effect is that settings are notes, so they sync with your notes for free, on whatever carries the folder. That is the whole feature, already shipped, with no code written for it. It is worth noticing when a decision pays twice.

The third row is why "sync everything" is wrong. If pane layout synced, opening your laptop would rearrange your desktop's windows. Machine state is supposed to be local.


2. The four options

A. Folder sync — the default, and it should stay the default

iCloud Drive, Dropbox, OneDrive, Syncthing, a network share. Zero code in Slate beyond §0. Works on every desktop. The vault is a folder of text files; this is what folders of text files are good at.

Limits, stated plainly. Two machines editing the same note at the same minute will produce a conflicted copy — Dropbox names it, iCloud names it, and you resolve it by reading two files. For a single person moving between two machines, that is rare and survivable. It is also exactly what Obsidian users already live with, and most of them never hit it.

Recommendation: document this as the answer, and fix §0 so it is true.

B. Git — the answer for the target user

FEATURES.md already promises a git plugin instead of built-in sync, and it is the right call for someone who "lives in a terminal or an editor" (PRD §3). Versioned, conflict-visible, free, hostable anywhere, and it makes "I deleted something three weeks ago" a solvable problem rather than a sad one.

It is now also the first serious test of whether the plugin API is real. A git sync plugin needs two things core does not yet broker:

  • network:<host> — the permission vocabulary has it; the broker does not implement it (~90 lines, and the statusline egress indicator PLATFORM.md asked for).
  • Some way to run git. On desktop that is a Tauri command; in the browser it is an HTTPS git client against GitHub/GitLab, which is a real dependency and a real decision.

Recommendation: build it as a plugin, after network:, on desktop first. If it needs an API the manifest cannot express, that is the signal to grow slate.v1 — which is precisely the pressure the plugin system was built to receive.

C. Hosted sync — superseded: this is the product

Superseded by ACCOUNTS-AND-SYNC.md. Kept for the record; the reasoning below was an argument about cost and liability, not about what the product is.

Conflict resolution, encryption, accounts, servers, liability, and a monthly bill for something a folder already does. FEATURES.md refused it; PLATFORM.md refused it; nothing has changed except that we now have a plugin system, which makes the refusal cheaper rather than harder to hold.

If it is ever built, it is a plugin someone else could have written, not a thing core knows about.

D. Real-time collaboration — a different product

CRDTs change the data model from "files you own" to "a document we host". PRD §2 lists it as a non-goal. Agreed, permanently.


3. Accounts — superseded by ACCOUNTS-AND-SYNC.md §2

One line of this survives: the app stays fully usable signed out. The rest — the refusal of accounts, the "license key, not a login" recommendation — is replaced by a real account model.

The position (superseded)

Core never requires a session. The app opens, reads, and writes vaults with no network stack at all. There is no login screen on the roadmap and there should not be one. This is PLATFORM.md decision #6 and it should be treated as load-bearing: the moment the editor requires a session, the product's promise — "plain text, on your disk, yours" — is no longer true, whatever the marketing says.

The one login worth building, and it is not a login

A license key for a paid desktop build:

  • A signed string, pasted once.
  • Verified offline against a public key baked into the binary.
  • Stored in the OS keychain, never in the vault (PLATFORM.md's hard rule).
  • No server at runtime. Works on a plane. Works if we go out of business.

Roughly 50 lines and one build step. It is not an account: there is no profile, no session, nothing to log out of, and nothing to breach.

If an account ever appears anyway

Three rules, and they are not negotiable individually:

  1. Additive and inert. The app is fully functional signed out. Signing in adds a service; it never unlocks the editor.
  2. Nothing in the vault depends on it. A vault written by a signed-in user opens identically for a signed-out one, in Slate or in any other editor.
  3. It arrives as a plugin first. If a hosted service cannot be expressed as a plugin with declared permissions, the plugin API is too weak — and that is a reason to fix the API, not to put a login in core.

4. What to actually do

# Do Cost Unblocks
1 Detect external changes — mtime check on focus, ask before clobbering a dirty buffer ~140 lines Folder sync being honest
2 Document folder sync in the README as the answer prose Most users, today
3 Tauri FS watching ~80 lines, M5 Live instead of on-focus
4 network: broker + statusline egress indicator ~90 lines Any plugin that leaves the machine
5 Git sync plugin ~250 lines The target user's real answer
Hosted sync, real-time collab, accounts in core Refused

Step 1 is not optional and does not depend on any decision in this document. Everything else can wait for someone to ask.


Decisions this document asks for

# Decision Cost of deciding now Cost of deciding later
1 External changes are detected before any sync story is told ~140 lines Silent data loss on a synced folder, which is the worst bug a notes app can have
2 Machine state never syncs, and settings sync only because they are notes nothing — it is already true A settings sync system, then a settings screen, then an account
3 Sync is a plugin, never core nothing Servers, liability, and a monthly bill for what a folder does
4 Core never requires a session; a paid build uses an offline license key ~50 lines when it matters The product's entire promise