FieldForce Mobile App Design

FieldForce becomes a real offline-first Flutter mobile app backed by PowerSync, with the Go backend retaining all identity, write authority, conflict resolution, feature flags, and business rules. The grill pass tightened the design around Task as the canonical sync entity, mobile JWT/session source, Phase 1 Shift backend work, PowerSync Cloud Free for development, explicit uploadData endpoint mapping, rejected Task upload UX, and Crashlytics debug context.

View source markdown ↗ generated by gpt-5.5 · diagrams mermaid

FieldForce mobile is a Flutter, offline-first client over the existing Go backend. PowerSync owns interactive local data and sync; Go remains authoritative for identity, writes, conflict resolution, feature flags, location intake, and business rules.

Grill Outcome

Canonical entity

resolved

Task is the MVP sync entity. "Job" is informal copy only and Work Order is not modeled until the backend introduces it.

Security gate

resolved

Use the existing SolidStart / Better Auth source for mobile access: valid backend-issued session, isActive = true, and platform = "mobile", enforced by a centralized go_router guard.

Location authority

backend needed

A server-side ff_shifts or equivalent Shift entity is required before production background location can be enabled.

Write bridge

resolved

uploadData() is Dart-side bridge code that drains PowerSync queued writes into existing or entity-specific Go API endpoints.

Architecture

flowchart TD
  UI["Flutter presentation\nwidgets + Riverpod"]
  Domain["Pure Dart domain\nentities + use cases"]
  Data["Data repositories\nPowerSync + API datasources"]
  SQLite["Local SQLite\nPowerSync-managed"]
  Upload["PowerSync upload queue\nuploadData() Dart bridge"]
  Location["flutter_background_geolocation\nown offline queue"]
  PowerSync["Self-hosted PowerSync"]
  Go["Go API\nexisting endpoints"]
  LocAPI["Dedicated Go location endpoint"]
  PG["Postgres\nsingle source of truth"]
  Push["NATS JetStream / FCM + APNs"]

  UI --> Domain --> Data
  Data --> SQLite
  Data --> Upload --> Go
  Data --> Location --> LocAPI
  SQLite <--> PowerSync
  PowerSync <--> PG
  Go --> PG
  LocAPI --> PG
  PG --> Push
  Push --> UI
Interactive entities sync through PowerSync; location telemetry deliberately bypasses PowerSync.

Core Decisions

Flutter + Melos for the mobile platform

Build FieldForce as a Flutter app in a Melos workspace.

The app needs offline-first work, push, background location, secure storage, and future multi-app package sharing. Flutter gives native mobile capability without hand-splitting iOS and Android.

Rejected: WebView/mobile web for native background constraints; separate native apps for scope and duplication.

PowerSync for interactive offline data

Use PowerSync-managed SQLite for Tasks and other interactive entities.

PowerSync gives durable local reads/writes, upload replay, and reactive queries while keeping Postgres as the source of truth.

Rejected: hand-built sync, conflict resolution, or local schema migration machinery.

Separate location telemetry path

Route background location through flutter_background_geolocation to a dedicated Go endpoint.

Location pings are high-volume append-only telemetry and should not bloat PowerSync buckets.

Rejected: syncing location through PowerSync buckets.

Security, Sync, And Conflicts

ConcernDecisionConsequence
Verified accessSolidStart / Better Auth JWT source with platform = "mobile"Device never asserts verification locally; valid session + isActive = true gates access.
Offline writesuploadData() maps queued operations to Go endpointsNo generic PowerSync write endpoint; endpoint map is a Phase 1 artifact.
Task status conflictADR-0013 state machine winsRejected queued events show the reason, latest server version, failed local submit, and review/resubmit path.
PowerSync deploymentCloud Free for developmentProduction hosting and bucket storage are decided later after real usage and compliance needs are known.
Background locationThree gates: feature flags, permission, active ShiftProduction location remains off until Shift exists and is synced.
Crash debuggingCrashlytics plus custom non-PII keysRecord flavor, build, platform, route, org, hashed user ID, active Task/Shift, PowerSync state, queue count, last sync, location gates, and feature flags.

Roadmap

  1. Phase 1

    Offline-first Task cycle, mobile JWT gate, ff_shifts backend work, brand UI, crash reporting, and location behind flags.

  2. Phase 2

    FCM/APNs hardening, silent pre-sync, deep links, assignment and approver notifications.

  3. Phase 3

    Location maturity: OEM battery handling, shift-scoped consent, geofencing and activity tuning.

  4. Phase 4+

    Digital Worker package reuse, OTA evaluation, richer field capabilities, and advanced sync scoping.

Open Questions

  • PowerSync deployment: use PowerSync Cloud Free for development; decide production hosting and bucket storage later.
  • JWT details: use the existing SolidStart / Better Auth auth source, with standard/custom JWT claims and platform = "mobile".
  • Shift: implement ff_shifts or equivalent as explicit Phase 1 backend scope before enabling production location.
  • Endpoint map: fully define each uploadData() operation-to-endpoint mapping before implementation.
  • Rejected Task uploads: show clear reason, latest server version, failed local submit state, and review/resubmit path; never silently delete the action.
  • Sync Rules: MVP only requires users see allowed records; deeper RBAC partitioning is deferred.
  • Licensing: confirm paid flutter_background_geolocation production license.
  • Crashlytics context: set custom non-PII keys and verify no raw PII, tokens, or precise location are sent.