Offline Mode

A real local database in your browser, a persistent write-ahead queue, and a background worker that drains it — not just a "you're offline" banner.

ℹ️ Install as an app PersivApps can be installed as a Progressive Web App (PWA) on desktop, Android, and iOS. Installed apps get the same offline support described here, plus faster launch and an icon on your home screen. Installing changes nothing about how offline data is stored or synced — it's the same mechanism either way.

The Local Database

Everything offline runs on top of a real client-side database — an IndexedDB instance (persivapps_offline) with dedicated object stores for each kind of data, so a lookup doesn't mean scanning a flat blob:

StoreKeyed byHolds
smarttablesidSmartTable metadata (name, entry/display schema versions) for offline rendering
foldersidFolder structure, so navigation works offline
metadatasmarttable_idPer-smarttable settings needed to render the entry form without a server round-trip
columnssmarttable_idColumn/field definitions for the entries table view
entriesid, indexed by smarttable_id and smarttable_data_entry_idCached entry data — both what's synced from the server and what you've created offline
offline_queueauto-increment id, indexed by temp_id and statusEvery write you've made offline, waiting to be sent — see below
sync_logauto-increment idA record of every completed sync run, for audit/troubleshooting

The schema is versioned (currently version 10) with real migration logic — indexes get rebuilt and stale stores get dropped automatically the first time a new app version opens an older browser's database, rather than requiring you to clear site data.

What Works Offline vs. What Doesn't

The dividing line is simple: anything answerable from data already on the device works; anything needing the server to decide something does not.

ActionOfflineWhy
View smarttables, folders and previously loaded entries Works Served from the local stores, not the network. Every SmartTable you opened while online is stored in full (all pages, refreshed at most every 10 minutes), so search and filters offline see every row.
Create and edit entries Works Written to entries immediately and queued for sync.
Autofill suggestions Works Uses the last-synced reference data, so it can be stale.
Past-record suggestions Works Comes from your own previously entered data, held locally.
Submitting for approval Needs a connection Validation happens server-side; the decision cannot be made on the device.
PDF / print output Needs a connection Rendered server-side.
Large file uploads Needs a connection Past the size the local queue will hold.
Anything depending on another user's live state Needs a connection For example, whether someone else currently holds an edit lock on the same entry — only the server knows.

The Offline Queue — how a write actually gets there

Saving or updating an entry offline doesn't fail and doesn't silently disappear — it's written as a queue item in offline_queue with a real state machine: pending → syncing → done, with failed and cancelled as the other two terminal-ish states. Each item records its type (SAVE_ENTRY or UPDATE_ENTRY), its payload, a timestamp, and — critically — a temp_id.

💡 Why temp_id matters: chained edits A brand-new offline entry doesn't have a real server ID yet, so it's assigned a temporary one (prefixed offline_) until sync confirms it. If you create an entry offline and then edit it again before ever syncing, both the create and the edit share that same temp_id — they're a chain. Cancel the first one and the whole chain cancels together, rather than leaving an edit queued against a create that will never happen. Once the server confirms the create and hands back a real ID, every other queued item referencing that temp_id — including its URL and any embedded entry-id reference in the payload — is rewritten to the real ID automatically, and the local entries row is migrated from the temp key to the real one.
IndicatorMeaning
Offline bannerShown when the app detects no connection; includes a "Sync Now" button
Sync queue badgeLive count of items in pending or failed state
Sync status indicatorWhether a sync is currently running, succeeded, or failed

When a Change Won't Sync — "Needs attention"

Most failed uploads (no signal, a server hiccup) are retried automatically with a growing delay. Two kinds are not: a change the server rejected (for example a required field is missing, or you no longer have access), and a change that failed 25 times. These show in the Sync Log's Pending Queue as Needs attention, with the server's reason underneath, and Sync Now skips them, since sending the same thing again would fail the same way. A red note at the top of the queue says how many there are.

Each queued change has three buttons:

ButtonWhat it does
Retry (only on "Needs attention")Puts the change back in the queue and syncs straight away. Use it once the cause is fixed, e.g. after an admin restores your access.
Download (arrow icon)Saves one .json file with the exact request and every attached photo or file inside it. Send this file to your support contact, and they can create or update the entry for you. It does not contain your password or login token.
DiscardDeletes the change from this device for good. Download it first if you may need it.

The file is named offline-request_<type>_st<SmartTable id>_q<queue id>_<YYYYMMDD_HHMMSS>.json, with the time in UTC.

How Sync Actually Runs — a Web Worker, not the main thread

Sync doesn't run on the same thread as the UI. On first use, the app spins up a dedicated Web Worker (js/offline-worker.js) and hands it your API base URL and access token; from then on, syncing, connectivity checks, and speed measurement all happen off-thread so a large sync doesn't freeze typing or scrolling. If the browser can't create a Worker for some reason (some embedded WebView contexts can't), the same logic runs a fallback pass on the main thread instead — you get the same behavior, just without the isolation.

  • As soon as the browser's online event fires, sync starts automatically
  • You can also trigger it manually with Sync Now
  • Every code path that can trigger a sync — the automatic online listener, the manual button, the Sync Log modal, even the network-speed monitor — checks a single offlineModeEnabled flag at the point of sending the message to the worker, rather than trusting every caller to check it first. This was a deliberate fix: gating it only at initialization reliably missed some of these call sites in practice.
  • A sync_log entry records what was synced and when, so you can confirm nothing was lost after an extended offline stretch
💡 Partial failure is safe by construction Sync failing partway through (connection drops again mid-sync) can't corrupt anything, because each queue item's state only advances one step at a time and is persisted to IndexedDB after every transition — whatever didn't reach done simply stays pending/failed and is picked up on the next successful connection. There's no in-memory-only sync state that a crash could lose.

Network Speed Indicator

The same Web Worker measures actual throughput in the background (polling roughly every 15 seconds) and reports it back to the UI as a 0–4 bar indicator, so you know whether to expect slower saves and syncs before you start a long entry — this isn't just navigator.onLine (which only tells you a network interface exists, not that it's usable).

Service Worker & App-Shell Caching

Separately from the IndexedDB layer above, a browser Service Worker caches the app's own code (JS/CSS/HTML) so the app can even launch with no connection, not just keep working once it's open. The offline-sync worker script and its dependencies are explicitly precached on install — before the general caching strategy takes over — specifically so the offline-save path is guaranteed to be available from the very first load rather than picked up opportunistically on a later visit.

Mobile App

PersivApps is also available as a native mobile app for Android and iOS — a lightweight wrapper around the same app described throughout this site, not a separate product to relearn. It adds a few things a browser tab can't do on its own:

  • Native print & share — printing and sharing a document use your device's native print dialog and share sheet instead of a browser-only download
  • Downloads — files save straight to your device's Downloads folder, with a completion notification
  • Push notifications — the same notifications described on the Notifications page, delivered as native mobile push
  • Faster reloads while offline — the app keeps its own local copy of the app shell, so it can reopen even from a cold start with no connection
ℹ️ Same sync, same data The mobile app uses the same offline queue and sync described above — there's nothing separate to configure, and switching between the mobile app and a browser doesn't change how your data syncs.