H2O Professional Services · field ops suite

Build status & where we go next

Everything below was verified by running it, not by reading the code. Rate the directions and copy your feedback back to Claude — your choices are saved in this browser.

18commits this session
7 / 7Android modules building
7 / 7Xcode schemes building
21 / 29jobs mapped from real coords
$62,987revenue attributed, Aug MTD

Where it runs

TargetStateEvidence
ST Bridge running systemd user unit, survives reboot; production tenant 4155131542, read-only, raw passthrough off. Board and performance both answer in ~0.7s. Recovers from SIGKILL.
Android running All 7 modules green. Verified live on a phone emulator and a Pixel Tablet against the production tenant.
iOS / watchOS held All 7 schemes build against Xcode 26.6 on the Mac mini. Dispatch board verified running in the Simulator. Deliberately parked behind Android — see the parity ledger.
Physical devices untested No handset or tablet has ever run this. Android needs a USB device; iOS additionally needs an Apple Developer team for signing.

Live data vs sample

The suite's original problem was screens quietly showing invented data. This is where that stands.

ScreenAndroidiOSBacked by
Dispatch boardlivelive/api/dispatch/board — 19 appointments, 10 technicians, customer names and addresses
Technician performancelivelive/api/performance — revenue, jobs worked, WIP, memberships, conversion
Pricebook · Live tablivelive783 priced services across 5 pages
Pricebook · other 4 tabssamplesampleNothing to back them: materials and equipment have zero priced records in this tenant
Dispatch listlivepartialAndroid: real day per technician, multi-column on tablet with the shared detail rail. iOS still on assignment-derived counts
Dispatch maplivepartialAndroid plots 21 of 29 from real coordinates, fits the service area with outliers one tap away, full-bleed on tablet. iOS still geocodes technician home addresses
Admin dashboard tiles6 of 8 livesampleAndroid: board + performance. Parts Scanned and Lowest Margin have no ServiceTitan source and are marked per tile
Admin activity feedlivesample/api/activity — assignments, sold estimates and completed jobs merged newest-first over 7 days
Home-screen widgetlivesampleAndroid: next job on the board, with the count remaining. iOS widget not started

iOS parity ledger

What Android has that iOS does not, as of now. This is the cost of letting Android run ahead — it is recoverable in one catch-up pass, but it grows every time Android ships alone.

BehaviourEffort to portNote
Hold-to-peeksmallSwiftUI has a direct equivalent — onLongPressGesture(pressing:) gives press-and-release for free
iPad three-pane layoutlargeThe real work. Same shape, but SwiftUI layout, not Compose
Queue stripsmallPart of the three-pane pass
Map on real coordinatesmediumBridge side is done — the payload carries lat/long. iOS map still geocodes technician home addresses and fails most of them
List as the real schedulesmallSame board data, a different row layout
Map fixesn/aAndroid-specific: the clipping bug and the Compose line-height fix have no iOS counterpart

Directions

Rate each one. Nothing here is started.

Decisions already locked in

  • Revenue attribution — if you sell it it's your job; otherwise whoever worked it. Revenue splits across shared jobs so the column totals to what was billed; job counts stay whole and are labelled "jobs worked".
  • Pricebook is services-only — materials and equipment carry no pricing at all in this tenant, so quoting from them would show customers a wall of $0.
  • The bridge is read-only by construction — non-GET is rejected before any handler runs. ServiceTitan writes belong on the web stack's field API, where they're journalled per technician.
  • Cancellation is not failure — a LaunchedEffect that catches every exception also catches the cancellation fired by changing the date or period. That made the board claim it could not reach the bridge while showing data from the response that arrived. All fetching effects rethrow cancellation first.
  • Report dates are UTC — so a technician west of the office and the office itself see the same numbers for "today".
  • Three ServiceTitan filters silently lietechnicianId on appointments, date filters on dispatch/assignments, and startsOnOrBefore on appointments. All return 200 with wrong data. Diff filtered against unfiltered before trusting a new one.