Official website: https://mobile.fuj.io — product story, screens, day-in-the-life, architecture, schemas.
| Repo | What |
|---|---|
| mobile_field_service | Phone app (this repo) |
| mobile_field_service_pages | Docs site — mobile.fuj.io |
| mobile_field_service_deployment | Capella cluster + App Services (Terraform / Ansible) |
A phone app for people who work in the field — inspect a pump, deliver parts, take an order on a doorstep — even when there is no signal.
When the radio comes back, the phone syncs with Couchbase (Sync Gateway or Capella). The office sees your copy of the work, not a tug-of-war on the same document.
Version: 0.1.0 (0.1.0+1 on device) — RELEASE_NOTES.md
Status: Expo SDK 52 + Couchbase Lite (encrypted field.* + replicator). Vector search is not on.
Platforms: iOS and Android development builds (not Expo Go).
Repo: Fujio-Turner/mobile_field_service
- Sign in with work email (or Sign in with your company IdP). Demo: Jon Hale / Maya Chen / Priya Shah (or any other non-empty id as Jon).
- Today shows a live clock and a seconds countdown (next start, in-progress end, late-by, or end of day), then jobs and orders for this calendar day, newest first. Scroll for more. Right of the time is a tiny sync HUD (green / yellow / pending count) — not a second card.
- Tap a row to open it by document id. Start work (bottom of the inbound screen) makes your copy. Hermes has no
crypto; ids use expo-crypto. - Do the work with no network: photos, parts, notes, employee chat, map of nearby assets. Mark operations and checklist with outline vs filled buttons (not a cycling row tap). Each save keeps a history of what changed (qty 10 → 5) with time and place. Driving around writes tracking crumbs for that employee and day (TTL 30 days).
- Complete. That copy freezes and the office owns it. Forgot a photo? You add a new sheet of paper that points at the original — you do not reopen the frozen one.
- Profile has a one-line sync bar, optional Large screen optimize (right-thumb zone; Left hand when that is on), and Settings / debug (versions, database path, replicator, collection counts, channel filters, job rules). Developer catalog of every setting: guides/SETTINGS.md.
Walk through a real day:
| If you… | Read |
|---|---|
| Inspect / repair / move company kit | Day in the life — assets |
| Serve a customer site, then take another order | Day in the life — customer |
| Sell and deliver, then the next stop | Day in the life — sales |
Index of all three: docs/DAY_IN_LIFE.md.
The tab bar is Today · Notes · Map · Stock · Chat · Profile. Notes and Stock are lists (general notes; van qty + catalog). The four shots below are the surfaces you live in during a shift. Demo login as Jon Hale (workModes: assets) hides the orders card; Maya / Priya still see orders on Today.
This is the home list. The teal header is a live clock plus a seconds countdown (next start, in-progress end, late-by, or end of day) and the job that countdown belongs to.
Sync HUD sits in that same row, immediately after the time — a dot and at most two short figures, not another card on top:
| You see | Meaning |
|---|---|
| Green dot | Connected to Sync Gateway |
Yellow dot + 12m / 2h |
Not connected; time since last successful sync |
| Number after the dot | Documents waiting to push |
| Red dot | Sync error |
Demo has no replicator, so Today shows a yellow dot and no elapsed/count (nothing has synced, nothing is queued). Other tabs (Notes, Map, Stock, Chat, Profile) keep a one-line bar: Connected, Not connected · last synced …, N waiting to send, or Local only in demo.
Walk-up depends on workModes. Assets and customer get Walk-up job (CreateWorkOrderIn) — still a ticket, not labor; labor starts after Start work. Sales does not show that card; Priya’s walk-up is New field order (customer + order). Customer mode keeps both.
Jobs are one row per work order: number · kind, site, summary, time, priority stripe. Badges: Started (you already have a copy), Reassigned (inbound went to someone else today; your copy is still yours), Amendment. A reassigned leftover from an earlier day does not stay on Today. Tap is a KV get: inbound if you have not started, outbound copy if you have. Assets-mode Today does not show commercial orders; customer and sales logins do.
The Map tab is on for every mode. Pins always come from local Couchbase Lite, so they still show in airplane mode. The basemap (OpenFreeMap Liberty) needs network.
| Mode | What you plot | Chips |
|---|---|---|
| Assets (Jon) | Company kit (field.assets) |
Area / Near job / Near me, type (pump / valve) |
| Sales (Priya) | Customers and order site.geo |
Area / Near stop / Near me |
| Customer (Maya) | Customers and order sites by default | Same as sales, plus Kit for company assets |
Tap a pin or a row to open the asset, customer, or order. On the assets map, Use on {job} links kit to your outbound copy. Completing a job does not write the asset master.
Employee chat only — not customers. Completing a job does not freeze threads; messages push on send (readyToPush).
Type a DM employeeId (or @ them in the body) and a message. Tag a job with WO-10482 or an order with ORD-3301; the message stores those ids and shows a chip that opens the job or order. Job-screen chat is a separate thread (thr:wo:{inbound id}) that already points at that work order. Status changes (block, complete) stay on the work-order copy; chat is the conversation.
Who you are on this device: username, email, employeeId, workModes, auth strategy, DB name, app version. Crumbs today is a count of tracking points (no map dump).
The sync bar at the top is the same status as Today’s HUD, in words (demo: Local only). Large screen optimize (off by default) moves primary buttons into the thumb zone; Left hand appears when that is on. Settings / debug is versions, DB path, replicator URL/status, collection counts, optional channel filters, job rules, and DB encryption (default off). Full list: guides/SETTINGS.md. Search follows workModes: Jon gets notes + assets; Priya and Maya get notes + products + customers (Maya also gets assets when the open job is kit). Product and customer hits open. Sign out drops the session, not the database key.
Dispatch — or you, on the phone — can create a job ticket. You never edit a ticket the office already sent. You copy it, work the copy, and sync that.
If they reassign the job while you are in a basement on a job scheduled for today, Today shows Reassigned. Your copy still goes up. Two documents for one job number is expected. A leftover from an earlier day does not stay on Today.
The phone keeps an encrypted Couchbase Lite database. Sync Gateway (or Capella App Services) talks to Couchbase Server. Login mints a session with an expiry; a fat OIDC token is exchanged for that session, not sent on every sync request.
Replication how-to (for implementers): guides/REPLICATION.md · official API: cbl-reactnative.dev · sample app: expo-cbl-travel.
We use the Fujio-Turner cbl-reactnative fork (vector index, Couchbase Lite 4.x target), not the official 1.1 plugin as source of truth.
| Role | Why you are here |
|---|---|
| Field tech / sales | The product: Today, jobs, orders, photos, offline. |
| Engineer joining the repo | Start with a day-in-the-life, then docs/DESIGN.md, then guides/. |
| Someone wiring Sync Gateway | Email is the SG username. Session + TTL. Example below. |
Chat is employees only (you ↔ dispatch), not customers. Orders snapshot catalog prices; we assume stock is there; no credit card payment in this version. Proof of delivery is a photo for now (signature pad is later).
Node ≥ 20. Couchbase Lite is native — Expo Go cannot open the encrypted DB or MapLibre. npm install runs scripts/fetch-cbl-native.sh (CBL Swift + JS submodules npm does not fetch).
cp .env.example .env # demo login, no Sync Gateway
npm install
npm test
npx expo run:ios -d "iPhone 16 Pro"
# or
npx expo run:androidAlready installed. run:ios is the first native compile. After that, opening the app without Metro shows Expo’s Development servers screen (Connect gray if the last URL is a dead LAN IP like 10.0.0.25). That is the dev client waiting for a bundler, not a Field Service crash. Start Metro on localhost and open the simulator:
npm run start:iosDo not paste a 10.* / 192.168.* URL into the simulator — it talks to the Mac at 127.0.0.1:8081. Phone on Wi-Fi: npm start (LAN) and scan the QR. This app is never Expo Go (start passes --dev-client).
Xcode 26.4+. Apple Clang 21 rejects {fmt} 11.0.2 consteval (React Native 0.76). plugin.fmt.js (listed in app.json) injects a CocoaPods post_install hook that sets FMT_USE_CONSTEVAL 0 in the vendored fmt headers. ios/ is gitignored; the plugin re-applies on prebuild. Remove it when we move to Expo SDK 56 / RN ≥ 0.83.9.
Missing CBL Swift. npm does not clone cbl-reactnative submodules. If npx expo run:ios fails with cannot find 'DatabaseManager' in scope (and ReplicatorManager / CollectionManager), node_modules/cbl-reactnative/ios/cbl-js-swift/ is empty. Run bash scripts/fetch-cbl-native.sh (or npm install so postinstall runs), then rebuild.
.env.example sets EXPO_PUBLIC_AUTH_STRATEGY=demo. Sign in as Jon (jon.hale@example.com), Maya (maya.chen@example.com), or Priya (priya.shah@example.com); any other non-empty id is Jon. You land on Today with seed jobs (WO-10470 / 10482 / 10490, plus WO-10460 leftover) once the DB is open. Version on the login footer and Profile comes from app.json, not a hard-coded string.
Every env flag, Profile toggle, debug job rule, and Keychain key: guides/SETTINGS.md.
Replication schema is build-time (EXPO_PUBLIC_REPL_SCHEMA=simple|oneshot), not a Profile setting. simple (default) keeps one continuous replicator. oneshot pulls workordersin + orders first, then one-shots all field collections every EXPO_PUBLIC_REPL_ONESHOT_SEC seconds (default 300) and when the app comes to the foreground.
Simulator keyboard. If a field focuses but no keyboard appears, the Mac keyboard is attached: ⌘K (I/O → Keyboard → Toggle Software Keyboard).
CBL SQL++ for Mobile does not accept IN ['a','b'] or parameterized LIMIT $limit / OFFSET $offset. Today and bbox queries use equality/OR and a numeric LIMIT baked into the SQL string.
Map. Asset pins always come from local field.assets (bbox SQL++), including airplane mode. The basemap is OpenFreeMap Liberty via MapLibre and needs network (or MapLibre’s last style cache). Style URL: EXPO_PUBLIC_MAP_STYLE_URL (default https://tiles.openfreemap.org/styles/liberty). MBTiles is later.
Binding: Fujio-Turner/cbl-reactnative (feat/vector-search-support). Shipping encryption + vector still needs a Couchbase Lite Enterprise license; lab/testing the module does not. Vector search is not in this train (S15).
| I want to… | Go here |
|---|---|
| Get the phone running and syncing | Getting started |
| Understand the product | Official site + this README + DAY_IN_LIFE.md |
| See collections, queries, freeze rules | DESIGN.md |
| See document / collection fields | docs/schema/ |
| See login, Keychain, 401 handling | AUTH.md |
| See every env / Profile / debug setting | guides/SETTINGS.md |
| Inspect versions, DB path, replication on device | Profile → Settings / debug |
| See what we build in what order | ROADMAP.md |
| See what shipped in this build | RELEASE_NOTES.md |
| Log, style UI, cut a release, sync | guides/ |
| Agent / coding rules | AGENT.md |
| Tests | tests/ |
Login identifier is the email. Channels come from document fields (emp:, email:, cus:, route:, region:, store:, …) — never a hardcoded public / type: channel. A job can sit on a route before it is assigned to a person.
username: jon.hale@example.com
password: (set on App Services; never stored in Couchbase Lite)
session: POST /mfs/_session → session_id + expires
replicator: SessionAuthenticator(session_id, "SyncGatewaySession")
Profile (field.users)
employeeId: E-4412
email: jon.hale@example.com
username: tech.jon ← audit.by only
workModes: ["assets"] ← Maya Chen customer / Priya Shah sales
routeIds: ["HFD-NORTH"]
region: CT
storeId: HFD-YARD
customerIds: [cus:…]
assetTypes: ["pump", "valve"]
Channels: emp:E-4412
email:jon.hale@example.com
route:HFD-NORTH
region:CT
store:HFD-YARD
…plus cus: and assetType: from the profile
Lab/testing the native module does not require a Couchbase Lite Enterprise license; shipping encryption + vector still does. Capella layout: mobile_field_service_deployment.



