OdontoX Mobile App — Foundation Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
Goal: Scaffold odontox-app/ (Expo SDK 55) with complete auth (email/password → mPIN → biometric), role-based navigation shells for all 4 roles (patient, doctor, admin, receptionist), and the mobile permission engine (DB + API + app hook).
Architecture: Expo Router v7 file-based routing with native-tabs layout groups per role. JWT lives in expo-secure-store (Keychain/Keystore); mPIN is validated locally as SHA-256 hash; biometrics wrap the PIN gate. Role is stored in Zustand after PIN entry and drives which layout group mounts. One new backend endpoint (POST /api/v1/auth/mobile-signin) skips Turnstile. One new protected endpoint (GET /api/v1/protected/mobile/permissions) returns the role’s enabled module set, seeded per-clinic from a new mobile_role_permissions table.
Tech Stack: Expo SDK 55, RN 0.83, Expo Router v7, NativeWind v4, Zustand, expo-secure-store, expo-local-authentication, expo-crypto, React Query v5, jest-expo, @testing-library/react-native, Hono (Cloudflare Workers), Drizzle ORM, Neon Postgres
File Map
New — odontox-app/ (repo root)
Modified — server/
Modified — ui/
Task 1: Scaffold Expo SDK 55 project
Files: Createodontox-app/ at repo root with app.json, package.json, tsconfig.json, babel.config.js, metro.config.js, tailwind.config.js, global.css, jest.config.js
- Step 1: Create the project
/Users/ssh/Documents/Beta-App/odontoX:
✅ Your project is ready!
- Step 2: Install all dependencies
- Step 3: Write
app.json
- Step 4: Write
tsconfig.json
- Step 5: Write
babel.config.js
- Step 6: Write
metro.config.js
- Step 7: Write
tailwind.config.js
- Step 8: Write
global.css
- Step 9: Write
jest.config.js
- Step 10: Verify build config compiles
- Step 11: Commit
Task 2: Brand theme and shared UI primitives
Files:-
Create:
odontox-app/constants/theme.ts -
Create:
odontox-app/components/ui/Button.tsx -
Create:
odontox-app/components/ui/TextInput.tsx -
Step 1: Write
constants/theme.ts
- Step 2: Write
components/ui/Button.tsx
- Step 3: Write
components/ui/TextInput.tsx
- Step 4: Commit
Task 3: Secure storage library
Files:-
Create:
odontox-app/lib/secure-storage.ts -
Create:
odontox-app/__tests__/lib/secure-storage.test.ts - Step 1: Write failing tests
odontox-app/__tests__/lib/secure-storage.test.ts:
- Step 2: Run tests — verify they fail
FAIL — Cannot find module ’@/lib/secure-storage’
- Step 3: Implement
lib/secure-storage.ts
- Step 4: Run tests — verify they pass
PASS __tests__/lib/secure-storage.test.ts
- Step 5: Commit
Task 4: mPIN hash utility
Files:-
Create:
odontox-app/lib/pin.ts -
Create:
odontox-app/__tests__/lib/pin.test.ts - Step 1: Write failing tests
odontox-app/__tests__/lib/pin.test.ts:
- Step 2: Run tests — verify they fail
FAIL — Cannot find module ’@/lib/pin’
- Step 3: Implement
lib/pin.ts
- Step 4: Run tests — verify they pass
PASS __tests__/lib/pin.test.ts
- Step 5: Commit
Task 5: API client
Files:-
Create:
odontox-app/lib/api.ts -
Create:
odontox-app/__tests__/lib/api.test.ts - Step 1: Write failing tests
odontox-app/__tests__/lib/api.test.ts:
- Step 2: Run tests — verify they fail
FAIL
- Step 3: Implement
lib/api.ts
- Step 4: Run tests — verify they pass
PASS __tests__/lib/api.test.ts
- Step 5: Commit
Task 6: Zustand auth store
Files:-
Create:
odontox-app/store/auth.ts -
Create:
odontox-app/__tests__/store/auth.test.ts - Step 1: Define the User type
odontox-app/store/auth.ts (write full file next step, but define the type first for reference):
- Step 2: Write failing tests
odontox-app/__tests__/store/auth.test.ts:
- Step 3: Run tests — verify they fail
FAIL
- Step 4: Implement
store/auth.ts
- Step 5: Run tests — verify they pass
PASS __tests__/store/auth.test.ts
- Step 6: Commit
Task 7: PinPad UI component
Files:-
Create:
odontox-app/components/ui/PinPad.tsx -
Step 1: Write
components/ui/PinPad.tsx
- Step 2: Commit
Task 8: Auth screens (login, set-pin, pin)
Files:-
Create:
odontox-app/app/auth/_layout.tsx -
Create:
odontox-app/app/auth/login.tsx -
Create:
odontox-app/app/auth/set-pin.tsx -
Create:
odontox-app/app/auth/pin.tsx -
Step 1: Write
app/auth/_layout.tsx
- Step 2: Write
app/auth/login.tsx
- Step 3: Write
app/auth/set-pin.tsx
- Step 4: Write
app/auth/pin.tsx
- Step 5: Commit
Task 9: Root layout auth gate + role shells
Files:-
Create:
odontox-app/app/_layout.tsx -
Create:
odontox-app/app/+not-found.tsx -
Create:
odontox-app/app/(patient)/_layout.tsx -
Create:
odontox-app/app/(patient)/index.tsx -
Create:
odontox-app/app/(doctor)/_layout.tsx -
Create:
odontox-app/app/(doctor)/index.tsx -
Create:
odontox-app/app/(admin)/_layout.tsx -
Create:
odontox-app/app/(admin)/index.tsx -
Create:
odontox-app/app/(receptionist)/_layout.tsx -
Create:
odontox-app/app/(receptionist)/index.tsx -
Step 1: Write
app/_layout.tsx
- Step 2: Write
app/+not-found.tsx
- Step 3: Write
app/(patient)/_layout.tsx
- Step 4: Write
app/(patient)/index.tsx
- Step 5: Write
app/(doctor)/_layout.tsx
- Step 6: Write
app/(doctor)/index.tsx
- Step 7: Write
app/(admin)/_layout.tsx
- Step 8: Write
app/(admin)/index.tsx
- Step 9: Write
app/(receptionist)/_layout.tsx
- Step 10: Write
app/(receptionist)/index.tsx
- Step 11: Start dev server and verify each role shell navigates correctly
- Step 12: Commit
Task 10: Backend — mobile-signin endpoint
Files:-
Modify:
server/src/routes/auth.ts— addPOST /mobile-signin -
Step 1: Add the mobile-signin route to
server/src/routes/auth.ts
const signinRateLimit = ... line (around line 38) and the auth.post(‘/signin’, …) definition. Add this new route immediately after the existing auth.post('/signin', ...) handler ends (around line 400+). To find the correct insertion point:
auth.post('/signin', ...) closing });:
- Step 2: Verify TypeScript compiles
- Step 3: Commit
Task 11: Backend — mobile permission engine
Files:-
Create:
server/drizzle/0029_mobile_role_permissions.sql -
Create:
server/src/schema/mobile_role_permissions.ts -
Modify:
server/src/schema/index.ts— export new schema -
Create:
server/src/routes/mobile.ts -
Modify:
server/src/api.ts— register mobile route under protected -
Step 1: Write migration
server/drizzle/0029_mobile_role_permissions.sql
- Step 2: Write Drizzle schema
server/src/schema/mobile_role_permissions.ts
- Step 3: Export from schema index
server/src/schema/index.ts and add:
- Step 4: Write route
server/src/routes/mobile.ts
- Step 5: Register route in
server/src/api.ts
protectedRoutes section (around api.route('/protected', protectedRoutes)). Add the import at the top of api.ts with the other imports:
protectedRoutes.route(...) calls are and add:
- Step 6: Verify TypeScript compiles
- Step 7: Commit
Task 12: Mobile permission hook
Files:-
Create:
odontox-app/lib/permissions.ts -
Create:
odontox-app/hooks/usePermissions.ts -
Create:
odontox-app/__tests__/lib/permissions.test.ts - Step 1: Write failing tests
odontox-app/__tests__/lib/permissions.test.ts:
- Step 2: Run tests — verify fail
FAIL
- Step 3: Implement
lib/permissions.ts
- Step 4: Implement
hooks/usePermissions.ts
- Step 5: Run tests — verify pass
PASS __tests__/lib/permissions.test.ts
- Step 6: Commit
Task 13: Fetch permissions after login
Files:-
Modify:
odontox-app/app/auth/login.tsx -
Modify:
odontox-app/app/auth/pin.tsx -
Step 1: Call
fetchAndCachePermissionsin login.tsx after successful login
app/auth/login.tsx, import and call fetchAndCachePermissions after await login(...):
- Step 2: Call
fetchAndCachePermissionsin pin.tsx after biometric/PIN success
app/auth/pin.tsx, update navigateToRole:
- Step 3: Commit
Task 14: Web settings — MobilePermissionsPanel
Files:-
Create:
ui/src/components/settings/MobilePermissionsPanel.tsx - Step 1: Find where staff settings renders tabs
<MobilePermissionsPanel /> call there in Step 3.
- Step 2: Write
ui/src/components/settings/MobilePermissionsPanel.tsx
- Step 3: Add GET and PATCH endpoints to
server/src/routes/mobile.tsfor the web panel
server/src/routes/mobile.ts:
getTxDb at the top of mobile.ts:
- Step 4: Add the panel to the Staff Settings page
- Step 5: TypeScript check both packages
- Step 6: Commit
Task 15: EAS configuration
Files:-
Create:
odontox-app/eas.json -
Modify:
odontox-app/app.json— addextraenv block -
Step 1: Write
odontox-app/eas.json
- Step 2: Update
lib/api.tsto use env variable for BASE_URL
BASE_URL line in odontox-app/lib/api.ts:
- Step 3: Run full test suite
- Step 4: Commit
Self-Review
Spec coverage check:| Spec section | Covered by task |
|---|---|
| One binary, role-based | Task 9 (layout groups) |
| Expo SDK 55 scaffold | Task 1 |
| NativeWind v4 | Task 1 (tailwind.config, babel, metro) |
| Expo Router v7 | Task 1 (app.json, app/_layout.tsx) |
| JWT in SecureStore | Task 3, 5, 6 |
| mPIN hash + verify | Task 4 |
| Biometric auth | Task 8 (pin.tsx) |
| Auth gate (root layout) | Task 9 |
| Login screen | Task 8 |
| Set PIN screen | Task 8 |
| PIN entry screen | Task 8 |
| mobile_role_permissions table | Task 11 |
| GET /mobile/permissions | Task 11 |
| POST /auth/mobile-signin | Task 10 |
| Permission hook (can()) | Task 12 |
| Fetch permissions after login | Task 13 |
| Web admin mobile toggles | Task 14 |
| EAS config | Task 15 |
| Patient tab shell | Task 9 |
| Doctor tab shell | Task 9 |
| Admin tab shell | Task 9 |
| Receptionist tab shell | Task 9 |
| Concurrent session detection | Handled by existing server (ConcurrentSession 401 → SESSION_EXPIRED in api.ts Task 5) |
| Session idle lock | Not in Plan 1 — Plan 2 |
| Push registration | Not in Plan 1 — Plan 2 |
eas.json has "ascAppId": "TBD" and "appleTeamId": "TBD" — these require actual App Store Connect registration and cannot be filled in yet. Flagged intentionally.
Type consistency:
AppUserdefined instore/auth.ts, imported in all screens — consistentapi.get<T>/api.post<T>generic pattern used in Task 5 and Task 8 — consistentfetchAndCachePermissionsreturnsstring[]in Task 12 and consumed in Task 13 — consistentmobileRolePermissionsschema exported from schema index and imported inmobile.ts— consistent

