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

  • Viewing smarttables, folders, and previously loaded entries — served from the stores above, not the network
  • Creating and editing entries — written to entries immediately and queued for sync (see below)
  • Autofill suggestions, using the last-synced reference data
  • Past-record suggestions from your own previously entered data
⚠️ What Needs a Connection Anything that needs the server to make a decision can't happen offline: approval-workflow submission (server-side validation), PDF generation (rendered server-side), file uploads past the size the queue will hold locally, and anything depending on another user's live state — e.g. checking whether someone else currently holds an edit lock on the same entry.

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: pendingsyncingdone, 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

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.