skills/mobile-offline-sync-architect/SKILL.md
Mobile offline-first architecture with local databases, CRDT conflict resolution, and background sync. Activate on: offline sync, offline-first, local database, WatermelonDB, SQLite, CRDT, conflict resolution, background sync, mobile persistence. NOT for: server-side databases (use data-pipeline-engineer), web caching strategies (use pwa-architect), API design (use api-architect).
npx skillsauth add curiositech/windags-skills mobile-offline-sync-architectInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert in building offline-first mobile applications with local databases, conflict resolution, and reliable background synchronization.
Activate on: "offline sync", "offline-first app", "local database mobile", "WatermelonDB", "SQLite sync", "CRDT conflict resolution", "background sync", "mobile data persistence", "op-sqlite"
NOT for: Server databases → data-pipeline-engineer | Web caching → pwa-architect | API design → api-architect
updated_at, deleted_at, sync_status columns to all synced tables| Domain | Technologies | |--------|-------------| | Local DB | op-sqlite, WatermelonDB, Realm, expo-sqlite | | Sync Protocols | Delta sync, CRDT (Yjs, Automerge), operational transform | | Conflict Resolution | Last-write-wins, merge functions, CRDT automatic merge | | Background Sync | react-native-background-fetch, expo-background-fetch | | Platforms | PowerSync, ElectricSQL, Replicache, custom sync engines |
┌─────────────────────────────────┐
│ Mobile App │
│ ┌──────────┐ ┌─────────────┐ │
│ │ UI Layer │──│ Sync Engine │ │
│ └──────────┘ └──────┬──────┘ │
│ │ │
│ ┌────────────────────┴───────┐ │
│ │ Local SQLite DB │ │
│ │ (source of truth offline) │ │
│ └────────────────────────────┘ │
└────────────────┬────────────────┘
│ Background sync
│ (when online)
▼
┌────────────────────────────────┐
│ Server │
│ ┌──────────┐ ┌────────────┐ │
│ │ Sync API │──│ Server DB │ │
│ │ /sync │ │ (Postgres) │ │
│ └──────────┘ └────────────┘ │
└────────────────────────────────┘
Pull: GET /sync?since=<timestamp> → changed records
Push: POST /sync { changes: [...] } → conflicts
interface SyncRequest {
lastSyncTimestamp: string;
changes: ChangeSet[]; // Local changes since last sync
}
interface ChangeSet {
table: string;
created: Record[];
updated: Record[];
deleted: { id: string; deleted_at: string }[];
}
interface SyncResponse {
serverTimestamp: string;
changes: ChangeSet[]; // Server changes since client's lastSync
conflicts: Conflict[]; // Records changed on both sides
}
// Conflict resolution strategy
function resolveConflict(local: Record, server: Record): Record {
// Strategy 1: Last-write-wins (simple, data loss possible)
return local.updated_at > server.updated_at ? local : server;
// Strategy 2: Field-level merge (no data loss for non-conflicting fields)
// return mergeFields(local, server, base);
// Strategy 3: CRDT (automatic, no conflicts by design)
// return crdtMerge(local, server);
}
import { synchronize } from '@nozbe/watermelondb/sync';
async function syncDatabase() {
await synchronize({
database,
pullChanges: async ({ lastPulledAt }) => {
const response = await api.get('/sync', {
params: { since: lastPulledAt },
});
return {
changes: response.data.changes,
timestamp: response.data.serverTimestamp,
};
},
pushChanges: async ({ changes, lastPulledAt }) => {
await api.post('/sync', { changes, lastPulledAt });
},
migrationsEnabledAtVersion: 1,
});
}
[ ] Local database chosen and configured (op-sqlite, WatermelonDB, Realm)
[ ] Sync schema includes updated_at, deleted_at, sync_status columns
[ ] Delta sync protocol implemented (not full-table)
[ ] Conflict resolution strategy defined and tested
[ ] Background sync configured (periodic + on-reconnect)
[ ] Offline indicator visible in UI
[ ] Pending local changes count displayed
[ ] Sync errors handled gracefully (retry with exponential backoff)
[ ] Data integrity: no data loss during conflict resolution
[ ] Large dataset performance tested (10K+ records)
[ ] Soft deletes used (deleted_at, not hard DELETE)
[ ] Sync works after app kill and restart
data-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.