Local-First Sync
Overview
This skill provides a reusable, minimal pattern for local-first persistence with background server sync, including dedupe/merge rules, tombstones for deletes, and a safe retry model that avoids infinite loops.
Use it when the user requires:
- •Instant local persistence (works offline)
- •Backend as eventual consistency store
- •Create/update/delete support
- •Explicit handling for failures and retries
Assumptions (state explicitly)
- •Whether backwards compatibility is required.
- •Whether the backend supports idempotency keys or client-provided ids.
- •What determines identity (server id, natural key, or fingerprint).
Data Model
Store local records with:
- •
client_id(UUID) - •
server_id? - •
sync_status:synced | pending | failed - •
pending_op:create | update | delete - •
last_error?(string) - •
local_updated_at(ISO) - •
deleted?(tombstone)
Recommendation: keep local records as a superset of server records (so rendering code can stay simple).
Storage Keys
- •Prefer versioned keys:
entity:v1:<entity_scope_or_id> - •Scope keys by natural grouping (e.g.
highlights:v1:${article_url}) to avoid large global blobs.
Core Workflow
1) Read path (open modal / load view)
- •Load local state immediately and render.
- •Fetch server state.
- •Merge + dedupe server into local.
- •Persist merged local state.
- •Kick off background sync for
pending/failedops.
2) Write path (create/update/delete)
Always do the local write first:
- •Create/update/delete in local store; mark
pending_opandsync_status=pending(or keep tombstone for delete). - •Render from local store.
- •Trigger background sync.
3) Sync loop (background)
For each record with pending_op:
- •
create: POST to server, then store returnedserver_idand clear pending fields. - •
update: PATCH to server (requiresserver_id), clear pending fields. - •
delete: DELETE on server ifserver_idexists; then remove local record.
Failure behavior:
- •Mark local record
sync_status=failedand setlast_error. - •Do not tight-loop retries; retry on explicit user action (button) and optionally on reconnect.
Merge + Dedupe Strategy
Identity approach (strong → weak):
- •
server_id - •Fingerprint of stable fields (example):
${start}:${end}:${normalized(text)}
Merge rules (common default):
- •Keep local tombstones: if local says
deletedandpending_op=delete, do not resurrect from server. - •Prefer newer note/edit: compare
local_updated_atvs serverupdated_atif available. - •Preserve local
client_idandsync_status.
UI Guidance
- •Show sync state near the entity list:
Synced | Saving | Offline | Failed. - •Add a
Retry syncbutton whenfailed. - •Don’t block user writes when offline; queue them.
Testing Checklist
- •Dedupe: server merge doesn’t create duplicates.
- •Tombstones: pending delete doesn’t resurrect.
- •Offline: create/update/delete marks
pendingand persists locally. - •Failure: marks
failedand recordslast_error. - •Retry: transitions
failed → pending → synced.
Debug Logging Guidance
- •Log one-line summary per sync run (counts: pending/failed/synced).
- •Log failing entity
client_id,server_id,pending_op, and request URL. - •Avoid logging full article text or other large/PII payloads.