Skip to content

Offline-first architecture

A syndicate does not live where the data centre is. The aircraft is on a grass strip; the boat is in a mooring with no mast-high signal; the holiday home is at the end of a single-track lane. Syndik8 is built so that the app works where the asset is — offline, and trusting its own local copy — and reconciles with the server when connectivity comes back. This page explains the three people that model is built for, what “offline-first” means in practice, and the one part of the app that is deliberately not offline.

The pilot on an airfield with no signal. Pre-flight, on the apron, phone in hand, wants to see today’s booking, log the out-time, record a squawk from yesterday’s flight. Mobile data is two bars on a good day and gone on most.

The instructor in a steel-frame hangar. Signal behaves as if the hangar is a Faraday cage. They want to scroll through past usage logs and log an hour’s ground brief against today’s booking. Round-trips to a server are not available.

The treasurer working in a cafe. Usually has Wi-Fi, occasionally doesn’t. They want to pull up member balances and last month’s expenses without the app going blank.

None of these three users want to think about connectivity. The app’s job is to make connectivity invisible — to work with what is local, to queue what needs to go to the server, and to tell the user honestly when something is waiting.

The sync engine keeps a copy of your syndicate’s data in a local SQLite database on the device. Reads come from that local copy, which is why the calendar, the bookings tab, and the asset details render instantly whether or not the device is online. Writes go into the local copy first, then into a queue the engine pushes to the server at the next opportunity.

Around twenty tables sync to the device — the full list is on the Offline capabilities reference page. They cover every part of the operational day: syndicates, memberships, assets, bookings, usage logs, maintenance items and logs, squawks, billing schemes, transactions, expenses, notifications, and more. If it is something you look at on a normal day, it is probably on the device.

Sync rules follow active membership: a syndicate is in your local cache only while your membership is in active status. The moment you leave a syndicate (or an admin removes you) the syndicate’s tables drop out of your device’s sync set and the cache stops receiving updates for them. The data already on the device stays until the next clean sync clears it, but no new changes will appear.

How the app talks to you about offline state

Section titled “How the app talks to you about offline state”

Two affordances do most of the work, and you’ll see them everywhere.

The “will sync when you’re back online” suffix on success messages. When you create or edit something that is queued in the local database, the success snackbar tells you so. Online, you see Booking created. Offline, the same action shows Booking created — will sync when you’re back online on a second line. The change is real on your device immediately; the suffix is a promise that the server will hear about it when connectivity returns. Used on bookings (create, confirm, approve, reject, cancel), usage logs, expense submissions, squawk reports, and the other PowerSync-backed writes.

Disabled buttons with a clear reason on online-only actions. A small set of actions cannot be queued — they touch the financial ledger or call out to a third-party service that has no offline contract. When the device is offline, those buttons disable and surface a short snackbar that names the reason: Settlements need a connection, Approval needs a connection, Invites need a connection, Payment setup needs a connection. The button re-enables automatically when the connection returns. You don’t have to refresh, and you don’t get the chance to tap an action that would silently drop on the floor.

The app prefers to be honest. It would rather tell you “this can’t go out yet” than accept a tap that has nowhere to go.

A few pieces of business logic that started life as server-side checks have moved into the app so they keep working offline. The most visible is the maintenance check that runs when you open the booking dialog. Syndik8 reads the maintenance items, the asset’s current meter reading, and the recent usage history — all from the local database — and shows an amber or red warning in the dialog if the booking would push past a service threshold. Typical evaluation time is a few milliseconds; no network round-trip is required. A pilot opening the app on the apron sees the same warning the treasurer would see at home on Wi-Fi.

The profile avatar in the navigation chrome carries a live connectivity dot. Green means connected and receiving real-time updates from the server. Amber means reconnecting — the app has noticed the connection dropped and is trying to restore it. Red means offline; no PowerSync session, no network. The dot reacts in seconds rather than waiting for a protocol timeout, because the OS tells the app when the radio goes away.

That single indicator is the whole status display. You do not need to tap through screens to know whether the app is current — the avatar says so.

The trade-off: eventual consistency vs strict consistency

Section titled “The trade-off: eventual consistency vs strict consistency”

Offline-first is a deliberate choice with consequences. Two devices can make conflicting edits while both offline; when they reconnect, one has to give way. Syndik8 handles different parts of the app with different consistency models depending on how bad a conflict would be.

Most of the app is eventually consistent. If two members edit the same record offline and reconnect, the later write wins. For a booking note or a usage-log comment, last-write-wins is fine. A member typing on a phone in a hangar does not want the app to refuse the edit because another member edited the record three minutes ago.

Bookings are eventually consistent, but with a server-side guard. Two members who each create a confirmed booking overlapping the same slot offline will both see their booking locally. When their devices reconnect, the first to reach the server wins; the second is rejected by the database’s exclusion constraint and surfaces as a sync error on the losing device. The losing member edits the times or cancels. See Conflict detection. The cost is occasional late rejection; the benefit is that members can plan bookings on an airfield with no signal at all.

Financial finalisation is strictly consistent. This is the one domain where offline-first is the wrong answer. Money conflicts are not an inconvenience — they are a correctness problem. If two people attempted to finalise the same booking offline, the ledger could end up double-charged or silently re-written under the last-write-wins rule. Syndik8 therefore writes ledger sources of truth (settlements, reversals, disbursements) only online. The synced subset of the financial tables — transactions, expenses, member funds, mandates, advance notices — is read-only on the device, so members can see their balance and “debit incoming” cards offline; any action that changes the ledger goes through an online button. Finalising a booking, approving or rejecting an expense, generating an invite, and starting payment setup all sit behind disabled buttons when the device is offline, with a short reason in a snackbar. The sister explanation page on why finalisation is a separate step explores the money model more fully.

Why the web build is a slightly different story

Section titled “Why the web build is a slightly different story”

The web build runs in a browser tab and does not keep local data between sessions the way iOS and Android do. Close the tab and the local cache goes with it; reopen it and the app re-hydrates from the server. This is a consequence of the browser security model, which is hostile to long-lived background data stores in ways the native platforms are not. On the web, a member signing in with a flaky connection may see parts of the UI take a moment to populate while the first sync runs. On iOS and Android, the same member opens the app to a fully populated UI regardless of whether they are online.