RestaurantBrain feasibility study

#R08 — Client Applications & Device Fleet Management

Scope: every screen the product ships, the framework decision for each, app distribution, MDM/fleet ops, and the POS-specific UX constraints that engineers routinely underestimate. Date: 2026-08-18. Labels: [verified] = checked this session with a URL, [known] = confident from training data, [estimate] = reasoned guess. Reads alongside: r01-hardware-peripherals.md (printers, terminals, GMS/EDLA trap), r04-offline-sync-domain.md (sync core, hub election), r03-fiscal-regulatory.md, r06-crm-loyalty-privacy.md. Effort here excludes the sync core, the printing stack, fiscal drivers, payments certification, and backend — those are costed in their own docs.


#0. Bottom line up front

  1. You will ship 11–14 distinct client surfaces. That is the real scope of "a POS + CRM", and it is why this category eats EUR 5M. Ranked honestly, only three are required for a first paying customer: the POS terminal app, the back-office web console, and a kitchen output path (printer is enough; KDS is a fast-follow).
  2. Framework verdict: Kotlin + Jetpack Compose, Android-first, structured as Compose Multiplatform modules from day one. Web (React/TypeScript) for back office, guest ordering, and QR. No Flutter, no React Native, no dual-native, no Electron. The decisive argument is not rendering performance — it is that every peripheral and payment SDK in the certified matrix is a Java/Kotlin AAR, and bridging 10–14 of them into a JS or Dart runtime costs 4–7 engineer-months of pure glue plus permanent maintenance. Kotlin makes that cost zero. Section 2.3 has the arithmetic.
  3. The switch condition is explicit: if the beachhead flips to Western EU or the US, iPad share rises materially and Compose-Multiplatform-on-iOS becomes a load-bearing bet rather than a free option. In that world React Native (New Architecture) is the safer pick. Decide the beachhead before you write the first Gradle file.
  4. Two operational facts will hurt more than any framework choice:
    • Android cannot downgrade an app in place. Uninstall/reinstall wipes app-private storage — i.e. the offline order journal. Therefore rollback does not exist; you have forward-fix + staged rollout + server-side kill switches, or you have Friday-night outages.
    • Silent app install on Android requires Device Owner mode, which can only be established at factory-reset time. A device an installer unboxed and set up "normally" can never be silently updated again without wiping it. This makes the 20-minute install runbook a product requirement, not ops hygiene.
  5. Apple's App Review is not the P1 risk people think — 90% of submissions clear in under 24h and expedited review is typically ~24h [verified]. The real Apple risks are: no rollback, Guideline 4.2.6 (no per-restaurant white-label apps for SMB), the multicast entitlement, and the Local Network permission prompt that silently kills printer discovery on install day.
  6. Budget EUR 12–30/site/month for MDM at 6 devices/site. Against a EUR 79–129/site/month subscription that is 10–25% of gross margin. Recommendation: cheap MDM for enrolment + kiosk lock + OS update windows only (Mosyle EUR ~1/device, Hexnode EUR ~1–1.8) and put diagnostics/telemetry/remote-config inside your own app, where it works offline and you control it. Esper (USD 2–6/device, 25-device min [verified]) is worth its premium only once you have >200 sites or a self-order kiosk fleet.

#1. Surface inventory and MVP ranking

#1.1 The full list

#SurfacePrimary userDeviceWhy it exists (the job it does)Cost of omitting it
1POS terminal appServer, cashier, managerSunmi D3 Pro 15.6" / Elo I-Series / iPad 11"Take the order, take the money, close the shift. This is the product.No product
2Back-office web consoleOwner, manager, our supportAny browser (desktop-first, 1280px+)Menu, prices, staff, roles, reports, device config, CRM. Where the owner lives.No product
3Kitchen printer pathKitchenEpson TM-m30III / TM-U220IIGet the ticket to the line.No product (costed in r01)
4KDS (kitchen display)Line cooks15–21.5" Android AIO or Elo, wall/shelf mountReplaces paper: ticket state, timers, all-day counts, bump. Sells against paper on ticket-loss and speed-of-service.Lose every site with >2 prep stations; lose the "modern" story
5Handheld waiter appServerSunmi V3 Mix / L2s Pro / phone (5–6")Order at the table, fire courses, take payment tableside. Cuts table-turn time ~10–15% [estimate] in table service.Lose most full-service restaurants in DE/AT/CZ where tableside is the norm
6Expo / pass screenExpeditor21.5"+ landscapeAssembles the whole order across stations; the counterpart to the per-station KDS.A KDS mode, not a separate app. Omitting it breaks any kitchen with 3+ stations
7Customer-facing display (CFD)Guest at the counterSecond screen on the AIO (Sunmi D3 Pro dual, Elo) or a 10" tabletShows the running bill, the loyalty prompt, the tip screen, the QR to join loyalty. This is the single highest-yield CRM capture point (see r06 §2).Loyalty capture rate drops hard; some markets require price transparency at the counter
8Guest web ordering + loyalty walletGuest, off-premiseMobile browser, PWA, Apple/Google Wallet passOrder-ahead / pickup / delivery-direct. The revenue argument for the whole CRM: commission-free orders vs 25–30% on Glovo/Wolt/Bolt Food.Lose the CRM's headline ROI story
9QR table-ordering PWAGuest, on-premiseGuest's phone, no installOrder + pay from the table. Genuinely popular in PL/CZ/UA cafés and bars; near-zero adoption in DE/AT fine dining.Lose a differentiator in the beachhead; low cost because it reuses #8
10Owner mobile appOwner, multi-site operatoriOS + Android phoneToday's sales, live tickets, cash-drawer alerts, remote void approval, staff clock-in anomalies. Drives daily engagement → drives retention.Retention/NPS hit; no daily habit; measurable churn driver [estimate]
11Self-order kioskGuest21–27" portrait + unattended EMV terminalQSR/fast-casual labour substitution. Average check typically +8–15% vs counter [estimate].Locked out of QSR/fast-casual entirely
12Drive-thruGuest in carOrder confirmation board, HME/3M headsets, lane sensors, outdoor terminalUS/PL QSR chains only.Nothing, in SMB. Do not build.
13Fleet/device agent + admin consoleUs (support)Embedded in each app + internal webProvision, update, diagnose, telemetry, remote-config.Support cost per site goes up 3–5× [estimate]; you cannot scale past ~50 sites
14Internal support consoleOur support/CSWebImpersonate a site read-only, inspect device state, pull logs, replay an order, reissue a receipt.Every ticket becomes an engineer's problem

Two surfaces are missing from the brief's list and belong in it: #13 fleet agent and #14 internal support console. Teams skip them and then discover that customer #30 costs the same engineering attention as customer #1.

#1.2 MVP ranking (build order)

RankSurfaceTierRationale for the position
1POS terminal appT0Nothing sells without it
2Back-office web console (menu + staff + basic reports)T0The owner's buying evaluation happens here, not at the till
3Kitchen printingT0Costed in r01
4Fleet agent (config push, OTA, log pull, telemetry)T0Cheap now, impossible to retrofit onto 200 deployed devices
5KDS + expo modeT1First upsell; first "modern POS" proof point
6Handheld waiter appT1Required for full-service. ~70% code reuse from #1
7Owner mobile app (thin, read-only + approvals)T11.5 eng-months for a disproportionate retention effect
8Internal support consoleT1Needed by roughly customer #20
9QR table-ordering PWAT1/T2Cheap once #10 exists; strong beachhead differentiator
10Guest web ordering + wallet passT2The CRM ROI story, but a whole payments+menu+delivery surface of its own
11CFDT21.2 eng-months; move to T1 if loyalty capture is the wedge
12Self-order kioskT3+4.5 eng-months app plus 5–8 for unattended EMV certification (r01 item D)
13Drive-thruNever (SMB)Enterprise QSR feature, enterprise sales motion

Explicit non-goals for v1: drive-thru; native per-restaurant white-label guest apps (see §4.6); a Windows/desktop POS client; a smartwatch app; anything on a TV OS.


#2. Framework decision — hard verdict

#2.1 The five tests, stated as falsifiable requirements

TestConcrete pass criterion
(a) Offline + SQLiteAppend 1 order event to a local WAL-mode SQLite DB and return control in ≤ 5 ms p99 on a Sunmi D3 (RK3568, 2 GB RAM). Survive process kill mid-order with zero lost keystrokes. Full-text search 3,000 menu items in ≤ 30 ms.
(b) Raw hardware accessOpen a TCP socket to printer:9100; bind an AIDL service (IWoyouService) for the built-in printer/drawer; receive a scanner broadcast intent; call a payment terminal SDK (Adyen/SumUp/Viva/PAX) and handle its async callbacks; read RS-232 from a scale via a USB-serial adapter.
(c) 60 fps under a rushp99 frame time ≤ 16.7 ms with a 200-item menu grid on screen, an open ticket of 40 lines, an active MQTT connection, and a print job in flight. Cold start to interactive ≤ 1.5 s on the D3.
(d) One codebase across iPad + Android POS + KDS≥ 80% shared code across the counter surfaces; a new modifier rule ships once, not three times.
(e) HiringYou can hire a mid-level engineer in Kyiv/Lviv/Warsaw/Kraków in ≤ 6 weeks at ≤ EUR 4,500/mo gross-of-taxes contractor rate [estimate] and have them productive in ≤ 3 weeks.

#2.2 Candidate matrix

(a) Offline+SQLite(b) Raw hardware(c) 60 fps(d) One codebase(e) HiringVerdict
Kotlin + Compose (Android-only)Best-in-class: Room/SQLDelight, direct JNINative. Zero bridge. Every vendor SDK is a Java AARExcellent with Baseline Profiles + R8 full modeAndroid POS + KDS + CFD + handheld + kiosk = one codebase. iPad excludedHuge Android pool in UA/PL/ROCHOSEN
Compose Multiplatform (adds iOS)Same, via SQLDelightAndroid native; iOS needs Swift interop (but iOS has far fewer SDKs to bind)Stable for iOS since May 2025; UI stable on Android/iOS/desktop, Web/Wasm still beta as of May 2026 [verified]Adds iPad at ~15–25% extra [estimate]KMP-specific experience thin, but you hire Android devs not "KMP devs"CHOSEN as the module structure, iOS target activated later
React Native (New Architecture)op-sqlite / expo-sqlite are fine; JSI makes sync calls cheap. Bridgeless default since 0.78 [verified]Every vendor SDK needs a TurboModule. 0.2–0.6 eng-mo each × 10–14 SDKsAchievable but requires discipline (FlashList, memo hygiene); JS thread is a shared resource with your sync engineBest breadth: iPad + Android + (with RN-Web, partially) browserLargest, cheapest pool in CEE by a wide marginRunner-up. Becomes first choice if iPad is primary
FlutterExcellent (drift/sqflite); isolates for the sync engineSame platform-channel tax as RN, in DartBest raw rendering. Risk: Impeller is the only renderer on modern Android as of 2026 [verified] — exotic Rockchip/Mediatek AOSP GPU drivers on cheap POS AIOs are an unvalidated surface [estimate — must test on the actual D3/Elo units]GoodDart pool in CEE is real but ~4–6× smaller than TS [estimate]Rejected: same bridging tax as RN, smaller hiring pool, driver risk on the exact hardware we resell
Native Swift + native KotlinBestBestBestWorst: 1.8–2.2× surface work [estimate]FineRejected. Fatal for S1
Web / PWA in a WebViewOPFS+SQLite WASM works; quota and eviction are hostileDisqualifying. No TCP sockets, no Bonjour, no vendor SDKs, no Web Bluetooth on iOS (r01 §2.5). WebView version is frozen on non-GMS Sunmi ROMs (r01 §1.3)Adequate; janky on 2 GB devicesGreat for the other surfacesLargest poolRejected for the counter. Correct for #2, #8, #9, #14.
Electron / TauriFineFull native access on desktopFineIrrelevant to tabletsFineRejected as a POS client. Accept a small Tauri "site bridge" if you ever need to drive Polish fiscal printer DLLs or legacy serial gear from a Windows box (r01 §6.3)

#2.3 The decisive argument: the SDK bridging tax

Enumerate the native SDKs the counter app must call in the first 24 months:

SDK / interfaceLanguage shippedBridge cost in RN/Flutter [estimate]
Sunmi printer + drawer (IWoyouService AIDL)Java AAR0.3 mo
Sunmi scanner broadcast + HIDJava0.15 mo
Sunmi dual-screen / Presentation CFDJava0.3 mo
Sunmi Pay Kernel (V2/P2 payment devices)Java AIDL0.6 mo
Epson ePOS Android SDKJava AAR0.4 mo
Star Android SDK / CloudPRNTJava AAR0.4 mo
Zebra Link-OS / Brother QL SDK (labels)Java AAR0.3 mo
Payment terminal SDK #1 (SumUp / Viva / Adyen Android)Java/Kotlin AAR0.6 mo
Payment terminal SDK #2 (second acquirer, per market)Java AAR0.5 mo
PAX Neptune Lite (com.pax.dal.IDAL) if you go PAXJava0.6 mo
Fiscal device bridge (PL Posnet/Elzab, RO AMEF)Java/JNI wrapper0.5 mo
Android Enterprise DevicePolicyManager kiosk/OTA controlsJava0.4 mo
USB-serial for scales (UsbManager + FTDI/CP210x)Java0.3 mo
Android Keystore / device attestationJava0.2 mo
Total bridge glue≈ 5.5 eng-months

Plus a permanent tax: every vendor SDK bump re-tests the bridge, every RN/Flutter major bump re-tests all of them. Budget 0.15–0.25 eng-months/quarter forever [estimate].

In Kotlin this number is zero. Five and a half engineer-months is ~14% of the entire client-app budget in §Build effort, and it buys nothing a customer can see. That is the verdict.

The honest counter-argument: RN would let you share TypeScript domain logic with the back office and guest PWA. But per r04 the authoritative domain is an event-sourced core that should live in one place and be deterministic — and KMP compiles that same Kotlin core to a JVM backend and to JS/Wasm for the web console if you want it. Kotlin gives you the shared-core benefit too, in the direction that matters.

#2.4 Verdict, stated as a commitment

Counter surfaces (POS, handheld, KDS, expo, CFD, kiosk):
    Kotlin 2.x + Jetpack Compose, min SDK 26 (Android 8.0), target latest
    Module layout from day 1:
        :domain      (KMP, pure Kotlin, no Android deps)  <- shared with backend
        :data        (KMP, SQLDelight, sync client)
        :ui-core     (Compose Multiplatform: design system, POS widgets)
        :ui-pos / :ui-kds / :ui-cfd / :ui-kiosk   (CMP where possible)
        :android-app (Android-only: AIDL, USB, DPM, vendor SDKs)
        :ios-app     (NOT built in v1; the seam exists)

Back office, guest ordering, QR PWA, internal support console:
    TypeScript + React (Vite), no SSR-heavy framework for the console;
    guest ordering gets SSR (Next.js or Remix) for SEO + first-paint

Optional site bridge (Windows/Linux, only if fiscal or legacy serial forces it):
    Tauri 2.x + Rust, ~30 MB, no Chromium bundle

Languages the company maintains: Kotlin + TypeScript (+ Rust only if the bridge ships).

What would flip this to React Native: (1) beachhead becomes US or Western EU with >40% iPad attach; (2) you decide to sell a per-restaurant branded native guest app (you shouldn't — §4.6); (3) you cannot hire 2 senior Android engineers in 8 weeks.

What would flip this to Flutter: essentially nothing, unless your existing team is already a Flutter team — in which case use Flutter and eat the 5.5-month bridging tax, because team familiarity is worth more than 5.5 months.

#2.5 What iPadOS forbids (the concrete list)

ConstraintDetailConsequence for a POS
No general USB host APINo third-party USB access on iPadOS. USB-C handles displays, UVC cameras, audio, Ethernet at OS level only. FTDI/CP210x serial adapters are invisible to apps. [known]No USB printers, no USB scales, no USB cash-drawer interfaces. LAN or MFi Bluetooth only
Bluetooth Classic requires MFiSPP accessories need MFi certification (paid by the accessory maker). BLE/GATT via CoreBluetooth is open. [known]Epson TM-m30III BT works (MFi); a EUR 40 generic BT printer never will
Background executionbeginBackgroundTask gives ~30 s. Available background modes: audio, location, BLE central/peripheral, external-accessory, VoIP (deprecated pattern), background fetch (opportunistic), push. No long-lived listening socket. [known]An iPad cannot be the LAN sync hub (r04 §2.3/2.7). A KDS iPad must be pinned foreground via Single App Mode or it stops receiving tickets
Bonjour/mDNSRequires com.apple.developer.networking.multicast, granted by Apple on application only. [known]Weeks of lead time, possible refusal. Manual-IP fallback is mandatory (r01 §2.6)
Local Network permissioniOS 14+ prompts once. If the installer taps "Don't Allow", every printer disappears and there is no programmatic re-prompt — only Settings. [known]Top-3 install-day failure. The install wizard must test local network access and hard-block until granted
Kiosk modeAutonomous Single App Mode (UIAccessibility.requestGuidedAccessSession) only works on a supervised device with your bundle ID in the MDM's ASAM allow list. Manual Guided Access is fragile (staff exit it). [known]You cannot ship a locked-down iPad POS without ABM + MDM. Adds EUR 1–5/device/mo and a purchasing constraint (§5.2)
In-app purchase rulesSelling your SaaS subscription inside the merchant app invites 15–30% commission. [known — post-DMA/Epic rules are volatile]Ship the app with sign-in only; all billing on the web
Guideline 4.2.6Apps generated from a template/generation service must be submitted from the client's own developer account. [known]No white-label per-restaurant guest apps for SMB (§4.6)
DMA alternative distributioniOS 17.4+/iPadOS 18+ permit alternative marketplaces in the EU, with notarization and a Core Technology Fee. Terms revised through 2025. [known — volatile, verify before relying on it]A theoretical escape from App Review; not a plan

#2.6 What Android POS vendors require

Vendor / topicRequirementImpact
Sunmi printer/drawerBind the AIDL service (woyou.aidlservice.jiuiv5 / IWoyouService), or the newer printer library. Drawer via openDrawer(). [known — verify current package names at docs.sunmi.com]Direct Kotlin call. Service can die; you need rebind-with-backoff
Sunmi scannerBroadcast intent (...ACTION_DATA_CODE_RECEIVED) or HID keyboard emulationTwo code paths; HID mode steals focus from text fields
Sunmi dual screen (D3 Pro)DisplayManager + Presentation, plus Sunmi's ext-display service for the small LCD variantsCFD is a second Activity in the same process; test what happens on process death
Sunmi remote managementSunmi DMP with remote desktop control, annotation, file transfer [verified]; devices also manageable via Esper/Intune/Scalefusion [verified]A free-ish fallback fleet tool tied to one vendor
PAXNeptune Lite API (NeptuneLiteUser.getInstance().getDal(ctx)); APKs must be signed and distributed through PAXSTORE, security-reviewed, and whitelisted in the acquirer's tenancy [verified that PAXSTORE performs signing + security checks]You usually cannot sideload on a production PAX estate. Expect 2–6 months of BD per acquirer [estimate]. Prefer Sunmi + a separate acquirer terminal in v1
No GMS (EDLA-less SKUs)No FCM, no Play Store, frozen WebView, no Play Integrity (r01 §1.3)You need your own MQTT foreground service (r01 costs it at 1.0 eng-mo) and your own OTA channel. Never put a WebView on the critical path of a non-GMS device
Device OwnerSilent APK install, setLockTaskPackages/startLockTask, setLockTaskFeatures, setSystemUpdatePolicy(TYPE_INSTALL_WINDOWED) all require Device Owner, provisionable only at factory-reset time (QR at the setup wizard, NFC bump, or zero-touch) [known]The install runbook is load-bearing. A "normally set up" device is permanently second-class
AOSP quirksStale tzdata/ICU on old ROMs (DST bugs in Europe/Kyiv, Europe/Warsaw); OEM battery killers terminating foreground services; missing PackageInstaller UI; some AIOs report the wrong Configuration.orientation on a landscape-locked panel [known]Add a tz-data sanity check at boot and a server-authoritative business-day boundary (ties to r04 §4.2 clock skew)
RAMEntry AIOs ship with 2 GB (e.g. Sunmi D3 Mini base) [estimate — confirm SKU]Hard PSS budget of ~250 MB. onTrimMemory must drop image caches. A process kill mid-order is a data-loss event unless every keystroke is journaled to SQLite

#3. Distribution

#3.1 Channel matrix

ChannelPlatformReview latencyRollout controlRollbackUse it for
App Store (public)iOS/iPadOS90% < 24 h; complex apps 2–5 days; expedited ~24 h [verified]Phased release for automatic updates: 1/2/5/10/20/50/100% over 7 days, pausable up to 30 days [known]NoneOwner app, guest app (if you ship one)
Custom App via Apple Business ManageriOS/iPadOSSame App Review, typically 1–2 days [verified]Assigned by MDM or redemption code; can be free or pricedNoneThe merchant POS app, if you ever ship iPad
TestFlightiOSInternal builds ~instant after the first external review of a version; 90-day expiry, 10k testers [known]Manual group targetingJust don't promoteBeta sites, internal
Ad HociOSNone100 devices/yr/type, 1-year profile expiry [known]n/aDevice lab only
Apple Developer Enterprise ProgramiOSNoneUSD 299/yrn/aDo not use for customers. It is for your own employees. Misuse gets the cert revoked and every app on every device stops launching instantly. This has happened publicly to multiple companies [known]
Managed Google Play (private app)Android GMSMinutes to a few hours [known]Percentage staged rollout, halt supportedHalt + forward-fixMerchant app on GMS/EDLA devices
MDM-pushed APKAndroid (Device Owner)NoneYour own cohorts, your own logicBlocked by Android (§3.3)The primary channel for POS/KDS/CFD
PAXSTOREPAX terminalsVendor security review, days–weeks [estimate]Acquirer tenancy controlsVendor-mediatedOnly if you commit to PAX
Sunmi App Store / DMPSunmiVendor review [estimate]Vendor consoleVendor-mediatedFallback
Direct sideloadAndroid non-Device-OwnerNoneManual, requires user tapManualNever in production

#3.2 The Friday-night P1, with a clock

Scenario: 19:40 on a Friday, a build shipped Thursday turns out to drop every third kitchen ticket at sites with two printers. 180 sites, mid-service.

PathWhat actually happens
Server-side kill switch (correct answer)19:42 flip a flag → clients fall back to the previous print-routing code path on the next order. Time to mitigation: 2 minutes. Requires the flag to have existed before the incident and to be evaluated locally from a cached config, because half the sites' internet is flaky
Android: push previous APK via MDMBlocked. INSTALL_FAILED_VERSION_DOWNGRADE. Uninstall-and-reinstall wipes app-private storage → destroys the unsynced order journal. Not an option during service.
Android: forward-fixBuild + test + sign + stage: realistically 2–5 h even with everything ready. Ships at ~01:00
iOS: remove from saleStops new installs. Does nothing for the 180 iPads that already auto-updated. No rollback exists.
iOS: expedited review of a fix~24 h typical [verified]; occasionally faster if you plead. Ships Saturday

Rules that follow, non-negotiable:

  1. Every risky code path ships behind a remotely-flippable flag with a locally-cached default, and the flag store must work when the site's WAN is down (last-known-good config persisted, TTL 7 days).
  2. Release freeze Thursday 12:00 → Monday 10:00. No exceptions, including "it's just a copy change".
  3. Staged rollout cohorts: internal lab (1) → 3 friendly sites → 10% → 40% → 100%, with a minimum 24 h soak spanning at least one dinner service at each step. Never advance on Fri/Sat.
  4. Forced-update policy must degrade, not block. A server returning 426 Upgrade Required that hard-blocks the till at 20:00 is an outage you caused. Correct behaviour: client below min_supported enters grace mode — local-only order entry and printing continue, sync is paused, a non-modal banner appears, and hard-block only triggers after min_supported_hard (set weeks later) and only outside the site's configured service hours.
  5. Version skew is permanent. A device offline for 3 weeks (r04 §4.6) will rejoin on an old client. The sync protocol must be N-2 compatible in both directions, with a schema version negotiated per connection.

#3.3 The Android rollback trap, spelled out

  • Android refuses in-place downgrades of a signed app (INSTALL_FAILED_VERSION_DOWNGRADE).
  • PackageInstaller rollback (SessionParams.setEnableRollback) requires system/privileged context — not available to a normal Device Owner app for arbitrary APKs [known — verify against current AOSP for your target API level].
  • Uninstall/reinstall wipes /data/data/<pkg> — the SQLite journal of unsynced orders.

Mitigations, in order of value:

  1. Feature flags + kill switches (as above). This is 90% of the answer.
  2. Store the event journal where a reinstall cannot destroy it: a second copy in app-scoped external storage or a BackupAgent-eligible location, encrypted with a Keystore-held key that is escrowed server-side per device. Costs ~0.4 eng-months and turns a catastrophe into an inconvenience.
  3. Keep the previous APK cached on-device so a reinstall is offline-capable.
  4. Ship a recovery build channel: a tiny, rarely-changed app that can (a) export the local journal to a file, (b) fetch and install a specified version, (c) display support contact and a device ID. Installed at provisioning, updated twice a year.

#4. Fleet management

#4.1 MDM options and real prices

ProductPlatformsList price [verified unless noted]MinimumsPOS-specific strengthsWeaknesses
Mosyle Business PremiumAppleUSD 1/device/mo, annual, 30-license min30Cheapest credible Apple MDM by a wide margin; free tier existsApple-only; support is thin at this price
Hexnode UEMApple, Android, WindowsUSD 1 / 1.8 / 2.8 / 3.8 / 5.8 per device/mo by tier, annualVariesCross-platform in one console; good kiosk/COSU supportConsole is dense; Android dedicated-device features less deep than Esper
EsperAndroid (dedicated), some AppleUSD 2 (Genesis) / 4 (Bridge) / 6 (Architect) per device/mo, 25-device minimum, 30-day trial25Built for POS/kiosk fleets. Remote view/control, staged app rollout pipelines, custom AOSP support, PCI DSS validated as a cloud provider for Android mPOSPriced at 2–6× Mosyle; overkill below ~200 sites
Jamf ProAppleUSD ~4 (small business) / 5.75 (mobile) / 12.50 (Mac) per device/mo; enterprise 3.67–12.50VariesDeepest Apple feature set, best ecosystemExpensive per till; you need ~5% of its features
Iru (formerly Kandji)Apple + Windows + Android since Oct 2025 rebrand~USD 30–60 per iOS device/yr; mostly custom quotesCustomPolished; now cross-platformOpaque pricing; the product changed shape in the rebrand [verified]
ScalefusionAndroid, Apple, Windows~USD 2–5/device/mo [known]VariesStrong COSU/kiosk story, India/EU presence, cheapLess DevOps-ish than Esper
Sunmi DMPSunmi onlyBundled / low [estimate]Remote desktop, annotation, file transfer, works on non-GMS Sunmi [verified]Vendor lock; useless for iPads or Elo

#4.2 Recommendation and the cost math

A reference single site runs 6 devices: 1 till AIO, 1 KDS, 1 handheld, 1 CFD (often the till's second screen — count it as 0), 1 spare/manager tablet, 1 kiosk (only in QSR). Realistically 4–6 managed endpoints.

StrategyCost/site/moComment
Esper Bridge on everything, 6 devicesUSD 24 ≈ EUR 2217–28% of a EUR 79–129 subscription. Only defensible with kiosks or >200 sites
Hexnode Pro on everything, 6 devicesUSD 10.8 ≈ EUR 10Reasonable
Mosyle/Hexnode base tier for enrolment + kiosk lock + OS update window only, everything else in our own agentUSD 6 ≈ EUR 5.5Recommended.
Build it all ourselvesEUR 0 recurring, +2.0 eng-months (r01 item 10) and permanent ownershipOnly if MDM cost genuinely threatens gross margin at scale

The split that works: MDM owns what only the OS can do — enrolment, supervision, lock-task/kiosk, OS update windows, network/Wi-Fi payloads, remote wipe, lost mode. Your own in-app agent owns everything else — app version, remote config, feature flags, log capture, printer health, telemetry, "send diagnostics", remote restart of the app, sync status. Your agent works offline against a cached config; an MDM does not. Your agent knows what a "printer" and an "unsynced order" are; an MDM does not.

#4.3 Zero-touch provisioning: 6 devices in under 25 minutes

Target: a non-technical installer (or the restaurant owner, from a box shipped by courier) brings a site live without a phone call.

Android (the primary path):

  1. Devices are shipped pre-registered. Either (a) the reseller enrols them in the Android zero-touch portal (GMS/EDLA SKUs only), or (b) we ship a printed QR provisioning card in the box. The QR encodes:
    {
      "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME": "com.rb.dpc/.AdminReceiver",
      "android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM": "<base64url SHA-256 of signing cert>",
      "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION": "https://ota.rb.example/dpc-<ver>.apk",
      "android.app.extra.PROVISIONING_WIFI_SSID": "<site ssid>",
      "android.app.extra.PROVISIONING_WIFI_PASSWORD": "<key>",
      "android.app.extra.PROVISIONING_SKIP_ENCRYPTION": false,
      "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE": { "site_claim": "K7M-4T2", "role": "TILL" }
    }
    [known — key names are stable across API 26+; verify checksum encoding]
  2. Installer powers on, taps the welcome screen 6× → QR scanner opens → scans the card. Device joins Wi-Fi, downloads the DPC, becomes Device Owner. ~4 min/device, mostly download.
  3. DPC applies policy (lock task, disable factory reset, disable USB debugging, setSystemUpdatePolicy window 03:00–05:00) and silently installs our app.
  4. Our app launches, reads site_claim from the admin extras, calls home, and self-assigns its role (TILL / KDS / HANDHELD / CFD / KIOSK) from the pre-built site plan in the back office.
  5. Printer discovery wizard runs (r01 §2.6): mDNS + Epson UDP 3289 + ARP sweep → installer confirms which physical printer is "KITCHEN" via a test print that prints the station name in 48 pt → assign static IP + record MAC → print and stick a physical label.
  6. Menu + staff + tax config sync down. Site is live.

Timing [estimate]: 4 min/device × 6 = 24 min of device time, ~8 min of which is parallelizable; printer wizard 6–10 min; total 25–35 min for a competent installer, 60–90 min for a first-timer following a printed runbook. Budget 1.0 eng-month for the wizard + runbook (also counted in r01 item 12).

Apple (if/when iPads exist) — the purchasing constraint that surprises people:

  • Automated Device Enrollment requires the device to be in your Apple Business Manager org, which requires purchase from Apple or an Apple Authorized Reseller who links the order to your ABM Organization ID. [known]
  • A device bought from a random EU webshop is not in ABM. Your only fallback is Apple Configurator over USB on a Mac — ~2 min/device and it grants the user a 30-day provisional window in which they can un-enrol. [known]
  • Therefore: iPad procurement is an ABM-linked purchasing decision, not a shopping decision. Write it into the reseller agreement or you will be manually configuring iPads forever.

#4.4 Remote diagnostics and screenshare

CapabilityAndroidiPadOS
Remote view (see the screen)Esper, Scalefusion, Sunmi DMP [verified]Not possible. No third-party or MDM screen mirroring exists
Remote control (drive the UI)Esper / Sunmi DMP [verified]Not possible
Screenshot on demandVia MDM agent or your own app with MediaProjection (needs a one-time user consent)Not possible
Practical substituteIn-app "Record 15 s and attach to ticket" using ReplayKit (user confirms), plus a diagnostics bundle button
Log pullYour agent uploads a rotating ring buffer (last 5,000 events, ~2 MB gz) on demand or on crashSame
Remote restart of the appDevice Owner can force-stop + relaunchNo
Remote rebootDevice Owner reboot()MDM RestartDevice command on supervised devices [known]

Design consequence: because iOS remote control does not exist and Android remote control costs Esper money, the product must be self-diagnosing. Ship a Device Health screen on every device — printers with green/amber/red, last sync time, unsynced count, WAN/LAN status, app version, storage free, battery/thermal — plus a single "Send diagnostics to support" button that bundles logs + config + last 50 orders' metadata and returns a 6-character reference the staff member reads over the phone. This one screen removes an estimated 30–40% of support call time [estimate].

#4.5 Telemetry, offline recovery, lost/stolen

Telemetry event set (v1, deliberately small):

SignalCadencePayload
Heartbeat60 sdevice_id, app_ver, battery %, thermal state, free disk, DB size, unsynced_count, LAN/WAN state, active role
Printer health30 s per printerreachable, ASB status byte, paper state, last successful job ts
Perfper sessioncold start ms, p50/p95/p99 frame time (AndroidX JankStats), ANR count
Business-criticalper eventorder sent, ticket printed, payment attempt/result, sync conflict resolved, forced logout
Crashon crashSentry (Team plan ~USD 26/mo for 50k errors [known]) or self-hosted

Volume: 6 devices × 1,440 heartbeats/day × ~200 B ≈ 1.7 MB/site/day → ~50 MB/site/month. At 1,000 sites that is 50 GB/month ingested — trivial cost, but compress and batch on the device, because r01 §4.4 notes CEE sites often run on LTE backup during outages where every megabyte is metered.

Offline recovery — the "site is dark for 3 days" plan:

  • The app must boot, open a table, take an order, print, take cash, and print a Z report with zero network. This is the r04 requirement restated as a client requirement.
  • Config, menu, tax rates, staff PINs, and feature flags are all persisted with a last-known-good copy and a 7–30 day TTL that warns rather than fails.
  • Ship a physical recovery card in the install kit: support phone number, site ID, device IDs, and an offline manager override code (HMAC of site_id + date, verifiable offline) so a manager can void/refund without a server.
  • The recovery build channel (§3.3) is the last resort.

Lost / stolen:

  • Handhelds are the realistic loss vector, not 15" AIOs.
  • Controls: PIN-on-wake after 60 s idle; full-disk encryption (default on modern Android/iOS); device-bound key in Keystore/Secure Enclave so a cloned APK on another device cannot sync; server-side device certificate revocation that takes effect on next connect; MDM lost-mode/lock/wipe.
  • Because we hold no card data (r01 PCI posture — payment lives on the terminal), a stolen handheld is a hardware loss (EUR 400–600) plus a GDPR question about the guest data cached on it. Cap the on-device guest cache: today's orders + loyalty balances for guests seen today, nothing historical. This is also the r06 §6.6 DSAR answer.

#4.6 The white-label trap

Do not ship a per-restaurant branded native app for SMB. Apple Guideline 4.2.6 rejects apps produced from a template/generation service unless submitted by the client from their own account [known], Google is increasingly similar in practice, and 500 App Store listings is 500 review queues, 500 sets of screenshots, and 500 places a P1 can hide. Correct shape: one multi-tenant guest PWA under the restaurant's own domain (order.restaurantname.pl, white-labellable in CSS and logo), plus Apple/Google Wallet passes for loyalty (which are per-brand and cost nothing to mint). Reserve native guest apps for chains with 5+ sites who will fund their own developer account.


#5. POS UX constraints engineers underestimate

#5.1 Latency budget (the one that decides whether staff like you)

InteractionBudgetHow it is achieved
Finger down → visual acknowledgement≤ 50 msSynchronous, local, no recomposition of the grid, no state round-trip. Use Modifier.pointerInput press feedback rather than waiting on the default ripple's async path
Tap → item appears on the ticket≤ 100 msLocal in-memory append; SQLite write is fire-and-forget on a background dispatcher but must complete before the next frame's commit for durability
"Send to kitchen" → confirmation≤ 200 msPersist + enqueue the print job locally. Never wait on the network or the printer. The printer result arrives asynchronously
Payment start → terminal wakes≤ 800 msVendor SDK bound
Cold start → interactive≤ 1.5 s on a 2 GB RK3568 AIOBaseline Profiles + Startup Profiles (typically ~20–30% improvement [known]), R8 full mode, no work in Application.onCreate beyond DI graph, lazy menu image decode
Frame time under a rushp99 ≤ 16.7 ms (60 Hz); ≤ 8.3 ms on 120 Hz iPadLazyVerticalGrid with stable key + contentType, derivedStateOf, no animateContentSize in the grid, images pre-decoded at exact display size

The classic killer: a 4000 px menu photo decoded at scroll time costs 60–120 ms of main-thread work and produces a visible stall exactly when the queue is longest. Pre-resize every menu image server-side to the exact device pixel sizes you ship (e.g. 240 px and 480 px squares) and cache them on disk at provisioning. Ship p99 frame time as telemetry so this regresses loudly, not quietly.

#5.2 The physical environment

FactorRule with a number
Wet / gloved fingersCapacitive touch degrades with a water film; nitrile and latex gloves are fine, thick vinyl is marginal. Specify touch targets in millimetres, not dp/pt, minimum 12 × 12 mm with ≥ 3 mm spacing. Compute per device: Sunmi D3 Pro 15.6" 1920×1080 ≈ 141 ppi → 12 mm = 67 px; D3 Mini 10.1" 1280×800 ≈ 149 ppi → 70 px; iPad 11" @264 ppi → 125 px = 62 pt. Material's 48 dp default is not enough on a 15" panel
GesturesNo swipe-to-delete as the only path (wet swipes misfire). No long-press as the only path. No drag-and-drop anywhere on the POS. Every destructive action needs an explicit button and a confirm
GlareTypical Android POS panels are 250–400 nits (r01 §1.4). Do not default to a dark theme — under specular glare a dark UI competes with the reflected room and loses. Use a high-luminance background, ≥ 7:1 text contrast, body text ≥ 16 sp, ticket lines ≥ 20 sp, money in tabular figures so columns align
One-handedOn a 5–6" handheld the server's other hand is holding plates. Put Send/Fire/Pay in the bottom 25% of the screen, never the top-right. Ship a left/right-hand mode toggle. Assume the device is held at 45° in poor light
8-hour shiftsCap all animation at 150 ms. No parallax, no springy overscroll, no confetti. One distinct "sent" chime, off by default in fine dining. Never buy OLED for a KDS — a static ticket header burns in within months. Handhelds die at ~5 h under continuous use [estimate]: spec 2 batteries or a hot-swap SKU per handheld
MountingKDS above the pass is read at ~2 m. Rule of thumb: cap height ≥ viewing_distance/200 → 10 mm cap height at 2 m. On a 21.5" 1920×1080 (≈102 ppi, 0.248 mm pitch) that is ~40 px cap height ≈ 50–60 px font size for item names. If your KDS uses 24 px text, the kitchen will keep the printer

#5.3 Train in 15 minutes

Make this a testable acceptance criterion, not an aspiration. A new hire who has never seen the product must, with a 90-second video and no trainer, complete in ≤ 15 minutes:

  1. Clock in with a 4-digit PIN.
  2. Open table 12, add 3 items including one with a required modifier group.
  3. Fire course 2.
  4. Split the bill 2 ways by item.
  5. Take a card payment and a cash payment with change.
  6. Void an item (with a manager PIN override).
  7. Clock out.

Run this test with actual restaurant staff, not engineers, at least once per quarter. Anything that fails it moves to the manager-only surface or the back office. Role-based UI is the mechanism: a server sees perhaps 20% of the app's functionality; a shift manager 60%; the owner uses the web console, not the till.

Why it matters commercially: hospitality staff turnover in the EU runs high (commonly cited at 70–100%/yr in the sector [estimate]), so training cost is a recurring cost the operator pays, and "the new kids can't work the till" is a genuine churn driver.

#5.4 Multi-language staff

  • Language is a property of the user, not the device. In PL/CZ/DE the kitchen brigade frequently works in Ukrainian or Russian, the servers in the local language, the owner in a third (r01 §2.7).
  • v1 language set for the CEE beachhead: uk, ru, pl, cs, ro, en, de, sk — 8 locales. Add es/it/fr/pt/nl for Western EU (13 total).
  • Sizing: ~1,400–1,800 UI strings at v1 [estimate]; professional translation EUR 0.08–0.15/word; average 3 words/string → EUR 3,000–6,500 initial, EUR 400–900/month upkeep [estimate]. Machine translation with a native reviewer per locale halves this and is acceptable for a POS but not for guest-facing legal/consent copy (r06 §6).
  • Engineering rules: no string concatenation; ICU MessageFormat for plurals (Polish/Czech/Ukrainian have 3–4 plural forms and one/few/many/other categories that break naive if (n==1) code); allow +40% text expansion in every button; German compound nouns will break your ticket layout.
  • Print language ≠ UI language ≠ guest language. Menu items need a per-station translation column (r01 §2.7) — a schema decision, not an architecture one.
  • RTL (Arabic/Hebrew) is not needed for the beachhead. Defer, but use logical layout properties (start/end) so it is a config change later, not a rewrite.

#5.5 Colour-blind-safe KDS

~8% of men and ~0.5% of women of Northern European descent have a colour-vision deficiency, predominantly deutan/protan [known]. A KDS that encodes "late" as red and "on time" as green is unreadable to one cook in twelve.

Rules:

  • Never encode state in hue alone. Every ticket carries a numeric elapsed timer (mm:ss) as the primary signal.
  • Add redundant channels: border weight (2 px → 6 px), position (late tickets sort left/top), and a fill pattern or an icon.
  • If you use a colour ramp, use blue → amber → magenta, not green → amber → red. Both deutans and protans distinguish it.
  • All text ≥ 4.5:1 contrast against its ticket background after the state colour is applied — the amber state is where teams fail this.
  • Validate with a deutan/protan simulator in CI on screenshot tests; it's a 0.1 eng-month check that prevents a class of bug you will otherwise never hear about (cooks do not file tickets).

#5.6 Tip screens

  • In a semi-integrated payment flow — which is what you should ship (r01) — the tip prompt is rendered by the payment terminal's own app (Adyen, SumUp, Viva, Nexi, PAX), not by yours. You configure the presets via the terminal API and read the tip amount back. You do not control that UI. Design accordingly: your CFD shows the total, the terminal shows the tip prompt, your ticket reconciles.
  • Full control of the tip UI requires a fully-integrated flow or your app running on the terminal, both of which pull you into PCI scope. Not a v1 move.
  • Beachhead reality: tipping is 5–10% in PL/CZ/UA and increasingly card-based; in DE/AT rounding-up is the norm; in FR/ES service is included. Default presets must be per-country, and CEE defaults should be lower than any US-derived library's.
  • Do not pre-select a tip percentage and do not use a "no tip" option rendered smaller or greyer. Beyond the reputational issue, pre-ticked defaults are a live consumer-protection question in the EU [estimate — worth 30 minutes with counsel].
  • Tips have fiscal and payroll consequences (r03 §4). The client's job is to record tip_amount, tip_method, and tip_recipient; the allocation policy (pooled vs individual) is back-office configuration.

#5.7 Accessibility — where it is legally binding

SurfaceRegimeRequirement
Guest web ordering, QR orderingEU Accessibility Act (Directive (EU) 2019/882), applicable from 28 June 2025. E-commerce services are in scope; conformance is via EN 301 549 → effectively WCAG 2.1 AA [known]Real obligation. There is a microenterprise exemption for services (<10 staff and ≤ EUR 2M turnover) but it protects the café, not us, if we are the service provider. Get counsel to confirm who the "service provider" is in a white-label ordering page
Self-order kioskEAA covers self-service terminals including payment terminals [known]; US: ADA Title IIITactile/audio mode, 3.5 mm headphone jack, screen-reader output, reach range (US ADA §308: 380–1220 mm side reach), high-contrast mode, no colour-only cues. This adds ~1.0–1.5 eng-months to the kiosk and constrains the hardware you may resell
Guest website (US)ADA Title III litigation; WCAG 2.1 AA is the de facto standardA real, insurable litigation cost in the US market
Staff POS / KDSNot consumer-facing; no direct EAA obligationBut employment-law reasonable-accommodation applies. Ship: adjustable text scale (100/125/150%), the colour-blind-safe palette (§5.5), and no audio-only alerts
Back-office consoleNot in scopeDo WCAG AA anyway; it costs ~5% extra if designed in and 10× if retrofitted

#6. Cross-cutting engineering decisions

DecisionCallWhy
Design systemOne token set (colour, spacing, type, elevation) with two component libraries — Compose for counter, React for web — generated from the same JSON tokens (Style Dictionary or similar)Two libraries is the honest cost; shared tokens keeps them from diverging. 2.0 eng-months
State management (Compose)Unidirectional; one StateFlow<ScreenState> per screen; no ViewModel holding the order aggregate — the aggregate lives in :domain and is the same object the sync engine folds (r04 §3.4)Prevents the classic bug where the UI and the event log disagree
Feature flagsServer-defined, locally cached with last-known-good, evaluated synchronously offline, with a per-site overrideThe Friday-night mitigation path (§3.2)
Crash + perfSentry (or self-hosted GlitchTip) + AndroidX JankStats + custom cold-start traceCheap; the alternative is guessing
TestingCompose UI tests on emulators for logic; a physical device lab is mandatory for the AIDL/printer/terminal paths (r01: EUR 6–10k)Device quirks are not emulatable
QA shape1 QA engineer who owns the device lab from day one, plus 20 hours/quarter of engineers working actual shifts in a customer's restaurantThe single highest-ROI practice in this category. Every non-obvious requirement in §5 came from a shift, not a spec
Kiosk lockdown (Android)Device Owner + setLockTaskPackages + setLockTaskFeatures(HOME | NOTIFICATIONS off | GLOBAL_ACTIONS off | KEYGUARD off | SYSTEM_INFO on) + setSystemUpdatePolicy(TYPE_INSTALL_WINDOWED, 03:00–05:00)The update window is the one people forget. Without it Android reboots a till at 20:15
Second screen (CFD)DisplayManager + Presentation in the same process as the till appSharing the process means zero IPC and zero sync problem; the cost is that a crash takes both screens

#Build effort

Assumes Kotlin/Compose for counter surfaces and React/TS for web, per §2.4. Excludes the offline/sync core (r04), the printing stack and hardware certification (r01), fiscal drivers (r03), CRM/loyalty backend (r06), payments integration/certification, and all backend services. AI assistance is assumed and already baked in at roughly 1.5× on web/CRUD, 1.15× on native/device/perf work, 1.0× on UX decisions and on-site debugging [estimate] — AI is close to useless for "the Sunmi service unbinds after 4 hours idle".

#Work itemEng-monthsVariance drivers
1Design system + tokens + Compose and React component libraries2.0Two renderers is the floor. A designer with POS experience halves the rework; without one, add 1.0
2POS terminal app — shell: auth, PIN, roles/permissions, device pairing + role self-assign, settings, i18n scaffolding1.5Role model complexity; 8 locales adds ~0.3
3POS — menu grid + modifier engine UI + search2.0Nested/conditional modifier groups and combo/meal-deal UI can double this
4POS — order/ticket: courses, seats, splits (by item/by amount/by seat), merges, table transfers, voids, comps, discounts3.0Splits and transfers are ~55% of it. Cutting them makes you unsellable to table service
5POS — floor plan / table map + service-state colouring + editor in back office1.5A drag-to-place editor is 0.8 of it; a fixed grid is 0.4
6POS — payment UI: split tender, cash rounding, tips, partial refunds, receipt choice (print/email/QR), semi-integrated terminal state machine2.0Second acquirer +0.5; the terminal state machine (timeouts, "did it go through?", reversal) is the risk
7POS — cash management: drawer counts, paid-in/out, X/Z, shift open/close, blind close, discrepancy reporting1.0
8POS — offline UX: degraded-mode banners, sync status, conflict surfacing, grace-mode forced-update behaviour, recovery flows1.0Depends on r04's model; the UI half is what's costed here
9POS — perf, memory, and hardening pass on 2 GB devices1.5Baseline profiles, image pipeline, PSS budget, process-death journaling. Skipping this is how you get "it's slow on Friday"
10Handheld waiter app3.0~70% reuse of 2–8; the 30% is one-handed layout, thumb zones, battery/thermal, tableside payment, offline-first at the far end of the Wi-Fi
11KDS + expo mode + bump bar3.0 (costed in r01 item 6)A ticket list ships in 1.0; a KDS a chef accepts is 3.0
12Customer-facing display1.2Dual-screen Presentation + loyalty prompt + tip/total display + idle marketing loop
13Back-office web console: menu management (3.0), staff/roles/labour (1.5), reporting + analytics (2.5), settings/devices/printers (1.5), multi-site switcher (1.0)10.0Menu management is chronically underestimated: modifiers, availability schedules, per-channel pricing, per-station routing, multi-language, images
14Owner mobile app (thin: today's sales, live tickets, alerts, remote void approval)1.5Push on iOS+Android; keep it read-only + approvals or it becomes a second POS
15QR table-ordering PWA3.0Reuses #16; adds table binding, pay-at-table, split-by-item, staff notification, EAA/WCAG pass
16Guest web ordering + loyalty wallet (menu render, cart, checkout, order tracking, account, Apple/Google Wallet pass, SEO/perf, WCAG 2.1 AA)7.0Delivery-mode logistics (slots, zones, fees) can add 2–3. Wallet passes alone are 0.8
17Self-order kiosk app4.5Plus 5–8 eng-months for unattended EMV certification (r01 item D), plus ~1.0–1.5 for EAA/ADA kiosk accessibility
18Fleet agent (in-app): remote config, feature flags with LKG cache, OTA install for Device Owner, log ring buffer + upload, telemetry, device health screen, remote app restart3.0Overlaps r01 item 10 (2.0) — count ~1.5–2.0 net new here if r01 is already funded
19Provisioning wizard + QR provisioning pipeline + installer runbook1.0 (in r01 item 12)Apple ABM path adds 0.5 if iPads ship
20Internal support console: read-only site impersonation, device inspector, log fetch, order replay, receipt reissue, audit trail2.0Needs a hard permission/audit model — you are looking at customers' financial data
21Release engineering: signed build pipeline, cohort/staged rollout service, kill-switch service, version-skew compatibility tests, recovery build channel2.0Buying Esper Pipelines cuts ~0.7 at USD 4/device/mo
22Accessibility + colour-blind CI checks + i18n plumbing across all surfaces1.5EAA scope for guest surfaces is the driver; retrofit costs ~5×
v1 sellable (T0 + T1: items 1–11, 13, 14, 18–21)≈ 38–44 → take 42
+ T2 (CFD, guest ordering, a11y sweep: 12, 16, 22)+9.7
+ T3 (kiosk app only, excl. EMV cert: 17)+4.5
iPad target activated later (CMP iOS)+6–1015–25% of the counter-surface total, plus MFi/entitlement/ABM overhead
If you choose RN or Flutter instead of Kotlin+5.5 and +0.15–0.25/quarter forever§2.3

#Minimum team shape

ScenarioShapeCalendar to v1 client surfaces
S1 — bootstrapped (founder + 4)2 Kotlin/Android (one owns POS, one owns KDS + devices + fleet), 1 full-stack TS (back office, then guest/QR), 1 backend/domain (r04), founder on product/design/sales. Plus a fractional product designer at ~0.5 FTE, EUR 2.5–4k/mo in UA/PL [estimate] — non-negotiable, a POS without a designer does not sell~2.2 effective FTE on client work → 42 eng-months ≈ 19 calendar months. That does not fit inside a 24-month runway once backend, printing, and fiscal are added. Honest S1 scope: POS + back office + KDS + printing + fleet agent, Android-only, one country — ≈ 26 eng-months ≈ 12 months calendar, leaving 12 months to sell. Handheld, guest ordering, CFD, kiosk are all year 2
S2 — funded (EUR 5–10M)POS pod (3 Kotlin + 1 designer), Web pod (3 TS + 1 designer), Guest pod (2 TS), Platform/devices pod (2 + 1 QA owning the hardware lab) = 13 product engineers + 2 designers + 1 QA. Plus a field-ops/installer hire by customer #10Full surface list (T0–T2) in 9–11 calendar months; kiosk + unattended EMV pushes to 15–18

Roles that are not optional at any funding level: (1) one QA engineer with a physical device lab; (2) one designer who has either worked on a POS or will work 20 restaurant shifts; (3) by customer #10, one field/support person who is not an engineer.


#Open questions / what would change this answer

  1. Beachhead = CEE or Western EU/US? This is the framework decision in disguise. CEE ⇒ Android AIO at EUR 465–688 dominates ⇒ Kotlin/Compose Android-first is unambiguously right and iPad may never be needed. US/Western EU ⇒ iPad attach rises materially ⇒ React Native becomes the safer pick and the +5.5-month bridging tax becomes the price of insurance. Resolve before the first commit. Verify by asking 15 target restaurants in the beachhead what device they'd expect on the counter and what they'd pay for it.
  2. Is Compose Multiplatform on iOS good enough for a POS in 2026? JetBrains marks the UI stable for Android/iOS/desktop as of May 2026 [verified], but "stable" and "a server will not notice" are different bars — text input, scroll physics, and accessibility on iOS are the areas to test. Verify: build the item-grid + ticket screen in CMP, run it on a real iPad for a week, measure p99 frame time and have three servers use it. Two weeks of work that de-risks a EUR 500k decision.
  3. Flutter's Impeller on the exact AOSP GPUs we resell. Impeller is the only renderer on modern Android as of 2026 [verified]; Rockchip RK3568 / Mediatek GPU drivers on EUR 465 POS AIOs are not a well-trodden path [estimate]. Only matters if question 1 pushes you toward Flutter. Verify: one afternoon with a Flutter sample on a D3 Mini and an Elo I-Series 4.
  4. Do we sell through acquirers on PAX/Ingenico estates? If yes, PAXSTORE distribution and acquirer whitelisting become a 2–6 month business-development dependency [estimate] that gates your entire release process, and sideloading stops being an option. If no, Sunmi + a separate terminal keeps you in control. Verify: one conversation each with a PL and a UA acquirer about third-party app onboarding, before designing the release pipeline.
  5. EAA scope: who is the "service provider" for a white-label ordering page? If it's us, the microenterprise exemption never applies and every guest surface must be EN 301 549 conformant from day one (+1.5 eng-months and an ongoing audit obligation). If it's the restaurant, most single-site cafés are exempt and we can ship a lighter v1. Verify with counsel — this is a 2-hour question with a 6-figure answer.
  6. MDM: build or buy, and at what scale does it flip? At 6 devices/site, Esper is EUR ~22/site/mo against a EUR 79–129 subscription. Our own agent costs ~2–3 eng-months up front. The flip point is roughly 150–250 sites [estimate]. Verify: get real quotes from Esper, Hexnode, and Scalefusion at 500 and 2,000 device commitments — published list prices are not what you'll pay.