Skip to Content
DevelopersOffline sync

Offline sync

PennyPlan must work with no signal. The web app saves every entry on the device first, then sends the queue (the outbox) to POST /v1/entries/sync whenever it can. This page is the contract between the two.

The one rule: never lose an entry

An entry sitting in the outbox is the person’s data. It is removed only after the server has said it has it. Not on an error, not on a sign-out, not on an expired session, not on an app update.

How an entry travels

  1. The device creates the entry with a client-generated UUID and saves it locally. This works fully offline.
  2. When online, it sends the outbox, oldest first, up to 200 entries per call.
  3. The server handles every entry independently and returns one result per entry, in the same order.
  4. For each result the device acts as below, and only then removes it from the outbox.
ResultMeaningThe device should
createdThe server saved itRemove from the outbox
updatedThe server applied your newer editRemove from the outbox
unchangedThe server already had exactly thisRemove from the outbox
staleThe server already holds a newer editReplace the local copy with the entry in the result, then remove from the outbox
rejectedThis entry is invalid (see error.details)Keep it, mark it as needing attention, and show the problem. Never drop it silently

Because every entry has its own result, one bad entry never blocks the others.

Retrying is always safe

Sending the same batch again changes nothing: the second time every entry comes back unchanged. So if the connection drops halfway and you do not know what arrived, just send it all again. This is the reason ids are generated on the device.

Last write wins, by the device’s clock for that edit

Each entry carries clientUpdatedAt, the moment it was last edited on the device. If two devices edit the same entry, the later clientUpdatedAt wins. An older edit arriving late comes back as stale and is ignored, so the device learns what the server kept.

Deleting offline

A deletion is sent as the full entry with "deleted": true and its own, newer clientUpdatedAt. The server keeps a hidden record of it, so:

  • an older edit that arrives late cannot bring the entry back (stale);
  • a newer edit can, if the person really did re-create it.

When the session has expired

The device may be offline for days and its sign-in may have expired in the meantime. The login is a cookie managed by the web app’s server (see ADR 0005), so the outbox cannot send anything until the person signs in again.

When a sync call comes back 401:

  1. Stop syncing and keep the outbox untouched. Do not retry in a loop.
  2. Show a clear, calm message: “Your entries are saved on this phone. Sign in to send them.”
  3. After the person signs in, resume from the same outbox. Nothing is lost or re-entered.
  4. If signing in produces a different account than the one that created the entries, do not send them under the new account. Ask the person what to do.

Limits

LimitValue
Entries per sync call200
AmountWhole kobo, 1 to 2,000,000,000
Note500 characters
Counterparty120 characters
DateNot before 2000, not more than a day ahead

How it is tested

The API’s real-database tests send a 60-entry offline session twice and check there are exactly 60 entries; sync the same new entry four times at once and check there is exactly one; confirm a late old edit cannot resurrect a deleted entry; and confirm that one user can never touch another’s entries by reusing an id.

Last updated on