Bookodi Mobile — Android & iOS Apps
Native Android and iOS apps for a live restaurant dashboard that was built for laptops — fitted to every phone and tablet without touching the web code.
- Role
- Sole engineer — shells, page layer, layout audit, tests, CI, docs
- Team
- Solo, AI-assisted
- Timeline
- Oct 2026
- Organisation
- Bookodi (XEOO)
- Code
- Proprietary (employer)
- Reading time
- 6 min
A Capacitor 8 shell that runs the live Bookodi dashboard inside native Android and iOS apps, with a page layer injected before the web app loads and a mobile layout layer. A layout audit across 123 page states found 915 problems on the website and none in the app.
Overview
Restaurant owners and staff wanted Bookodi on their phones and tablets — the same dashboard, the same account and the same data as the website. The dashboard had been designed for laptops: on a phone the POS panels overlapped, order cards ran off the screen and dialogs hid their buttons in landscape.
Bookodi Mobile is a native app for Android and iOS that loads the live dashboard, adds what a phone app needs, and fits every page to small screens — while the web repository stays untouched.
The problem
- The apps had to be exactly the website: every web deploy should reach them immediately, with no separate rebuild of around twenty dashboard pages.
- The dashboard broke on small screens in portrait and landscape.
- Payments had to stay on the website, in line with app-store rules.
- Native expectations — safe areas, the back button, downloads, links, staying awake on the kitchen screen — had to work on both platforms.
Context & constraints
The web app and its API belong to a separate repository and team, so nothing could change there. I had no Mac with Xcode: iOS had to be built and tested entirely in CI.
Approach
- Native shells — Java on Android, Swift on iOS, both on Capacitor 8. They provide the launch screen, a connection screen that retries by itself, crash recovery, safe-area handling with system bars in the page's own colours, downloads, an in-app browser and the Android back button.
- Page layer — a TypeScript bundle injected at document start, before the web app's own scripts, and only on the Bookodi origin. It guards payments, hands files to the native side, routes new-tab links, keeps the screen awake on the Kitchen, Waiter and POS pages and tells the shell when the page has really drawn.
- Layout layer — six stylesheets scoped to the app and to each route, switched off automatically once the website ships the same fixes. Every fix is written up with the matching change for the web team: 65 write-ups.
- Proof — an automated audit that renders every dashboard page in both browser engines and checks for sideways scrolling, clipping, overlap and overflowing text.
Key capabilities
- The live web app in a native shell — web deploys reach the apps instantly
- Every dashboard page fitted to phones and tablets, portrait and landscape
- Branded launch screen that lifts only when the dashboard has drawn
- Connection screen with automatic retry and renderer-crash recovery
- Payments kept on the website, with platform-appropriate notices
- Exports to Downloads on Android and the share sheet on iOS
- Screen kept awake on the Kitchen, Waiter and POS pages
- iPad Split View and Slide Over
Architecture
Native shell
Injected at document start
Live product
Verification
The native shell loads the live dashboard; a page layer injected before the app’s own scripts adds native features and the mobile layout, and talks to the shell over a small bridge. Select a component to trace its connections.
Key decisions
ADR-01A live-web shell, not a native rewrite
- Context
- The requirement was “exactly the same as bookodi.com”. Rebuilding about twenty dashboard pages in React Native or Flutter would diverge on day one and double every future change.
- Decision
- Load the live web app in a Capacitor 8 shell and add native behaviour around it.
- Trade-offs
- The apps need a connection and can’t use Web Push, and app-store reviewers must be shown the native value — handled in the review notes.
ADR-02Fix the layout from outside the web code
- Context
- The dashboard was built for laptops, and its repository had to stay untouched.
- Decision
- Inject CSS scoped to the app and to each route, applied only in the media queries that need it, and document every fix with the exact change for the web team.
- Trade-offs
- Selectors depend on the web app’s markup, so rules are written to fail safe — and the layer is an interim measure until the website adopts the fixes.
ADR-03Guard payments at two levels
- Context
- Subscriptions must be bought on the website, and app-store rules apply.
- Decision
- Wrap fetch at document start so billing calls are answered locally with an inline notice, and block the payment provider’s pages natively as a second defence.
- Trade-offs
- The guard must run before the web app’s scripts — which is why the page layer is injected at document start.
ADR-04Always load the latest deploy
- Context
- Serving a cached app shell would start faster, but an old shell can point at assets a newer deploy has deleted.
- Decision
- Revalidate the page on every launch while hashed assets are cached for a year.
- Trade-offs
- Slower cold starts on poor networks in exchange for never running a stale version.
Challenges
A laptop dashboard on a phone
Problem. POS panels overlapped, order cards and charts ran off the edge, menu text wrapped one word per line, figures were cut short and dialog buttons disappeared in landscape.
Solution. The scoped layout layer, measured by an audit of every page state in both browser engines and reviewed screenshot by screenshot.
Building iOS without a Mac
Problem. No Xcode was available locally, and Capacitor’s iOS internals limit how safe areas and injected scripts can be customised.
Solution. Swift is type-checked locally against UIKit, while real builds and iPhone/iPad simulator smoke tests run on GitHub’s macOS runners. A container root view handles safe areas; the user script is added after Capacitor’s own bridge scripts.
An embedded preview sharing storage
Problem. A same-origin preview frame shared the dashboard’s storage, so closing it could sign the owner out.
Solution. The page layer gives preview frames their own in-memory storage.
A launch that never flashes
Problem. The splash screen should lift onto a finished page — not a spinner — and survive server errors during deploys.
Solution. A readiness signal from the page layer, a 20-second safety timeout, and a WebView client that sends failures to the connection screen and returns to the same page when the network comes back.
Quality & delivery
Testing
- Layout audit: 20 dashboard pages plus sign-in and sign-up, 123 page states, 23 screens, in Chromium (Android WebView) and WebKit (iOS) — 2,655 page-state checks. On the 14 core screens the website showed 915 real layout problems; the app showed none.
- Android end-to-end: 14 of 14 passed on an Android 16 emulator — sign-in, auto sign-in, background resume, payments, back button, Excel download, Custom Tab, file picker, bar recolouring, dialer, landscape rotation and offline recovery.
- 19 unit tests (Vitest) for the page layer.
- CI: type-check, lint and unit tests; Android lint and release builds with an emulator smoke test against the live site; iOS simulator and device builds with smoke tests on an iPhone and an iPad simulator in both orientations.
- A private local copy of the platform in Docker, so no test ever touches a real restaurant’s data.
Deployment
A signed APK for testers and an AAB for Google Play are produced locally; the signing key never enters the repository, and CI signs only when its secrets are present. iOS builds come from CI. Release hardening: minified, WebView debugging off, HTTPS only, two network permissions and nothing else.
Outcome
- Layout problems, website vs app
- 915 → 0
- 14 core screens, both browser engines
- Page-state checks
- 2,655
- 23 screens × 123 page states
- Android end-to-end checks passed
- 14 / 14
- Android 16 emulator
- Resume from background
- 0.13 s
- Debug build, measured on an emulator
Version 1.0.0 was delivered in October 2026: a signed Android APK, an AAB ready for Google Play, and iOS builds verified in CI. Store publishing waits on the company’s Google Play Console and Apple Developer accounts; App Links and push notifications are the next phase.
Lessons
- When “the same as the website” is the requirement, the website is the product — wrap it, measure it, and send the fixes upstream.
- An audit that counts problems turns “looks fine on my phone” into a number nobody can argue with.
- CI can stand in for hardware you don’t have — even an entire operating system.
Building something similar?
Tell me what you’re working on — I reply to every serious message.
Related work
Bookodi Desktop & Print Bridge
Native desktop app for the Bookodi restaurant platform with a built-in Print Bridge: it pairs with the cloud in one click, discovers printers on the local network and delivers server-rendered tickets over raw TCP, IPP, LPD, CUPS or the Windows spooler.
- TypeScript
- Vite
- Electron
- Node.js
- WebSockets
- GitHub Actions
- +3
Bookodi TestBot — Live Demo & End-to-End QA Bot
Playwright drives up to four real Chrome windows — owner, guest phone, kitchen display and waiter phone — through 21 chapters, from sign-up to the end-of-day report. It prints real tickets through its own Print Bridge and measures how much of the 180-endpoint API each run exercises.
- TypeScript
- Node.js
- WebSockets
- LLM APIs
- Printing protocols
- Playwright