# Offline Sync

> Design offline-first sync for apps spanning web, mobile, and desktop — conflict resolution, delta sync, and local storage per platform. Use when adding offline support, handling sync conflicts, or choosing a client-side database.

- Skill: `rockclaver/offline-sync` (Agent Skill)
- Install (CLI): `npx skillmds@latest add rockclaver/offline-sync`
- Raw SKILL.md: https://api.skillmd.com/api/skills/rockclaver/offline-sync/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: rockclaver (https://skillmd.com/u/rockclaver)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/rockclaver/offline-sync

---

# Offline Sync

## Conflict Resolution — Choose One Per Data Type

| Strategy | Use when |
|---|---|
| Last-Write-Wins (LWW) | Settings, profile fields — losing concurrent edits is acceptable |
| Server-wins | Inventory, balances — server is authoritative |
| Client-wins | Purely local data (drafts, preferences) never shared |
| Field-level merge | Documents — non-overlapping fields merge; conflicts flagged |
| CRDT (Yjs, Automerge) | Collaborative text, sets, counters — guaranteed merge; high complexity |

Define the policy per collection in writing before implementation — changing it later requires a data migration.

## Sync Protocol Design

- Every write gets a **logical clock** (Lamport or hybrid logical clock). Wall-clock alone is insufficient — clocks drift and go backward.
- **Delta sync**: client sends `{since: lastSyncedCursor}`; server returns only changes since that cursor. Never full-table sync after initial load.
- **Tombstones**: soft-delete with `deletedAt`; never hard-delete until all clients have synced past that version.
- **Sync cursor**: opaque, server-generated; stored per collection on the client; resumed on next sync.

## Local Storage by Platform

| Platform | Recommended |
|---|---|
| Web | IndexedDB — persistent and queryable |
| iOS / macOS | Core Data, SQLite (GRDB), or Realm |
| Android | Room (SQLite) or Realm |
| Electron / Tauri | SQLite via `better-sqlite3` or `rusqlite` |
| Cross-platform | WatermelonDB (RN + web), PowerSync, Realm |

## Optimistic UI

Apply the change locally immediately; sync in background with a client-generated idempotency key. On success, reconcile if server differs. On failure, surface the error — never silently discard user edits.

## Background Sync

- Web: Background Sync API (Service Worker).
- iOS: `BGProcessingTask` / `BGAppRefreshTask` (not guaranteed — sync-on-foreground is primary).
- Android: WorkManager with network constraint.
- Desktop: `setInterval` + `navigator.onLine` / OS connectivity events.

## Testing Sync

- **Partition**: disable network mid-op; apply local changes; reconnect; assert server state matches.
- **Concurrent edits**: two clients edit offline; reconnect both; assert final state matches the conflict policy.
- **Tombstone propagation**: delete on client A while B is offline; reconnect both; assert B sees deletion.
- **Cursor fuzz**: send stale, future, malformed cursors; assert safe error or reset.

## Guardrails

- Never use wall-clock time alone as a conflict arbiter.
- Never hard-delete synced records until all clients advance past the deletion cursor.
- Apply TLS and field-level encryption for sensitive fields.

