Implementation plan

Add offline draft sync to the notes app

status: draft · est. ~2 days · owner: notes-core

Let people keep writing when the network drops. We will persist draft edits locally, queue unsynced changes, and reconcile them with the server on reconnect — so a note is never lost and the editor never blocks on a request. The goal is a sync layer that is invisible when it works and honest when it conflicts.

Phase 1 — Local persistence

Make every keystroke durable on-device
  1. Add an IndexedDB store, drafts, keyed by note id with a version and updatedAt field.
  2. Wrap writes in a debounced (400ms) saveDraft() so typing never triggers a write storm.
  3. On editor mount, hydrate from the local draft before fetching the server copy.
  4. Migrate any drafts currently held in localStorage on first launch, then clear the old keys.

Phase 2 — Sync queue

Track what hasn't reached the server yet
  1. Introduce an outbox: an ordered queue of pending mutations with a stable client id.
  2. Flush the queue on a successful network ping and on the browser online event.
  3. Use exponential backoff for failed flushes; cap retries and surface a quiet "saving…" state.
  4. Mark each note with a sync badge — synced, pending, or offline — in the list view.

Phase 3 — Conflict reconciliation

Decide fairly when both sides changed
  1. Compare server version against the draft's base version on flush.
  2. If they match, fast-forward; if not, run a three-way merge on the text body.
  3. When the merge is ambiguous, keep both versions and prompt the user to choose — never silently overwrite.
  4. Log every conflict resolution to telemetry so we can tune the merge heuristics later.

Phase 4 — Polish & rollout

Ship it behind a flag, watch, widen
  1. Gate the whole flow behind the offline_drafts feature flag.
  2. Add an integration test that toggles the network mid-edit and asserts no data loss.
  3. Roll out to 10% of accounts, watch conflict and flush-failure rates for a week.
  4. Remove the flag and the legacy autosave path once metrics hold steady.

Key changes — files to touch

src/sync/draftStore.tsnew — IndexedDB wrapper & migration
src/sync/outbox.tsnew — pending mutation queue & flush
src/editor/useDraft.tshydrate, debounce, persist
src/sync/merge.tsthree-way text reconciliation
src/notes/NoteListItem.tsxsync status badge
config/flags.tsadd offline_drafts gate

Open questions