Offline-First PWA for Room Tablets: How VetRx Ledger Keeps Running When Wi-Fi Drops
Service workers, IndexedDB event queuing, and background sync — the architecture that makes room-tablet logging resilient to network failures.
David Park is a senior software engineer specializing in healthcare compliance infrastructure. Before joining VetRx Ledger, he built audit-trail systems for DEA-regulated pharmaceutical distributors. He focuses on offline-first architecture, cryptographic integrity, and performance under clinical-pace constraints.
The Wi-Fi Problem in Veterinary Hospitals
Surgery suites are often shielded. Isolation wards are purpose-built to limit biological and sometimes electromagnetic transmission. Older hospital wings with thick concrete walls turn hallway Wi-Fi signals into background noise. Room tablets that depend on a live internet connection for every event submission fail at exactly the moments when reliable logging matters most — mid-procedure, under time pressure.
A tech recording a fentanyl draw during a hip replacement surgery should not be staring at a loading spinner waiting for the network to recover. Traditional web applications fail completely when offline: form submissions hang, data is lost, staff workaround with paper scraps, and the scraps don't make it into the digital record.
VetRx Ledger solves this with an offline-first architecture: events are queued locally the instant they're submitted, and the app reports success to the user immediately. When connectivity returns — whether in 30 seconds or 4 hours — the queue syncs to the server automatically in the background.
Progressive Web App Foundations
A Progressive Web App (PWA) is a web application that uses three browser capabilities to behave like a native app:
| Capability | What It Provides | VetRx Ledger Use |
|---|---|---|
| Service Worker | Background JS thread that intercepts network requests | Offline caching, event queuing, background sync |
| Web App Manifest | JSON metadata for installable home-screen app | Icon, name, theme color, display mode |
| IndexedDB | Client-side structured database | Offline event queue storage |
On iOS and Android, tapping “Add to Home Screen” after visiting the site installs VetRx Ledger as a standalone app with its own icon — no App Store required. Full installation instructions are in the Tablet Installation Guide.
Service Worker v4 Architecture
VetRx Ledger's Service Worker (version 4) uses three independent cache buckets with different strategies:
| Cache | Contents | Strategy |
|---|---|---|
vetrx-v4 | HTML shell, CSS, JS bundles | Stale-while-revalidate (serve cached, update in background) |
WASM_CACHE | Tesseract.js WASM + language data (~6 MB) | Cache-first (never expire — OCR works offline) |
API routes (/api/*) | Live server data | Network-first with offline fallback to IndexedDB queue |
The request handling flow for API calls:
Browser → Service Worker
For /api/ledger/events (POST):
├─ Network available?
│ YES → Forward to server, cache success response
│ NO → Write to IndexedDB offline queue
│ Return optimistic { ok: true, queued: true }
│ Register BackgroundSync tag "vetrx-sync"
│
└─ On BackgroundSync event:
Flush IndexedDB queue → POST /api/sync (batch)
Clear synced entries → Update UI badgeFree Offline-First PWA Implementation Guide
A technical guide covering Service Worker architecture, IndexedDB queue design, and Background Sync patterns for offline-capable veterinary compliance applications.
Download Technical GuideIndexedDB Offline Queue
The lib/idb-queue.ts module manages an IndexedDB object store named vetrx-offline-queue. Each queued event is stored as a structured record:
interface QueuedEvent {
id: string; // UUID — idempotency key
endpoint: string; // e.g. "/api/ledger/events"
method: "POST";
body: string; // JSON-stringified request body
timestamp: string; // ISO 8601 — when queued
retryCount: number; // incremented on each failed sync attempt
}
// Writing to queue (fires when offline)
await queueEvent({
endpoint: "/api/ledger/events",
body: JSON.stringify(eventPayload),
});
// Flushing queue (fires on BackgroundSync or manual trigger)
const pending = await getPendingEvents();
for (const event of pending) {
const res = await fetch(event.endpoint, {
method: event.method,
body: event.body,
});
if (res.ok) await removeFromQueue(event.id);
}The idempotency UUID prevents duplicate events if a sync attempt partially completes — the server ignores events whose ID has already been committed to the chain.
Background Sync
The Background Sync API allows the Service Worker to defer network operations until connectivity is available — even if the browser tab has been closed. VetRx Ledger registers the sync tag vetrx-sync whenever an event is added to the offline queue.
On Chrome and Chromium-based Android browsers, the OS fires the sync event when the device regains connectivity, triggering the flush workflow automatically. This means a tech can record events all morning in a spotty-signal surgery suite, close the tablet, return to the nursing station — and the queue syncs in the background without any manual action.
WASM Caching for Offline OCR
Tesseract.js — the OCR engine that reads drug labels from the camera — uses a WebAssembly binary (~6 MB) and language data for text recognition. This is loaded once from CDN on first use and cached permanently in the WASM_CACHE bucket.
The WASM cache uses a cache-first strategy: once cached, the WASM binary is never re-requested from the network. This means OCR-based drug name, lot number, and expiry capture works in full offline mode — the tablet doesn't need internet connectivity to read a drug label once the WASM has been cached.
The WASM cache bucket is preserved across Service Worker version upgrades (it's separate from the vetrx-v4 asset cache). This prevents a 6 MB re-download on every app update.
Optimistic UI and the Offline Status Banner
When an event is queued offline, VetRx Ledger shows an optimistic success state immediately — “Event recorded (pending sync)” — rather than showing an error. This is deliberate: the event is durably stored in IndexedDB and will sync as soon as the network is available. Showing an error would disrupt the clinical workflow.
The Offline Status banner (bottom of screen on all pages) provides ambient awareness:
- 🔴 Offline — shows queue depth (e.g., “3 events pending sync”)
- 🟡 Syncing… — when flush is in progress
- 🟢 All events synced — with last sync timestamp
Staff can always see whether events have reached the server. This eliminates the anxiety of “did it save?” during poor-connectivity situations.
Testing Offline Behavior
VetRx Ledger's Playwright E2E suite includes an offline-pwa.spec.tsfile that verifies offline behavior using Playwright's network simulation:
// Simulate offline and submit an event
await context.setOffline(true);
await page.click('#submit-event-btn');
await expect(page.locator('#event-success-banner')).toBeVisible();
// Verify queue depth increases
const status = await page.locator('.offline-status').textContent();
expect(status).toContain('pending');
// Restore connectivity and verify sync
await context.setOffline(false);
await page.waitForTimeout(1000);
const synced = await page.locator('.offline-status').textContent();
expect(synced).toContain('synced');These tests run against the deployed production URL — the OIDC token and background sync behavior only function in a real deployment environment, not in local dev.
Trade-offs and Known Limitations
- Dual-witness events cannot be completed fully offline. The witness token validation requires a server round-trip to check whether the token has already been consumed. This is intentional — allowing offline token consumption would create a replay attack vector. Dual-witness WASTE events require connectivity at the moment the witness authenticates.
- IndexedDB storage limits. Most devices allow ~50 MB of IndexedDB storage before the browser requests permission. At typical event sizes (~500 bytes per event), this supports ~100,000 offline events before hitting the limit. For high-volume practices, regular syncs are important.
- iOS Background Sync. iOS 17 has partial Background Sync support; iOS 16 and below require the app to be in the foreground for sync to trigger. Apple continues to improve PWA support in each iOS release.
- Offline chain verification. Chain verification requires reading all events from the server, which requires connectivity. Offline devices can log events but cannot run a full chain audit until they sync.
Ready to install VetRx Ledger on your room tablets?
The tablet installation guide covers iOS and Android PWA installation in under 3 minutes.
Tablet Installation Guide →Get compliance updates in your inbox
Monthly DEA regulation updates, inspection prep tips, and SOP guides for veterinary teams. No spam, ever.