OdontoX Mobile — Production-Ready Completion Spec
Date: 2026-05-08 Status: Approved for implementation References:2026-05-06-odontox-mobile-app-design.md (architecture baseline)
Target: Physical iOS device (TestFlight) + Google Play Internal Testing track
1. What This Spec Covers
The existing app has correct structure and all 19 server endpoints wired. This spec closes the gap between “screens exist and call APIs” and “production-ready app on a real device”. Six critical gaps + component library uplift + rule engine integration + notifications.2. New Dependencies
Install all before writing any code. All are compatible with Expo SDK 55 managed workflow via EAS Build.| Package | Version | Purpose |
|---|---|---|
@gorhom/bottom-sheet | ^5.1 | Booking modal, filter drawer, detail sheets. Uses existing reanimated + gesture-handler. |
react-native-pdf | ^6.7 | True in-app PDF rendering (iOS + Android). EAS Build only — not Expo Go. |
react-native-image-viewing | ^0.2 | X-ray / file image lightbox with pinch-zoom. |
react-native-toast-message | ^2.2 | In-app notification toasts when app is foregrounded. |
react-hook-form | ^7.54 | Booking form + patient create form validation. |
zod | ^3.23 | Schema validation for booking form (same version as server). |
expo-haptics | ~55.x | Tactile feedback on PIN pad digit press and biometric success. |
react-native-webview | ~14.x | Fallback PDF renderer on Android if react-native-pdf unavailable. |
app.json additions
3. Component → Library Map (no reinventing wheels)
| Feature | Library | Already installed? |
|---|---|---|
| Date selection (booking, calendar view) | react-native-calendars | ✅ |
| Day/week schedule grid | react-native-calendars Agenda | ✅ |
| Bar/line charts | react-native-gifted-charts | ✅ |
| Bottom sheet (booking, filters, detail) | @gorhom/bottom-sheet v5 | 🆕 |
| mPIN pad | Existing components/ui/PinPad.tsx + expo-haptics | ✅ + 🆕 |
| PDF viewer (in-app) | react-native-pdf | 🆕 |
| Image / X-ray lightbox | react-native-image-viewing | 🆕 |
| In-app toast / notification banner | react-native-toast-message | 🆕 |
| Booking form validation | react-hook-form + zod | 🆕 |
| Slot grid (available times) | Custom grid → /available-slots API | built here |
| Push notification token | expo-notifications (registered on login) | ✅ (unwired) |
| AppState (background/foreground) | React Native AppState built-in | built here |
| Biometric on resume | expo-local-authentication | ✅ (unwired) |
| Skeleton loading | react-native-gifted-charts has built-in + manual shimmer | ✅ |
4. Gap 1 — AppState Session Security
Problem: App goes to background → comes back → no re-auth. v2 decision: session flushes on close. Fix: AddAppState listener in app/_layout.tsx.
suspendSession()— clearsuserfrom memory but NOT from SecureStore (keeps jwt/pin)requireReauth()— setsneedsReauth: trueflag, AuthGate redirects to/auth/pin
needsReauth state → redirect to /auth/pin if true.
5. Gap 2 — Appointment Booking Flow
Problem: Patients can’t book. No booking UI anywhere. Architecture: Bottom sheet modal launched from patient appointments screen + home screen CTA.5a. Slot grid component (components/ui/SlotGrid.tsx)
Mirrors the web SlotPicker. Calls /protected/appointments/available-slots?date=YYYY-MM-DD.
Response shape (existing endpoint): { slots: string[], clinicClosed: bool, doctorOff: bool, clinicPhone: string|null }
5b. Booking bottom sheet (components/booking/BookingSheet.tsx)
Three steps inside a single @gorhom/bottom-sheet:
5c. Zod schema for booking form
5d. Rule engine on mobile (client side)
The server enforces all rules. On mobile:- Past dates:
minDate={today}on calendar — never shown - Available slots: only slots from
/available-slotsshown — booked/clinic-closed/doctor-off handled by API - Status: Patient submits
status: 'requested'— clinic approves. Same as web patient portal. - Errors from server: Surface rule engine
messagefield in a toast (react-native-toast-message)
{ code: 'PATIENT_DUPLICATE', message: '...' } → show error toast, keep sheet open.
5e. Cancellation (patient)
On appointment detail screen, add “Cancel appointment” button (only shown for upcoming, non-cancelled appointments).5f. Per-role booking access
| Role | Can create appointment | How |
|---|---|---|
| Patient | ✅ Request (status: requested) | BookingSheet from appointments + home CTA |
| Doctor | ✅ Schedule directly (status: scheduled) | New appointment from their patient detail |
| Receptionist | ✅ Schedule directly (status: scheduled) | New appointment from appointments list |
| Admin | ✅ Schedule directly (status: scheduled) | Same as receptionist |
6. Gap 3 — Past Appointments Display
Problem:past[] array fetched but never rendered.
Fix: Add a “Past” section to patient appointments screen.
7. Gap 4 — Push Notification Token Registration
Problem:expo-notifications configured but token never registered with server.
Fix: Register immediately after successful PIN verification / biometric auth in app/auth/pin.tsx.
expo-device, expo-application, expo-constants (all included in SDK 55 managed workflow).
Notification listeners in _layout.tsx
Notifications screen (all roles)
New screen/(patient)/notifications (and equivalent per role).
Calls GET /protected/notifications?limit=50 — endpoint exists. Renders list with:
- Notification title + message
- Relative time (e.g. “2 hours ago”)
- Unread dot
- Tap → deep link to
actionUrlif present - Swipe left to mark read →
PATCH /protected/notifications/:id/read
8. Gap 5 — Real Chart Data
Problem: Doctor + Admin weekly bar charts show hardcoded data. Fix: Both/stats/doctor and /stats/admin responses include weekly data. Check response shape and wire it.
If the endpoint doesn’t return weekly breakdown, add a weeklyAppointments field:
react-native-gifted-charts BarChart as-is, just swap data source.
9. Gap 6 — Records Drill-Down + PDF/File Viewer
Problem: Records list tappable items go nowhere. PDF downloads fail or open share sheet only.9a. PDF Viewer (components/viewer/PdfViewer.tsx)
Uses react-native-pdf:
react-native-pdf render fails, open URL in react-native-webview using
Google Docs viewer: https://docs.google.com/viewer?url=${encodeURIComponent(signedUrl)}.
9b. Image Viewer (components/viewer/ImageViewer.tsx)
Uses react-native-image-viewing:
9c. Records detail routing
From patient records list, tap routes based on item type:prescription→/(patient)/records/prescription/[id]— Prescription detail screenfile→/(patient)/records/file/[id]— PdfViewer or ImageViewer based on MIME typeclinical_note→/(patient)/records/note/[id]— Clinical note text display
9d. New server API needed
Prescriptions detail:GET /protected/prescriptions/:id — check if exists.
Patient files: GET /protected/patient-files/:id — returns signed URL.
10. Per-Role Content Relevance
Doctor Home
- “Today’s appointments” must filter by
doctorId = currentUser.id - API already supports:
GET /protected/appointments?date=today&doctorId={userId} - Doctor should ONLY see their own patients, not all clinic patients
- Patient list:
GET /protected/patients?doctorId={userId}(check if server supports filter — if not, filter client-side from response)
Receptionist Home
- Show today’s appointment queue sorted by
appointmentTime - Show “Check In” button on each appointment (PATCH status →
in_progress) - Show walk-in count, no-show count for today
Admin Home
- All clinic appointments (no doctor filter)
- Full KPI strip: revenue today, appointments today, checked-in, no-shows
- Weekly chart from real data (see Gap 5)
Patient Home
- Only their own data — server enforces this for all
/stats/patient/*endpoints - Next appointment CTA: if no upcoming → show “Book Appointment” button
11. Biometric Enrollment Toggle (Settings)
Problem: Settings shows biometric status read-only. Fix: Add toggle that actually enrolls:expo-local-authentication already in place.
12. Haptics on PIN Pad
Fix incomponents/ui/PinPad.tsx:
13. Toast Setup
Add<ToastMessage /> to root layout (above <Slot />):
Toast.show({ type: 'success' | 'error' | 'info', text1, text2 }).
14. EAS Build Config for react-native-pdf
react-native-pdf requires a custom dev client (not compatible with Expo Go). Update eas.json:
15. Testing Without Publishing
iOS — physical device
Android — physical device
E2E test checklist before submission
16. Deployment Pipeline
OTA updates (post-launch)
For JS-only changes (no new native modules):eas update --channel production — reaches devices in ~5 min with no store review.
17. Desktop-Only Decisions (Critical Cuts)
| Feature | Decision | Rationale |
|---|---|---|
| Dental chart write mode | Desktop only | Selecting individual teeth on 6” screen is unusable UX |
| Treatment plan authoring | Desktop only | Multi-service tables, cost estimation requires large viewport |
| DICOM workstation | Desktop only | Windowing, measurements, pixel-level zoom = desktop |
| Staff/user management | Desktop only | Security-sensitive configuration |
| Clinic settings | Desktop only | Operating hours, service catalog, plan config |
| Payroll | Desktop only | |
| Report builder | Dashboard summary only on mobile | Filter/export builder = desktop |
| Lab case creation | Desktop only (view status on mobile) | Complex attachments and workflows |
- Receptionist invoice creation (chair-side payment is real workflow)
- Doctor quick clinical note (post-consult at chair-side)
- Doctor new prescription (30-second Rx before next patient)
18. Server-Side Additions Required
| Addition | Endpoint | Status |
|---|---|---|
| Push token registration | POST /protected/user-devices | ✅ exists |
| Notifications list | GET /protected/notifications | ✅ exists |
| Available slots | GET /protected/appointments/available-slots | ✅ exists |
| Patient files signed URL | GET /protected/patient-files/:id | verify exists |
| Prescription detail | GET /protected/prescriptions/:id | verify exists |
| Mark notification read | PATCH /protected/notifications/:id/read | verify exists |
| Doctor-filtered patient list | GET /protected/patients?doctorId=X | verify filter works |

