The brief was a handheld scanning app for annual stocktake. The site survey told us the one fact that mattered more than any feature request: the warehouse's steel racking blocked wifi almost everywhere except the office at the front.
Designing for offline as the default, not the fallback
Most apps treat offline as a degraded mode with a banner warning the user. We built this one the other way round: every scan writes to a local store first, full stop, and syncing to the server is a background concern the user never has to think about or wait for.
What that decision forced
- Conflict resolution had to be simple enough to explain to a warehouse supervisor in one sentence. We settled on: the most recent scan of a given bin location wins, and every scan is kept in a log rather than overwritten, so nothing is silently lost even when two people count the same bin.
- Barcode lookups needed a local copy of the product catalogue, refreshed whenever a device did have signal, because looking up a SKU over the network was not an option during the count itself.
- Progress reporting to the office had to tolerate large sync gaps — a device might not report in for hours, and the dashboard needed to say so honestly rather than showing stale numbers as if they were current.
The failure mode we designed against specifically
A device that loses power mid-sync should not lose scans that were already recorded locally. We tested this directly — killing the app and the device battery mid-transfer, repeatedly, during development — because it was the single scenario most likely to happen for real during an eight-hour stocktake shift and least likely to be caught by ordinary testing.
Offline-first is not a feature you add once the online version works. It is a different architecture, and retrofitting it is close to a rewrite.
What we measured on the day
Total scans recorded against total scans eventually synced, with a target of zero permanent loss regardless of how long a device stayed offline. On the actual stocktake day, sync lag ran to several hours on some devices deep in the racking, and the eventual reconciliation was complete, which was the only number the client actually cared about.
What we would do differently
Run the killed-power test even earlier in the build. It found problems that were cheap to fix when we found them and would have been expensive to discover on the warehouse floor.
