OdontoX Mobile App — Phase 1 Design Spec
Date: 2026-05-06Status: Draft
Scope: Phase 1 — end-to-end functional, essentials only
Roles: Patient · Doctor · Admin · Receptionist (Superadmin: Phase 2)
1. Goals
Build a single Expo SDK 55 binary (iOS + Android) that mirrors the role-based access model ofgo.odontox.io at a lean Phase 1 scope. Zero new backend infrastructure — the app runs entirely against the existing Hono/Cloudflare Workers API with JWT auth.
Non-goals (Phase 1):
- Dental chart
- Inventory, Lab, Payroll, Insurance
- AI/Ruby features
- Bridge (imaging)
- IPD
- Superadmin
- Audit logs
2. Tech Stack
| Layer | Choice | Reason |
|---|---|---|
| Framework | Expo SDK 55 (RN 0.83, React 19.2) | Cross-platform, lowest cost, OTA updates |
| Architecture | New Architecture only (Fabric + JSI) | Mandatory in SDK 55 — no legacy arch |
| Router | Expo Router v7 + native-tabs | Platform-native tab bars (UITabBarController on iOS, NavigationBar on Android) |
| Native UI | @expo/ui SwiftUI (iOS) + Jetpack Compose beta (Android) | True platform-native components, not JS shimmed |
| Styling | NativeWind v4 | Tailwind syntax for shared layout |
| Auth storage | expo-secure-store | Keychain (iOS) / Keystore (Android) |
| Biometrics | expo-local-authentication | Face ID / Touch ID / Biometric Prompt |
| Encryption | expo-crypto (AES-GCM) | Client-side PHI field encryption, no native module needed |
| Push | expo-notifications + EAS | APNs + FCM via single API |
| HTTP client | fetch + React Query v5 | Cache, mutations, background refetch |
| State | Zustand | Auth session only |
| PDF/Files | expo-file-system + expo-sharing | Signed URL download → share sheet |
| iPad layout | SplitView (Expo Router v7, experimental) | Native sidebar + detail pane |
| Build & OTA | EAS Build + EAS Update | 75% smaller OTA via Hermes bytecode diff |
| Backend | Existing Hono/CF Workers (/api/v1/*) | Zero changes to server |
3. System Architecture
4. Auth Flow
Rules:- JWT stored in SecureStore, never AsyncStorage
- mPIN validated locally (no network round-trip)
- Biometric prompt wraps mPIN path — same code
- Session flushed on app backgrounded > 15 min (configurable)
- Concurrent session detection: server returns
ConcurrentSession401 → force logout - Refresh token rotation on each refresh call
5. Mobile Permission Engine
A lightweight layer that gates tabs, screens, and action buttons on mobile. Separate from (but derived from) the web permission system.5a. New DB table (one migration)
5b. API endpoint (new, minimal)
5c. Web admin control
Existing Settings → Staff page gains a “Mobile Access” toggle panel per role. Superadmin can override per-clinic from their clinic detail view. No new pages — extends existing settings UI on the web app only. The mobile app shows a read-only view of current mobile permissions for the admin to reference.5d. App usage
can() returns false.
6. Module Matrix — Phase 1
| Module | Patient | Doctor | Admin | Receptionist |
|---|---|---|---|---|
| Appointments — view, create, status | own only | ✅ | ✅ | ✅ |
| Patients — list, detail, create | own profile | ✅ | ✅ | ✅ |
| Clinical Notes | — | ✅ | ✅ | — |
| Treatment Plans — view + create | view own | ✅ | ✅ | — |
| Prescriptions — view + create | view own | ✅ | ✅ | — |
| Medical Records / Files | view own | ✅ | ✅ | — |
| Vital Signs | view own | ✅ | ✅ | — |
| Billing — Invoices & Receipts | view own | — | ✅ | ✅ |
| Payments — record + view | — | — | ✅ | ✅ |
| Dashboard Stats | — | basic | full | — |
| Messaging (patient comms) | ✅ | ✅ | ✅ | ✅ |
| Notifications | ✅ | ✅ | ✅ | ✅ |
| Profile & Security | ✅ | ✅ | ✅ | ✅ |
mobile_role_permissions — admin can disable any per-clinic from web.
7. Navigation Structure
iOS: NativeUITabBarController via Expo Router v7 native-tabs — SF Symbols, zero JS shimmingAndroid: Material 3
NavigationBar via Jetpack Compose beta — dynamic color themingiPad:
SplitView (Expo Router v7) — native sidebar on left, detail on rightTransitions: Apple Zoom Transition (shared-element) enabled by default on iOS
8. Screen Inventory
8a. Shared / Auth
| Screen | Route |
|---|---|
| Login | /auth/login |
| Set mPIN | /auth/set-pin |
| Enter mPIN | /auth/pin |
| Select Clinic (multi-clinic) | /auth/select-clinic |
| Notification permission | /auth/notifications |
8b. Patient
| Screen | Route |
|---|---|
| Home (next appt, quick links) | /(patient)/ |
| Appointments list | /(patient)/appointments/ |
| Book appointment | /(patient)/appointments/book |
| Appointment detail | /(patient)/appointments/[id] |
| Medical records list | /(patient)/records/ |
| Treatment plan detail | /(patient)/records/treatment/[id] |
| Prescription detail | /(patient)/records/prescription/[id] |
| File viewer (PDF/image) | /(patient)/records/file/[id] |
| Invoice list | /(patient)/bills/ |
| Invoice detail | /(patient)/bills/[id] |
| Chat inbox | /(patient)/chat/ |
| Chat thread | /(patient)/chat/[id] |
| Profile | /(patient)/profile |
| Notifications | /(patient)/notifications |
8c. Doctor
| Screen | Route |
|---|---|
| Home (today’s schedule, stats) | /(doctor)/ |
| Appointment list | /(doctor)/appointments/ |
| Appointment detail + actions | /(doctor)/appointments/[id] |
| Patient search | /(doctor)/patients/ |
| Patient profile | /(doctor)/patients/[id] |
| Clinical notes list | /(doctor)/patients/[id]/notes |
| Add/edit note | /(doctor)/patients/[id]/notes/[noteId] |
| Treatment plans | /(doctor)/patients/[id]/treatment |
| Add treatment plan | /(doctor)/patients/[id]/treatment/new |
| Prescriptions | /(doctor)/patients/[id]/prescriptions |
| New prescription | /(doctor)/patients/[id]/prescriptions/new |
| Patient files | /(doctor)/patients/[id]/files |
| Vital signs | /(doctor)/patients/[id]/vitals |
| Chat inbox | /(doctor)/chat/ |
| Chat thread | /(doctor)/chat/[id] |
| Profile + settings | /(doctor)/profile |
| Notifications | /(doctor)/notifications |
8d. Admin
All doctor screens (same routes under/(admin)/) plus:
| Screen | Route |
|---|---|
| Stats dashboard | /(admin)/ |
| Invoice list | /(admin)/finance/invoices |
| Invoice detail | /(admin)/finance/invoices/[id] |
| Create invoice | /(admin)/finance/invoices/new |
| Receipt list | /(admin)/finance/receipts |
| Record payment | /(admin)/finance/payment/new |
| Mobile permissions view (read-only) | /(admin)/settings/mobile-permissions |
8e. Receptionist
| Screen | Route |
|---|---|
| Home (check-in queue) | /(receptionist)/ |
| Appointment list | /(receptionist)/appointments/ |
| Appointment detail | /(receptionist)/appointments/[id] |
| New appointment | /(receptionist)/appointments/new |
| Patient search | /(receptionist)/patients/ |
| Patient profile (read) | /(receptionist)/patients/[id] |
| New patient | /(receptionist)/patients/new |
| Invoice list | /(receptionist)/finance/invoices |
| Create invoice | /(receptionist)/finance/invoices/new |
| Record payment | /(receptionist)/finance/payment/new |
| Chat inbox | /(receptionist)/chat/ |
| Chat thread | /(receptionist)/chat/[id] |
| Profile | /(receptionist)/profile |
| Notifications | /(receptionist)/notifications |
9. API Integration
All calls use the existinghttps://api.odontox.io/api/v1/ base. No new endpoints except GET /api/v1/mobile/permissions and the seeding hook on clinic creation.
Error handling:
401→ attempt silent refresh → if fails, force logout to/auth/pin403→ show “Access denied” inline, no crashConcurrentSessioncode → force logout with message- Network offline → React Query stale cache shown with banner
10. Push Notifications
- On login: register
expo-notificationstoken →POST /api/v1/user-devices(existing endpoint) - Notification types Phase 1: appointment reminders, new message, payment received (patient), appointment status change
- Deep link on tap:
odontox://appointments/[id],odontox://chat/[id]
11. File & PDF Handling
Documents (prescriptions, invoices, records) are served from existing R2 via signed URLs from the API. Flow:- App calls existing file endpoint → receives signed URL (TTL 5 min)
expo-file-systemdownloads to temp cache- Rendered in-app via a WebView-based PDF renderer (
expo/use-dom+ PDF.js) for inline viewing, or passed toexpo-sharingfor system share sheet - Temp files purged on app background
12. Security
| Concern | Mitigation |
|---|---|
| Token storage | expo-secure-store only (Keychain/Keystore) |
| Screenshot prevention | FLAG_SECURE on Android via native module |
| Session idle | Auto-lock after 15 min background, configurable |
| Biometric re-auth | Required on return from background |
| HTTPS only | ATS enforced (iOS), cleartext blocked (Android) |
| mPIN brute force | 5 attempts → force full re-login |
| Concurrent sessions | Server-enforced, ConcurrentSession 401 |
| PHI in logs | No patient data in console/Crashlytics |
| PHI field encryption | expo-crypto AES-GCM for sensitive fields in local cache |
13. Design System
- iOS:
@expo/uiSwiftUI components — Button, TextField, Picker rendered as real SwiftUI views. SF Symbols throughout. NativeUITabBarControllervianative-tabs. - Android:
@expo/uiJetpack Compose (beta) — dynamic Material 3 color theming synced to device wallpaper. Material icons. - Shared: NativeWind v4 (Tailwind syntax for layout), brand color
#5048E5(indigo), adaptive dark/light mode - Component library: Built fresh — no dependency on legacy Atelier system
- Industry pattern: Bottom tab navigation is the universal dental DMS mobile standard (Curve Dental, CareStack, NexHealth all follow this). Clinical charting intentionally desktop-only (consistent with market leaders).
14. Project Structure
15. Backend Changes (minimal)
| Change | Scope |
|---|---|
mobile_role_permissions table | New migration, ~10 lines |
GET /api/v1/mobile/permissions | New route, ~30 lines |
| Seed defaults on clinic creation | Hook in existing clinic create handler |
| Web admin: Mobile toggles in Settings | Extends existing staff settings UI |
POST /api/v1/user-devices | Already exists — no change |
16. Build & Distribution
| Stage | Tooling |
|---|---|
| Local dev | expo start + Expo Go / Dev Client |
| CI builds | EAS Build (cloud) |
| TestFlight (iOS) | EAS Submit |
| Play Store (Android) | EAS Submit |
| OTA updates | EAS Update (JS-only changes, no store review) |
| Env secrets | EAS Secrets (never in repo) |
17. Phase 2 Backlog
- Dental Chart (interactive)
- Inventory
- Lab Cases
- AI/Ruby features
- Bridge imaging viewer
- Superadmin shell
- IPD
- Payroll & Insurance
- Offline mode (full sync)

