ADR-009: Mobile & Offline-Capable Client Application
On this page
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:
-
Pull:
GET /v1/{entity}?updated_since={cursor}— server sends changed records -
Push: Client sends locally-modified records with their
version -
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.rsfor 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
Recommendation
Option A (Tauri 2.0) is the strongest fit for CRAIG:
-
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. -
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.
-
Single codebase for all 5 platforms — caseworkers on state-issued Windows laptops, personal iPhones, and Android tablets all use the same application.
-
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.
-
Non-overlapping changes (different fields modified): auto-merge
-
Same field modified, same value: auto-merge (no conflict)
-
Same field modified, different values: flag for caseworker review
-
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
-
Initial login requires connectivity (Keycloak OIDC Authorization Code + PKCE)
-
Access token + refresh token stored in OS keychain (Tauri
tauri-plugin-strongholdor platform keychain) -
Offline: app validates cached JWT locally (check
exp, verify signature against cached JWKS) -
Token refresh attempted on every connectivity change
-
Hard expiry: if offline > 72 hours without refresh, require re-authentication (configurable per jurisdiction)
-
Device lock: biometric or PIN required to unlock app (Tauri
tauri-plugin-biometric)
Consequences
-
New workspace crate:
craig-mobile(Tauri app) orcraig-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:
versioncolumn 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
-
Tauri 2.0 — Rust + web tech, desktop + mobile
-
Tauri 2.0 Stable Release — October 2024
-
Dioxus — Pure Rust cross-platform UI framework
-
Dioxus 0.7.0 Release — October 2025, WGPU native renderer, Axum fullstack, hot-patching
-
Dioxus 0.8 Roadmap — Native APIs, Swift/Kotlin FFI, SwiftUI/Kotlin widgets
-
SQLCipher — AES-256 encrypted SQLite
-
45 CFR § 1355.52 — CCWIS project requirements