Skip to main content

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

  1. Backend is source of truth for accounting, tax, invoice numbering, e-invoice submission, audit logs, and permissions.
  2. Mobile is an action companion, optimized for capture, approvals, alerts, and simple workflows.
  3. Offline-first only where valuable: drafts, queued uploads, cached dashboard, and pending approvals.
  4. Country behavior is configuration/plugin-driven, not hardcoded screen forks.
  5. No regulated secrets or certificates in the mobile app unless a future legal/security review explicitly approves it.
  6. 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}

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:

  1. Drain queue on app foreground and network restore.
  2. Use FIFO order per entity.
  3. Use exponential backoff.
  4. Preserve item after failure; never silently discard user-captured document.
  5. 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:

  1. Expo Notifications for fastest Phase 3 implementation.
  2. Direct FCM/APNs if advanced routing/compliance requirements require it later.
  3. 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.
  • 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:

  1. Receipt captured offline survives app restart and syncs after network restore.
  2. HR/RS/BA company displays correct currency and country labels.
  3. Push notification opens correct invoice/expense/document.
  4. Stale approval action receives conflict and does not silently approve wrong version.
  5. 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

  1. Does Bilko currently expose mobile-safe BFF endpoints, or should apps/api add /mobile/*?
  2. Which auth/session mechanism is canonical for mobile refresh token rotation?
  3. Which local DB encryption approach is acceptable under Expo constraints?
  4. Which OCR provider meets cost, language, and privacy requirements for HR/RS/BA receipts?
  5. Should push be Expo-managed for MVP or direct APNs/FCM from day one?
  6. What is the maximum local retention period for cached financial data and attachments?