Bilko Mobile Architecture Bilko Mobile Companion Architecture Product: Bilko Mobile Companion Markets: HR / RS / BA Status: Direction approved — iPhone + Samsung/Android native companion; implementation details remain draft Date: 2026-05-25 Updated: 2026-06-04 Related PRD: docs/mobile/MOBILE-PRD.md 1. Architecture Decision Approved framework: React Native with Expo for iPhone and Samsung/Android delivery. Rationale: Bilko already has a TypeScript/React-oriented web product, so React Native maximizes language, tooling, and design-system reuse. Expo provides faster iOS/Android delivery, camera/file-system/push/secure-storage primitives, OTA update options, and a simpler CI path. The app does not require heavy native computation in MVP; regulated tax/e-invoice logic remains server-side. Native Swift/Kotlin would increase delivery cost and split the codebase too early. Decision status: final for Bilko mobile direction . The older PWA-for-MVP note in /Users/makinja/system/specs/bilko-tech-stack.md is superseded for mobile app work; responsive/PWA web may still exist as web capability, but it is not the mobile companion app delivery path. 2. Core Principles Backend is source of truth for accounting, tax, invoice numbering, e-invoice submission, audit logs, and permissions. Mobile is an action companion , optimized for capture, approvals, alerts, and simple workflows. Offline-first only where valuable : drafts, queued uploads, cached dashboard, and pending approvals. Country behavior is configuration/plugin-driven , not hardcoded screen forks. No regulated secrets or certificates in the mobile app unless a future legal/security review explicitly approves it. Security and auditability are product features , not afterthoughts. 3. Proposed App Structure apps/mobile/ ├── app/ # Expo Router routes │ ├── (auth)/ │ ├── (tabs)/ │ │ ├── today/ │ │ ├── inbox/ │ │ ├── invoices/ │ │ ├── expenses/ │ │ └── more/ │ ├── invoice/[id].tsx │ ├── expense/[id].tsx │ └── document/[id].tsx ├── src/ │ ├── api/ # typed API client + endpoint modules │ ├── auth/ # session, refresh, company switcher │ ├── components/ # UI primitives and feature components │ ├── country/ # country metadata and formatting adapters │ ├── db/ # local SQLite schema and repositories │ ├── features/ # dashboard, inbox, invoices, expenses │ ├── notifications/ # push registration, handlers, deep links │ ├── security/ # secure storage, app lock, logger scrubber │ ├── sync/ # queue, retry, conflict handling │ ├── types/ # shared DTOs and local models │ └── utils/ ├── assets/ ├── app.config.ts └── package.json 4. Navigation Recommended navigation: Expo Router with authenticated tab shell. Root ├── Auth Stack │ ├── Login │ ├── Forgot Password │ └── MFA / Device Verification (future) └── App Tabs ├── Today ├── Inbox ├── Invoices ├── Expenses └── More Deep links: bilko://invoice/{id} bilko://expense/{id} bilko://document/{id} bilko://approval/{id} bilko://compliance/{country}/{type} Universal links can be added later when production mobile domains are defined. 5. Country Plugin Layer The mobile app should request country/company metadata from the backend after login. Example mobile country config: type BilkoCountry = 'HR' | 'RS' | 'BA' type CountryMobileConfig = { country: BilkoCountry currency: 'EUR' | 'RSD' | 'BAM' vatLabel: 'PDV' | 'VAT' invoiceStatusProvider?: 'SEF' | 'HR_FISK_ERACUN' | null supportsRegulatedEInvoiceSubmission: boolean supportsMobileSimpleInvoice: boolean complianceReminderTypes: string[] } Initial defaults: Country Currency Regulated status in mobile Submission in mobile HR EUR eRačun/HR-FISK readiness/status when backend supports it Backend-only, mobile initiates only approved backend workflow RS RSD SEF status and failure alerts Backend-only, mobile initiates only approved backend workflow BA BAM VAT/PDV reminders and document/accountant workflow No e-invoice submission until official specs are stable 6. API Boundary Mobile should eventually consume a stable API/BFF layer rather than directly reproducing web internals. Phase 1 decision: use existing /api/v1/* endpoints where they are already live, plus a small Phase 0 auth bridge for Microsoft Entra External ID. Do not build the full /mobile/* BFF before the first iPhone/Android internal build. Phase 2+ target endpoints: GET /mobile/bootstrap GET /mobile/dashboard GET /mobile/country-config GET /mobile/invoices GET /mobile/invoices/{id} POST /mobile/invoices/{id}/reminders POST /mobile/invoices/{id}/mark-paid POST /mobile/invoice-drafts GET /mobile/expenses GET /mobile/expenses/{id} POST /mobile/expense-drafts POST /mobile/approvals/{id}/approve POST /mobile/approvals/{id}/reject POST /mobile/documents/upload-url POST /mobile/documents/complete-upload GET /mobile/sync/pull?since={cursor} POST /mobile/sync/push POST /mobile/push-token DELETE /mobile/push-token/{id} All mutation endpoints must: enforce server-side permissions; be idempotent where retry is expected; emit audit log entries; return server-generated status/version/cursor. 7. Local Storage Phase 1 storage: Expo SecureStore / native Keychain-Keystore for Entra/Bilko session tokens. In-memory Zustand state for dashboard/list data. No SQLite offline queue in Phase 1 ; failed uploads show retry UI and must not claim offline support. Phase 2+ target storage: SQLite for structured local cache and sync queue. Expo FileSystem for queued attachments. Expo SecureStore / native Keychain-Keystore for local encryption key. Local data categories: Data Storage Offline? Notes Access token / ID token SecureStore Yes short-lived; issued via Entra/Bilko auth bridge Refresh token/session token SecureStore Yes rotation required; never AsyncStorage Company/session metadata SQLite Yes non-secret but sensitive Dashboard cache SQLite Read-only stale indicator required Invoice/expense list cache SQLite Read-only limited retention Draft expense/invoice SQLite Yes sync queue item Captured document file FileSystem Yes encrypted/retention-managed Push token Server + memory/cache No revocable 8. Offline and Sync Strategy Phase 1 offline scope: no durable offline queue; no SQLite cache; show clear network error and preserve the current in-memory capture state where possible. Phase 2+ offline target scope: view last dashboard/list cache; create receipt/expense draft; attach photo/PDF; create simple invoice draft if product approves Phase 4 scope; queue approval action only if the record version is known. Sync queue item: type SyncQueueItem = { id: string entityType: 'expenseDraft' | 'invoiceDraft' | 'document' | 'approval' entityId: string operation: 'create' | 'update' | 'upload' | 'approve' | 'reject' payload: unknown fileUri?: string baseVersion?: string retryCount: number createdAt: string } Retry rules: Drain queue on app foreground and network restore. Use FIFO order per entity. Use exponential backoff. Preserve item after failure; never silently discard user-captured document. Move to user-visible “needs attention” state after max retries. Conflict rules: Dashboard/list cache: server wins. Drafts not submitted: client can edit locally. Submitted records: server version wins; user sees conflict/failure if stale. Approvals: require current server version or explicit server conflict response. 9. Push Notification Design Provider options: Expo Notifications for fastest Phase 3 implementation. Direct FCM/APNs if advanced routing/compliance requirements require it later. Third-party service only if product/ops wants non-engineering campaign controls. Phase 1: no push permission prompt and no push-token registration unless a backend device-token endpoint is explicitly added and approved. Avoid asking users for notification permission before the product can deliver useful notifications. Transactional notification types: Type Trigger Deep link approval.requested Expense/invoice needs user approval approval detail document.requested Accountant requests missing document inbox task invoice.overdue Invoice crosses overdue threshold invoice detail tax.deadline VAT/PDV deadline approaching compliance card einvoice.accepted SEF/eRačun provider accepted invoice invoice detail einvoice.rejected Provider rejected/failed invoice invoice detail certificate.expiring HR/country integration certificate warning settings/compliance Rules: No sensitive invoice/customer amounts in lock-screen notification body by default. Deep link payload contains IDs only, not PII. Respect notification preferences and legal consent requirements. 10. Security Architecture Required baseline: Microsoft Entra External ID for customer identity where supported by ALAI auth standard; OIDC Authorization Code + PKCE for mobile login; tokens only in Keychain/Keystore-backed secure storage; refresh/session token rotation; biometric/PIN app unlock after inactivity; TLS-only API traffic; no hardcoded secrets/API keys that grant backend access; local database encryption where feasible; PII and token scrubbing from logs/crash reports; remote logout/session revocation; RBAC enforced server-side for every mutation. Sensitive screens: company financial summary; invoice detail; expense detail with attachment; settings/security; regulatory/certificate status. Recommended controls: hide sensitive content in app switcher where supported; Android screen-capture prevention for sensitive screens if UX accepts it; jailbreak/root detection as warning/risk signal, not a sole control for MVP. 11. Analytics and Observability Track product events without PII: mobile_login_success mobile_dashboard_viewed mobile_receipt_captured mobile_draft_synced mobile_sync_failed mobile_approval_approved mobile_invoice_shared mobile_notification_opened Operational telemetry: crash-free sessions; API error rate; upload retry count; sync queue depth; dead-letter count; push delivery/open rate if provider supports it. 12. Build Variants Variant Bundle ID example API Analytics Push Dev no.alai.bilko.dev local/dev off sandbox Staging no.alai.bilko.staging staging limited sandbox Production no.alai.bilko production consent-based production Secrets and environment values must be injected via CI or secure config, not committed. 13. Testing Strategy Required test layers: unit tests for country formatting and sync queue logic; API contract tests for mobile BFF endpoints; component tests for key forms and offline states; device/simulator smoke tests for iOS and Android; E2E tests for login, capture draft, sync, approval, notification deep link; security review before production release. Critical acceptance tests: Receipt captured offline survives app restart and syncs after network restore. HR/RS/BA company displays correct currency and country labels. Push notification opens correct invoice/expense/document. Stale approval action receives conflict and does not silently approve wrong version. Token is not stored in AsyncStorage/MMKV/plain SQLite. 14. Implementation Phases Phase 0 — API and Design Foundations define mobile BFF contracts; finalize React Native/Expo decision; define country config payload; define design tokens reuse from Bilko web. Phase 1 — Read-Only App login/session; company switcher; Today dashboard; invoice/expense lists; cached read-only state. Phase 2 — Capture and Upload Queue camera capture; file import/share extension path; expense draft; attachment upload queue; sync failure recovery. Phase 3 — Notifications and Approvals push registration; deep links; approval actions; VAT/PDV reminders. Phase 4 — Simple Invoice Actions invoice draft create; backend PDF share; mark paid/payment reminder; audit trail verification. Phase 5 — Regulatory Status Views RS SEF status; HR eRačun/HR-FISK readiness/status; BA only after stable official requirements. 15. Open Technical Questions Does Bilko currently expose mobile-safe BFF endpoints, or should apps/api add /mobile/* ? Which auth/session mechanism is canonical for mobile refresh token rotation? Which local DB encryption approach is acceptable under Expo constraints? Which OCR provider meets cost, language, and privacy requirements for HR/RS/BA receipts? Should push be Expo-managed for MVP or direct APNs/FCM from day one? What is the maximum local retention period for cached financial data and attachments?