Baalda

Sync and offline

The two channels, what the sync indicator means, what happens after time offline, and when a server is behind the app.


Two things sync, on two paths. Structure, meaning the folder tree, the note registry and files, rides an always-on vault channel. Content rides one CRDT document per note.

The bridge, in detail

  • A change on disk fires the watcher. The old and new text are diffed and applied as operations in an origin-tagged transaction, so Yjs merges them with whatever arrived from other people in the meantime.
  • An update from the CRDT is debounced, serialized back to Markdown and written atomically. The app records the hash it wrote so the watcher ignores its own write, which is what breaks the echo loop.
  • Remote edits reach your file. Only changes that originated from disk are dropped on the way back out, which is what keeps the two directions from fighting.

The sync indicator

The light at the top describes the vault's connection rather than the open note, so it does not flicker every time you click a file. It settles in about a second after launch, because connecting and checking the vault happen together, and it reads Paused when the vault's folder is missing.

Coming back after time offline

Reconciliation is deliberately conservative, because the two states that look identical from the client, "this was deleted" and "I have not been told about it", have very different consequences.

  • Inbound deletes and revocations only act on a server-acknowledged state vector, and unsent edits get a recovery copy first.
  • Two devices that created the same path become two notes rather than one overwriting the other.
  • A rename made while the app was closed keeps the note's id, and a folder moved or renamed outside the app is applied as one server move that keeps every id underneath it, matched by sub-path and content hash.
  • The first pass after opening shows a one-time summary of the renames, moves and deletes made while the app was closed.
  • A live delete above the blast-radius cap asks whether to delete for everyone or restore, rather than being silently undone.
  • A vault root that is renamed, moved or unmounted stops every structural step and refuses writes that would recreate it.

Large vaults

A vault joins in batches rather than note by note, with the server capping how many notes, folders, files or documents one request may carry and how many bytes a bootstrap page may hold. Unchanged empty notes stop re-uploading after a restart, and notes that never finished uploading are retried.

When your server is behind

The desktop app updates itself and a self-hosted server does not. When the app needs something the server does not offer yet, a notice at the bottom of the sidebar says the backend is behind. Sync keeps working on the older path where one exists, and some features stay off until the server is updated. The app decides from the server's health endpoint, which lists the features it supports.

bash
curl https://<your-server>/health
{"ok":true,"version":"…","minDesktopVersion":null,"features":["notes-with-state","bootstrap-only","files-with-bytes"]}
A server that answers without a features list predates it and counts as behind. A server the app cannot reach at all is never reported as behind, because unreachable is not the same as old.

This page is written from docs/specs/03-sync-engine.md, docs/specs/05-vault-sync-engine.md, CHANGELOG.md and docs/DEPLOY.md in the repository. Those documents go further, and they are the ones that change first.

Read it on GitHub