Offline Approach

Strategy

Design conflicts out of the UX rather than resolve them in code. Use the single-owner write model to ensure every data partition has exactly one writer. Sync becomes a trivial queue flush.

Why Not Local-First Sync Engines?

Tools like PowerSync and ElectricSQL are production-ready sync engines that handle bidirectional sync with conflict resolution. They’re designed for the hard version of the problem we deliberately designed out of our UX.

Decision: start without them. The single-owner, append-only model is simple enough to implement directly. Revisit if complexity grows.

Client-Side Persistence

Each platform uses its native storage, with the shared core crate (public) handling data logic (validation, scoring, queue management) on all platforms:

PlatformLocal DatabaseStorage RiskBackground SyncEncryption
iOSSwiftDataNone (native file storage)BGTaskSchedulerNative CommonCrypto
AndroidRoomNone (native file storage)WorkManagerNative javax.crypto
PWAIndexedDBiOS Safari 7-day eviction policyForeground only (flush on reconnect while app is open)Limited

The native apps run the shared core crate locally — validation, scoring, and sync logic execute on-device, not on the server. Server-only logic (auth, billing, admin operations) stays in the private server crate and never ships to clients. This means full offline functionality for scoring, not just data capture.

Given the minimal data sizes (full event under 50 KB, individual scores 100-200 bytes), even a severely degraded connection can handle sync. Background sync on both iOS and Android ensures scores reach the server even when the app is not in the foreground.

Native Advantages

Going fully native with an embedded Rust core eliminates the entire category of WebView storage concerns and thin-client limitations. The mobile apps have full business logic, native persistence, and native background sync capabilities.