Skip to content

Reference build

Fieldnote

Mobile, offline-first

An app that has to keep working in a basement with no bars, then reconcile cleanly when it surfaces.

Year
2026
Engagement
Reference build

What it had to do.

Technicians receive a day of jobs, then drive to places with no reliable connectivity: plant rooms, lift shafts, rural sites. They record work done, parts used, signatures and photographs. Everything has to be captured on the spot and reach the office intact.

The constraint

Offline is the normal case, not the error case. An app that shows a spinner when the signal drops is useless here, so the phone holds the truth for the length of a shift and the server reconciles afterwards.

Architecture

How it is put together.

Device

  • React Native appExpo, OTA updates
  • Local databaseSQLite via WatermelonDB
  • Media queuephotos, signatures

Sync

  • Pull / push endpointchangeset based
  • Media uploaderresumable, backoff

Server

  • Fastify APINode, TypeScript
  • Conflict resolverfield-level LWW
  • Job schedulerdispatch, routing

Data

  • PostgreSQLjobs, audit trail
  • Object storagemedia, signed URLs
The local database and the sync endpoint carry the design: the server is a reconciliation point, not the live source of truth during a shift.

Stack

  • React Native
  • Expo
  • SQLite
  • WatermelonDB
  • Fastify
  • PostgreSQL

Every choice cost something.

An architecture without trade-offs is an architecture nobody has stress-tested. Here is what we gave up for what we got.

The phone is the source of truth during a shift

What we did

Local-first storage in SQLite through WatermelonDB, with a changeset sync rather than request-per-action.

Why

Every read and write is local, so the interface never waits on a network that is not there. Sync becomes a background concern instead of a blocking one.

What it cost

Schema migrations now run on the device, on data we cannot inspect first. Every migration ships with a rollback path and is tested against a corpus of real-shaped local databases before release.

Conflicts resolve per field, not per record

What we did

Last-writer-wins at field level, with every overwrite kept in a server-side audit trail.

Why

Two technicians touching the same job usually touch different fields. Record-level resolution would silently discard one person's entire afternoon.

What it cost

Field-level merging is wrong for anything where a record must stay internally consistent: money, stock counts. Those fields are explicitly excluded and resolved server-side instead.

Photographs sync separately from data, and the UI says so

What we did

Structured data syncs first; media uploads in a resumable queue with exponential backoff.

Why

A 4 MB photo should never hold up the job record the dispatcher is waiting for. Data is small and urgent; media is large and can wait for better signal.

What it cost

A job can be 'synced' while its photos are still in flight. Rather than hide that, the interface shows the two states separately. A lie here becomes a dispute later.

How it was built.

  1. Weeks 1-2

    Sync design

    Local schema, changeset protocol and conflict rules specified and reviewed before any screen was built.

  2. Weeks 3-5

    Capture flows

    Job list, job detail, parts, time, signature capture and camera integration, all offline by default.

  3. Weeks 6-8

    Sync and media

    Push/pull implementation, resumable uploads, retry semantics, and the airplane-mode test suite.

  4. Weeks 9-10

    Store submission

    Build pipelines for both stores, permissions copy, privacy declarations, and the review round-trip.

What is true of it.

Properties of the build we can demonstrate, not business results we did not measure.

  • A full shift can be completed with the device in airplane mode, then reconciled without manual intervention.

  • Interrupted uploads resume rather than restart, including across app restarts.

  • Every conflict resolution is recorded, so a disputed job record can be reconstructed field by field.

  • One codebase ships to both iOS and Android, with platform-specific code confined to camera and permissions.

Next step

Tell us what needs building.

A 30-minute call, under mutual NDA if you want one signed first. You leave with a view on scope, team shape and budget range, whether or not you work with us.

Or write to hello@warmbench.dev