# 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

```text
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.

```text
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:

```ts
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:**

```text
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:

```ts
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.

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:

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?