Sync Engine | Cine Power Planner

Sync Engine

Offline-first cloud synchronization with conflict detection, delta sync, and real-time updates

Overview

The Sync Engine enables cloud synchronization for authenticated users, ensuring data is backed up and accessible across devices. It implements an offline-first architecture with change tracking, delta sync via SHA-256 hash comparison, LZ compression for wire efficiency, an outbox pattern for reliable delivery, and real-time updates via Supabase Realtime channels.

Requirements

Functional Requirements

IDRequirementAcceptance CriteriaPrioritySource
REQ-SYN-001The system shall track local changes and batch-sync them to Supabase when authenticatedEdit data → change tracked; sync triggers → data appears in cloud; offline edits → queued in outboxMustsyncEngine
REQ-SYN-002The system shall detect conflicts when the same entity is modified locally and remotelyEdit on device A + B → sync → conflict detected; conflict UI shows both versions; user chooses resolutionMustsyncEngineConflicts
REQ-SYN-003The system shall use delta sync with SHA-256 hashing to minimize data transferEdit 1 field → only changed entity synced (not full dataset); hash match → skip; hash mismatch → syncMustdiff
REQ-SYN-004The system shall compress sync payloads over 10 KB with the browser's native CompressionStream (gzip)Payload > COMPRESSION_THRESHOLD (10 KB) → gzipped before send; smaller payloads sent as-is; a peer-pushed payload decompressing above MAX_DECOMPRESSED_BYTES (10 MB) is rejectedShouldcompression
REQ-SYN-005The system shall persist pending changes in an outbox that survives page reloadsMake offline edit → close tab → reopen → outbox still has pending change; reconnect → change syncedMustoutbox
REQ-SYN-006The system shall display sync status (syncing, synced, error, offline) in the UIBadge → shows current state; sync in progress → spinner; error → error styling and the detail line. The badge is role="status", not a controlMustSyncStatusBadge
REQ-SYN-007The system shall perform a full state reconciliation on login to detect and resolve divergenceLogin → full sync cycle; local-only data → pushed; remote-only data → pulled; conflicts → queued for resolutionMustloginSync
REQ-SYN-008The system shall receive real-time updates from other devices via Supabase Realtime channelsEdit on device A → device B receives update within 2s; UI updates without manual refreshShouldsyncSubscriptions
REQ-SYN-009The system shall sync every entity type in ALLOWED_ENTITY_TYPES (96 of them — projects, templates, contacts, owned gear, the device library, accounting, banking and more)Each entity type → syncs independently; partial failure → other types unaffected; entity routing correctMustsyncEntityRoutes

Non-Functional Requirements

IDRequirementMetricPriority
NFR-SYN-001Sync batch operations shall complete within 5s for ≤50 entitiesLatencyShould
NFR-SYN-002Outbox shall handle ≤500 pending changes without performance degradationCapacityShould
NFR-SYN-003Real-time updates shall arrive within 2s under normal network conditionsLatencyShould

Data Requirements

  • Sync entity: Wraps any entity with hash, version, timestamps, and compression flag
  • Outbox entry: Queued operation (upsert/delete) with retry counter
  • Supabase table: sync_entities for cloud state, apply_sync_batch RPC for atomic writes
  • Calendar events are the exception: they do not travel through sync_entities. They have their own table (calendar_events), their own metadata table (calendar_sync_metadata) and their own realtime subscription, and are upserted with deterministic UUIDv5 ids derived from user + project + type + index. This page said they synced as nested project metadata until 2026-09-07; the opposite is true — they are first-class rows, generated from a project's schedule.
  • Autosave debounce: AUTOSAVE_DELAY = 700 ms to coalesce rapid edits, with MAX_AUTOSAVE_DELAY = 5 s as the ceiling so a continuous stream of edits still gets written

Constraints & Limits

  • Requires authentication — sync is disabled for anonymous users
  • Supabase Realtime limited to active subscription channels
  • Batch size limited to prevent RPC timeout (large batches split automatically)

Offline Behavior

  • Outbox pattern: Changes queued locally, synced on reconnection
  • No data loss: Outbox persists across page reloads and browser restarts
  • Conflict detection: On reconnect, hash comparison detects divergence

Dependencies


How Sync Works

Automatic Sync

When signed in, sync happens automatically:

  1. Edit locally → change tracked with 700ms debounce
  2. Batch collected → pending changes queued in outbox
  3. Sync cycle → outbox drained, deltas computed, payload compressed
  4. Server writes → atomic transaction via apply_sync_batch
  5. Confirmation → outbox entries removed on success

[!NOTE] Delete operations are flushed immediately (no debounce) to prevent the deleted item from being resurrected by an incoming remote sync. Additionally, on page unload all pending entity changes — including owned gear, rental transactions, and timeline events — are flushed synchronously to IndexedDB to prevent data loss.

Sync Status

The sync badge in the header shows:

StatusMeaning
Syncing…Changes are still on their way — a push in flight, or rows still queued
SyncedAll changes synced — also the state shown when the status is not recognised
Sync Error"Changes are saved locally and will retry automatically"
OfflineNo network — changes queued in the outbox

There is no "Idle" state, and the badge is not a button. SYNC_STATUS has exactly four members (syncing, synced, error, offline); this page listed a fifth until 2026-09-07. The badge renders as role="status" with no click handler — a failed sync retries on its own, so there is nothing to click. Nothing is lost in the meantime: the changes are already in the local outbox.

Resolving Conflicts

When the same item is edited on multiple devices:

  1. Conflict notification appears
  2. Click to open the conflict resolution UI
  3. Compare local vs. remote versions side by side
  4. Choose which version to keep (or merge manually)

Data Retention & Cleanup

To ensure efficient synchronization and optimize device storage, the Sync Engine automatically enforces data retention limits:

  • Soft-Deleted Entities: A deleted item is kept as a tombstone and hard-deleted from the cloud 90 days after deleted_at — not 30, which this page claimed until 2026-09-07. The window is set in run_data_retention_cleanup() and is deliberately the same 90 days used for the other tombstones (project activity logs, closed invites, withdrawn consent rows).
  • Search History: The global search history is capped at the 10 most recent queries (MAX_HISTORY in useSearchHistory.js); saved searches are capped separately at 20.
  • Project Chat: Chat messages within collaborative projects are bounded by retention limits to maintain real-time performance.
  • Sync Metadata: Orphaned or stale synchronization metadata is periodically reaped to prevent storage bloat.

Tips

  1. Sign in for backup: Only authenticated users get cloud sync
  2. Check the status badge: Ensure changes are synced before switching devices
  3. Resolve conflicts promptly: Unresolved conflicts block further sync for that entity
  4. Use on stable connections: Large initial syncs work best on WiFi

Last Updated: 2026-09-07 Version: 0.790.2