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
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_successmobile_dashboard_viewedmobile_receipt_capturedmobile_draft_syncedmobile_sync_failedmobile_approval_approvedmobile_invoice_sharedmobile_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/apiadd/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?
No comments to display
No comments to display