OrderOpsBETA

Field notesEngineering

Picking through the dead spots

Every warehouse has a dead spot. Ours is halfway down the high racking, where steel shelving and a few tonnes of stock do to Wi-Fi what they do to everything else. A picking app that needs the network for every tap dies right there, holding your pick list.

Taps are local first

On the iPad picker, a tap never waits for the server. The row flips to picked on the same frame, written to the local store; the network call is a side effect. Pending mutations sit in a queue on the device, and a network monitor watches for the online edge — the moment connectivity comes back, the queue drains oldest-first. The server endpoint is idempotent, so a tap replayed twice can't pick twice.

Failures back off two seconds, doubling to a cap, and retry for as long as it takes — a transport failure never expires a pick. Only an action the server actually refuses — a 4xx that would refuse again tomorrow — is parked as dead-lettered rather than blocking everything behind it, visible on the iPad's diagnostics screen where support can clear it. The toolbar pill tells the picker the truth the whole time.

#   action            state     tries
1   pick  A1 · ×2     sent      1
2   pick  B3 · ×1     pending   0
3   qty   C2 · 1→3    pending   0
×   pick  D4 · ×1     dead      10   ← parked; clear via Diagnostics

The bug our own audit caught

Offline queues have a classic failure mode: a refresh from the server arrives while taps are still queued, and the server's older view clobbers the picker's newer one. Our reliability audit caught exactly that — a drain pass could erase taps queued mid-sync. The rule now is simple: any item with a pending queued action keeps its local state on refresh, and the pending flag clears only after a successful drain. The server doesn't get to overwrite a tap it hasn't seen yet.

You can't pick what's already packed

While someone picks, the bench packs. The pack–pick interlock locks an order's units at picked the moment a label exists for them — the count can't drop below what's already in a box. Void the label and the lock releases on the next refresh. There's no separate lock table to corrupt; it's computed live from active shipments.

The honest limits: offline-first is not offline-forever. A batch has to be opened online at least once to fill the cache, and creating a new batch needs the server to resolve its filters against the orders table. Scanning, which we originally deferred, has since shipped — camera or hardware scanner, off by default per organisation — and it rides the same offline queue as a tap.

Picking through the dead spots — OrderOps