Most apps can't sync without a server. We just built sync mechanics that work with USB drives, email attachments, or carrier pigeons. The network is just a transport detail—the real problem was already solved.
The Sync Paradox
Everyone builds sync backwards. They pick a sync provider first—Firebase, Supabase, custom WebSocket servers—then twist their data model to fit.
We did the opposite. We asked: What does sync actually need?
- A way to describe what changed (operations)
- A way to package those changes (bundles)
- A way to merge them safely (merge engine)
- A way to handle conflicts (explicit resolution)
Notice what's missing? The network. Sync is a data problem, not a network problem.
Sync Bundles: Operations as Cargo
A Sync Bundle is just a JSON file containing operations:
{
"bundleVersion": 1,
"createdAt": 1702934400000,
"workspaceId": "ws_abc123",
"workspaceName": "My Finances",
"baseOperationId": null,
"operations": [
{ "opType": "transaction.create", ... },
{ "opType": "transfer.create", ... },
{ "opType": "reconciliation.toggle", ... }
]
}Key design decisions:
- Operations only — No full entity snapshots. Changes are small.
- Chronologically ordered — Replay in sequence produces consistent state.
- Human-readable JSON — You can open it in a text editor.
- Incremental support — "Since last sync" bundles are tiny.
The Deterministic Merge Engine
When you import a bundle, the merge engine does three things:
1. Detect Duplicates
Operations with the same ID are skipped. Importing twice is safe.
2. Detect Conflicts
Same entity modified differently? Update vs delete? We catch it.
3. Accept Clean
New operations that don't conflict are accepted immediately.
The merge is deterministic. Same inputs, same outputs. Always.
Conflicts Are Features, Not Bugs
Most sync systems hide conflicts. They use "last write wins" or "most recent timestamp" and hope nobody notices when data silently disappears.
We show you everything:
| Conflict Type | What Happened |
|---|---|
| update_update | Same transaction edited differently on both devices |
| update_delete | You edited it, they deleted it |
| delete_update | You deleted it, they edited it |
| rule_manual | Rule changed it on one device, manual change on another |
For each conflict, you choose:
- Keep Local — Your version wins
- Accept Incoming — Their version wins
- Create Duplicate — Keep both versions
- Merge Manually — Review and fix yourself
No silent data loss. Ever.
How It Works In Practice
// Device A: Working offline Created: Transaction "Coffee" -$4.50 Updated: Transaction "Groceries" → "Whole Foods" // Device B: Working offline Created: Transaction "Gas" -$45.00 Deleted: Transaction "Groceries" // Later: Device A exports bundle Export → sync-bundle-2024-12-19.json // Device B imports bundle Import ← sync-bundle-2024-12-19.json ✓ "Coffee" accepted (new operation) ⚠ "Groceries" conflict (update vs delete) → User chooses: Keep deletion // Result: Both devices now match
Why No Networking?
This sprint deliberately excluded all networking. Why?
- Separation of concerns — Sync logic and transport logic are independent
- Offline-first verification — We can prove it works without any server
- Multiple transport options — USB, email, cloud storage, WebRTC—all work
- No lock-in — Your data, your choice of how to move it
The Test Suite
We added 13 new tests for sync mechanics:
// Sync Bundle Tests ✓ Full bundle exports all operations ✓ Valid bundle passes validation ✓ Invalid bundle fails validation ✓ Bundle without operations fails validation ✓ Future version bundle fails validation ✓ Importing same bundle twice is idempotent ✓ Merge detects update conflicts within concurrency window ✓ Incremental bundle exports operations since base ✓ Sync state is tracked per peer ✓ Conflict resolution generates appropriate options ✓ Bundle exports to valid JSON blob ✓ Operations are chronologically ordered ✓ Ledger invariants hold after merge Total: 58 tests pass (45 previous + 13 new)
What We Built
sync-bundle.ts
Bundle creation, validation, export. Tracks sync state per peer.
merge-engine.ts
Deterministic merge, conflict detection, resolution options.
operation-replay.ts
Replays operations through LedgerService. Idempotent.
/sync UI
Export bundles, import bundles, resolve conflicts.
What Comes Next
With sync mechanics complete, we can now add optional transports:
- WebRTC — Peer-to-peer sync without servers
- Cloud relay — Optional server for convenience
- QR codes — Scan to sync between phones
But all of those are optional. The bundle-and-merge system works without any of them.
Sync used to mean "connect to our servers." Now it means "exchange these files however you want."
Your data. Your rules. Your transport.
— The Accelerate Finance Team