How openGym Survives Two Devices at Once

openGym’s ownership model becomes difficult at the exact moment it becomes useful: a person can work on a laptop, continue on a phone, lose the network, and return later without wanting either device to erase the other. This chapter follows what happens when two copies of one profile diverge. The key idea is that synchronization is not a request to pick the newest file. It is a protocol for proving which version a device saw, recovering the current server copy when that proof is stale, and merging records according to what they mean.

From a Read Revision to a Safe Retry

The server-backed profile is treated as a revisioned document. When a device reads the profile, it learns the current server revision. When it later pushes a change, it sends the revision it last saw as its base. That value is a promise: “the state I am about to write descends from this version.” If the server still has that revision, the write can proceed. If another device has written since, the promise is false, and accepting the whole document would risk silently dropping the other device’s work.

openGym therefore refuses the stale write and returns the current document with a conflict response. The refusal is not the end of the operation. It gives the client the state it was missing, so the client can merge its local changes with the server’s newer copy and retry against the revision it has now received. This is more useful than simply returning an error because the server has already supplied the information needed for recovery. The retry is bounded; repeated conflict or network failure leaves the local change marked as owed rather than pretending it reached the shared profile.

The normal path is intentionally cheaper than a full download every time. While the app is open, a signed-in device can ask for the current revision alone. If the revision has not moved, it can keep its existing copy. If it has moved, or if the device returns from the background or comes back online with an owed change, it performs the fuller pull or push path. A phone that has spent an afternoon offline does not need to guess whether the server changed; it can learn that through the revision boundary.

sequenceDiagram participant A as Laptop participant S as Server participant B as Phone A->>S: Read profile at revision 12 B->>S: Write profile based on revision 12 S-->>B: Accept and store revision 13 A->>S: Push changes with base revision 12 S-->>A: Conflict with current state at revision 13 A->>A: Merge local changes with returned state A->>S: Retry merged state against revision 13 S-->>A: Accept and return revision 14 B-->>S: Later revision check S-->>B: Confirm or return newer revision

The sequence protects against a particular class of loss: a stale tab that still holds an old whole-profile copy. Without a conditional write, the tab could save its old routines and workouts after a phone had added new work, making the phone’s history disappear from the server even though the tab had made no conscious decision to delete it. The revision check turns that silent overwrite into a visible recovery path.

The client also keeps track of whether its local copy owes the server a change.