ADR-009: Mobile & Offline-Capable Client Application

On this page

Status

Pending

Context

Caseworkers frequently operate in environments with unreliable connectivity — rural home visits, courthouses with poor cell coverage, vehicles between sites. They need to review case information, record contact notes, update safety assessments, and capture data during visits — then sync when connectivity returns.

CRAIG currently has a server-rendered web UI (Askama + htmx + Alpine.js) that requires a live connection to function. A field-ready client must work offline, store sensitive data securely on-device, and sync bidirectionally with the CRAIG backend.

Requirements

  • Works on phones (iOS, Android), laptops (Windows, macOS, Linux), and tablets

  • Offline-first: full read access to assigned caseload data, write capability for contacts/notes/assessments

  • Encrypted local storage (child welfare data is federally protected under 45 CFR Part 1355, 42 USC §5106a)

  • Bidirectional sync with conflict detection and resolution

  • Authentication via Keycloak OIDC (token refresh when online, cached credentials offline)

  • Must not duplicate the full server-side codebase — client is a complement to the web UI, not a replacement

Federal Security Context

  • 2 CFR 200.334 (formerly 45 CFR 75.361): Record retention and access controls for Title IV-B/IV-E programs

  • 45 CFR 95.621(f): ADP system security requirements (applies to CCWIS at cost thresholds)

  • IRS Publication 1075: FTI data protection (if system handles tax information for IV-E eligibility)

  • NIST SP 800-53: Security controls referenced by ACF for CCWIS security assessments

  • Encryption at rest is a baseline expectation — AES-256 minimum for on-device storage

Options

Option A: Tauri 2.0 (Rust backend + web frontend)

Tauri 2.0 supports desktop (Windows, macOS, Linux) and mobile (iOS, Android) from a single codebase. The Rust backend layer can share code with existing CRAIG crates. The frontend uses standard web tech (HTML/CSS/JS).

Architecture:

┌─────────────────────────────────────┐
│         Tauri Shell (per-platform)  │
│  ┌───────────────────────────────┐  │
│  │   Web Frontend (Alpine.js)    │  │  ← Reuses craig-web patterns
│  │   or lightweight JS framework │  │
│  └───────────┬───────────────────┘  │
│              │ Tauri IPC             │
│  ┌───────────▼───────────────────┐  │
│  │   Rust Core                   │  │
│  │   ├── craig-common (types)    │  │  ← Shared workspace crates
│  │   ├── craig-auth (JWT/OIDC)   │  │
│  │   ├── craig-reference (enums) │  │
│  │   ├── sync engine             │  │
│  │   └── SQLCipher local DB      │  │  ← Encrypted SQLite
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘
         ↕ HTTP (when online)
┌─────────────────────────────────────┐
│   CRAIG Backend Services            │
│   (cases, placement, intake, etc.)  │
└─────────────────────────────────────┘

Local storage: SQLCipher (AES-256 encrypted SQLite) via rusqlite with bundled-sqlcipher feature. Encryption key derived from user credentials + device key.

Sync strategy: Each entity has a version (monotonic counter) and updated_at timestamp. Client tracks a per-entity-type sync cursor. On reconnect:

  1. Pull: GET /v1/{entity}?updated_since={cursor} — server sends changed records

  2. Push: Client sends locally-modified records with their version

  3. Conflict: If server version > client’s base version, flag for manual resolution (caseworker reviews both versions)

Pros:

  • Rust backend shares code with CRAIG workspace crates (types, enums, auth, validation)

  • Single codebase for all 5 platforms (Windows, macOS, Linux, iOS, Android)

  • SQLCipher provides FIPS-capable AES-256 encryption at rest

  • Small binary size (~10-15 MB) compared to Electron (~150 MB)

  • No web server dependency — app is fully self-contained

  • Tauri 2.0 is stable (released October 2024) with mobile support

Cons:

  • Tauri mobile is newer than desktop — some plugins not yet ported

  • Requires platform-specific build toolchains (Xcode for iOS, Android SDK)

  • WebView rendering differs across platforms (may need testing matrix)

  • Tauri ecosystem smaller than React Native/Flutter

Option B: Progressive Web App (PWA)

Enhance the existing craig-web with service workers, IndexedDB for offline storage, and background sync API.

Pros:

  • Builds on existing craig-web codebase — no new application

  • No app store distribution needed (install from browser)

  • Familiar tech stack (htmx + Alpine.js)

  • Simpler CI/CD — just deploy the web app

Cons:

  • IndexedDB is not encrypted by default — must use Web Crypto API wrapper (not FIPS-validated)

  • iOS Safari has significant PWA limitations (no background sync, limited storage, no push notifications)

  • Service worker + htmx is an awkward combination (htmx returns HTML fragments, not JSON)

  • Offline capability limited to what service worker can cache — complex for dynamic case data

  • No access to device features (camera for documents, biometrics for login) without additional APIs

  • Browser may evict IndexedDB storage under pressure — data loss risk

Option C: React Native / Flutter

Cross-platform native framework with platform-specific UI rendering.

Pros:

  • Mature ecosystem with extensive plugin libraries

  • Native UI components (better UX than WebView)

  • Strong offline/storage libraries (WatermelonDB, Realm, Hive)

Cons:

  • Introduces JavaScript/Dart into a Rust-only codebase — significant context switch

  • Cannot share code with existing CRAIG Rust crates

  • React Native bridge overhead; Flutter requires Dart runtime

  • Larger bundle sizes

  • Separate build pipeline from CRAIG workspace

Option D: Dioxus (Pure Rust cross-platform)

Dioxus is a pure-Rust UI framework (React-like) that targets web, desktop, and mobile from a single main.rs. Version 0.7 (October 2025) introduced a WGPU-based native renderer (Blitz engine) alongside the existing WebView mode, Axum integration for fullstack, and hot-patching of Rust code at runtime across all platforms. Current stable: 0.7.3.

Architecture:

┌─────────────────────────────────────┐
│         Dioxus App (per-platform)   │
│  ┌───────────────────────────────┐  │
│  │   Dioxus RSX UI (Rust)       │  │  ← Pure Rust, no JS/HTML
│  │   Components, hooks, state   │  │
│  └───────────┬───────────────────┘  │
│              │ Direct Rust calls    │
│  ┌───────────▼───────────────────┐  │
│  │   Rust Core                   │  │
│  │   ├── craig-common (types)    │  │  ← Shared workspace crates
│  │   ├── craig-auth (JWT/OIDC)   │  │
│  │   ├── craig-reference (enums) │  │
│  │   ├── sync engine             │  │
│  │   └── SQLCipher local DB      │  │
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘

Pros:

  • 100% Rust — no JavaScript at all, entire UI in Rust RSX macros

  • Shares code with CRAIG workspace crates (same as Tauri)

  • Single main.rs for all platforms (web, desktop, iOS, Android)

  • No IPC boundary between frontend and backend (direct Rust function calls)

  • Smaller binary than Tauri (no bundled JS runtime concerns)

  • Hot-patching of Rust code at runtime (Subsecond) — edit code without losing app state, works on web, desktop, and mobile

  • WGPU-based native renderer (Blitz engine) as alternative to WebView — GPU-accelerated HTML/CSS layout

  • Built-in Axum integration for fullstack server functions

  • React-like component model familiar to frontend developers

Cons:

  • Pre-1.0 (v0.7.3 as of March 2026) — API still evolving between minor versions

  • Smaller ecosystem and community than Tauri (fewer plugins, less battle-tested)

  • Cannot reuse craig-web’s existing Askama/htmx/Alpine.js templates — UI must be rewritten in RSX

  • Native Swift/Kotlin FFI and SwiftUI/Kotlin widget support planned for 0.8 but not yet shipped

  • No built-in encrypted storage or SQLCipher integration (would need custom implementation)

  • No established patterns for offline/sync compared to Tauri’s plugin ecosystem

  • Native APIs (camera, location, storage, OAuth) on 0.8 roadmap — not yet available

Option E: Hybrid — PWA for desktop, Tauri/Dioxus for mobile

Use the enhanced PWA (Option B) for laptop/desktop access where connectivity is more reliable, and Tauri or Dioxus mobile apps for phone/tablet field work where offline is critical.

Pros:

  • Desktop users get the familiar web UI without installing anything

  • Mobile users get native-quality offline support

  • Reduces Tauri scope to mobile only (simpler)

Cons:

  • Two codebases for two experiences

  • Inconsistent feature parity between desktop and mobile

  • More testing surface

Decision

TBD

Recommendation

Option A (Tauri 2.0) is the strongest fit for CRAIG:

  1. Rust code sharing is the decisive advantage — craig-common, craig-auth, craig-reference, and validation logic compile directly into the client binary. No FFI, no serialization boundary, no reimplementation.

  2. SQLCipher provides real encryption at rest with AES-256, which is essential for child welfare data on portable devices. PWA IndexedDB encryption is a best-effort wrapper, not a FIPS-auditable solution.

  3. Single codebase for all 5 platforms — caseworkers on state-issued Windows laptops, personal iPhones, and Android tablets all use the same application.

  4. Small footprint — relevant for state-issued devices with limited storage and older hardware.

The main risk is Tauri mobile maturity. Mitigation: start with desktop (Windows/macOS) where Tauri is battle-tested, add mobile after validating the sync engine and local storage patterns.

Option D (Dioxus) is the most interesting alternative. Version 0.7 (October 2025) was a major leap — WGPU-based native rendering, Axum fullstack integration, and hot-patching across all platforms including mobile. It shares the same Rust code-sharing advantage and eliminates the JS frontend entirely. However, it remains pre-1.0 (v0.7.3), the 0.8 roadmap still lists critical mobile capabilities (native APIs for camera/location/storage, Swift/Kotlin FFI) as in-progress, and there are no built-in patterns for encrypted local storage. Dioxus should be re-evaluated when it reaches 1.0 — if its native API story and mobile ecosystem mature, it could be the better long-term choice since the entire application stack would be pure Rust. For now, Tauri’s stability (v2.x) and established plugin ecosystem (SQLCipher, stronghold, biometric) make it the safer bet for a system handling federally-protected child welfare data.

Offline Data Scope

Not all CRAIG data needs to be available offline. The client should sync a working set scoped to the caseworker’s assignment:

Data Offline Access Sync Direction

Assigned cases (summary)

Read

Pull only

Persons on assigned cases

Read

Pull only

Contact notes

Read + Write

Bidirectional

Safety assessments

Read + Write

Bidirectional

Case plan tasks

Read + Write

Bidirectional

Court orders

Read only

Pull only

Placement info

Read only

Pull only

Reference data (enums, admin units)

Read only

Pull only (infrequent)

Attachments/documents

Cached on demand

Pull only

Estimated local DB size per caseworker: 5-20 MB (excluding cached documents).

Conflict Resolution Strategy

Child welfare data conflicts are safety-critical — a missed update to a safety assessment could endanger a child.

Approach: field-level three-way merge with mandatory review for safety fields.

  1. Non-overlapping changes (different fields modified): auto-merge

  2. Same field modified, same value: auto-merge (no conflict)

  3. Same field modified, different values: flag for caseworker review

  4. Safety-critical fields (safety_assessment, immediate_danger, placement): always require manual review even if only one side changed

Display both versions side-by-side with diff highlighting. Caseworker picks the correct value or writes a new one.

Authentication Offline

  1. Initial login requires connectivity (Keycloak OIDC Authorization Code + PKCE)

  2. Access token + refresh token stored in OS keychain (Tauri tauri-plugin-stronghold or platform keychain)

  3. Offline: app validates cached JWT locally (check exp, verify signature against cached JWKS)

  4. Token refresh attempted on every connectivity change

  5. Hard expiry: if offline > 72 hours without refresh, require re-authentication (configurable per jurisdiction)

  6. Device lock: biometric or PIN required to unlock app (Tauri tauri-plugin-biometric)

Consequences

  • New workspace crate: craig-mobile (Tauri app) or craig-client

  • New shared crate: craig-sync (sync engine, conflict resolution, used by client and potentially server-side sync endpoints)

  • New API surface: sync endpoints on each service (GET /v1/{entity}?updated_since=, POST /v1/{entity}/sync)

  • Schema addition: version column on synced tables (monotonic counter incremented on write)

  • Build infrastructure: Tauri build pipeline (desktop CI via existing Rust CI, mobile via GitHub Actions or dedicated runner with Xcode/Android SDK)

  • Testing: new test category — sync conflict scenarios, offline-to-online transitions, encrypted DB round-trips

References

Edit this page · latest