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:
| Platform | Local Database | Storage Risk | Background Sync | Encryption |
|---|---|---|---|---|
| iOS | SwiftData | None (native file storage) | BGTaskScheduler | Native CommonCrypto |
| Android | Room | None (native file storage) | WorkManager | Native javax.crypto |
| PWA | IndexedDB | iOS Safari 7-day eviction policy | Foreground 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.