Skip to content
Hanna.
← All work

Offline tablet app · work orders and job completion · 2024–2026

Offline drafts had no owner, so the newest work lost

Not sync frequency. Offline drafts had no ownership, so a device that had been away could land stale state on top of newer work — and nothing in the model could make that write fail.

How it was reported

Records keep reverting.

What it actually was

The obvious reading was sync frequency — devices at sea go days without a connection. Syncing more often would have shrunk the window and left the class of bug intact.

The real fault was that a draft said nothing about who owned it or when it stopped being valid. Two devices could hold the same record, and whichever reconnected last won — not whichever held the newer work.

So it presented as reverting rather than as a conflict. Nothing errored, the write succeeded, and the model had no concept that would have let it fail.

Device Aoffline, daysDevice Bonlinetimeboth read rev 1writes rev 2returns holding a rev 1 draftlate-draft guard rejects
Both devices read the same record. The online device moves it on. Without an ownership statement the returning device's older draft was applied on top; the late-draft guard rejects it instead.

What shipped

  • Generic draft-sync DTOs, so every module inherits one ownership contract instead of inventing its own and drifting.
  • A late-draft-sync guard: a draft arriving after its job has completed is rejected, because by then it provably describes a state that no longer exists.
  • Soft delete with cascade cleanup, so a removal propagates instead of leaving orphans that resurface on the next sync as new.
  • A versioned migration for the draft tables, plus resume-on-open — a half-finished job is still there when the app comes back, which is what the crew needed and why drafts existed.
  • Then the recovery: scripts that cleared duplicated work orders and, after a cleanup cost records it should not have, restored and backfilled them. Separately, the crashes, endless loads and local-database misuse after completion events, and batch-write and list-caching work on device.

Evidence

vessels running the platform
100+
customer organisations in production
3

The class is closed rather than the ticket. A stale write cannot land at all now, rather than landing less often.

  • React Native
  • Expo
  • WatermelonDB
  • SQLite
  • TypeScript
  • NestJS