Native Hardware Runtime
FlowPOS has two native hosts around the same unmodified frontend-pwa build:
| Host | App | Runtime |
|---|---|---|
| Mobile | apps/mobile | Expo / React Native WebView |
| Desktop | apps/desktop | Tauri v2 / Rust |
The product is the PWA. Each host is a container: device identity, durable local storage, tray and autostart (desktop), over-the-air web bundles vs signed native releases, and hardware the browser cannot reach.
The only contract between PWA and host is the shared bridge in
packages/global/types/mobile-bridge.types.ts
(the mobile- prefix is historical; both hosts speak it).
React Native host Tauri host
↕ ↕
NativeBridge NativeBridge
↕ ↕
frontend-pwa
Printing is the first native capability, not the architecture. Later hardware — scanners, scales, drawers, payment terminals, customer display — should land as additional advertised capabilities on the same bridge. See Capabilities beyond printing.
Rules that must not drift
These are load-bearing in 051-mobile-app-shell and 054-desktop-runtime-shell. A later hardware feature may add merchant UI; it does not quietly invent host or capability branches against them.
- FR-003 — the PWA never branches on which host it is in (
isWindows,__TAURI__,ReactNativeWebView, user-agent host tests). Enforced by a source guard. Host knowledge is permitted in exactly one module: the shared bridge client's transport layer (FR-003a). - FR-004 — both hosts speak the same handshake, capability declaration, and command shapes. One set of contract fixtures covers both.
- FR-004a — an absent capability means ordinary browser behaviour. There is no capability-conditional layout, no "desktop mode", and no screen that exists only when a capability is present. A capability may change what the host does with a request; it may not change what the merchant sees.
- FR-038 — an unsupported capability returns
UNSUPPORTED_CAPABILITY, never silence.
// Forbidden — host branch
if (isWindows) { /* … */ }
// Forbidden under current specs — capability-conditional UI
if (capabilities.scale?.serial) showScale();
// Allowed — the host advertises what it can do; the PWA degrades when absent
await bridge.request("scale.read", /* … */);
// host returns UNSUPPORTED_CAPABILITY; PWA behaves as in a browser
A future scale, scanner, or customer-display feature that needs merchant-facing UI gets its own spec. That spec amends FR-004a deliberately; it does not drift in application code.
Where to look
- Mobile Shell — Expo container, origin pin, sessions, outbox
- Desktop signing — code-signing and notarisation for the Tauri host
- Printing topology — where printing happens by platform today
- Specs:
051-mobile-app-shell,054-desktop-runtime-shell