What's new in alpha.153 · A layout made for tablets
Turn your tablet sideways for a whole new view. In landscape, the app now uses the full screen — a side menu on the left, your orders in the middle, and the selected order's details on the right, all at once. Tap an order to see its items without leaving the list. Phones and upright tablets keep the layout you already know.
Settings, tidied up. Each settings section is now a card you tap to expand, so it's no longer one long scroll. The end-of-day report stays visible with its print options tucked away until you need them, and each store now shows only the order services it actually offers — with a Refresh to pick up any you switch on from the dashboard.
Test an order from any screen. A new Test order button in the top bar sends a test order through the whole flow — the alarm, the new-order screen, and printing — so you can check your setup end to end. It's clearly marked as a test and never counts as a real order.
From alpha.152 · Live courier tracking
Follow the driver without leaving the order. For deliveries handled by a courier service, the order screen now shows the driver's progress as it happens — dispatched, driver assigned, on the way, delivered — with the driver's name, one-tap calling, and the live tracking map right there in the order. Delivered orders collapse to a tidy one-line summary.
The app no longer asks you to do the courier's job. When a courier is handling the delivery, your part finishes at Mark Ready. The "Out for delivery" and "Complete" buttons now disappear for those orders, because the courier reports those steps themselves — previously tapping them just failed. If you ever need to close one of these orders yourself, use Override status in the order's menu.
Quieter driver updates. Driver progress now arrives as a silent notification, so it never rings like a new order. If an automatic dispatch fails, you'll see a clear message and a Retry dispatch button.
From alpha.151 · Your prep times, everywhere they matter
The prep time buttons now match what you set up. The new-order screen used to show a fixed set of prep time buttons that ignored the prep times configured for your store — so changing them had no effect on the terminal. It now shows your prep times, per service, and your default is pre-selected and highlighted. Pickup, delivery and dine-in each use their own list and their own default; previously delivery and dine-in orders were shown the pickup default by mistake.
From alpha.150 · Text receipts on the built-in printer
Text templates now print as text on Sunmi terminals. If you chose a text-style receipt template, the built-in printer on Sunmi devices still printed the image-style receipt instead. It now prints the text version you selected — which is also noticeably faster. Image templates are unchanged.
From alpha.149 · Order alerts on every screen
New orders now ring and print no matter which screen you're on. The new-order alert only existed on the Home screen, so if staff were viewing an order or in Settings when an order came in, there was no sound and no docket — silently. The alert now sits above the whole app and works everywhere.
Orders confirmed automatically now print too. If your store confirms orders automatically on a timer, the server could confirm an order before anyone tapped Accept — and the kitchen docket never printed. Those orders are now picked up and printed. (For security, the alert still doesn't appear on the PIN lock screen — those play a sound instead.)
From alpha.148 · More reliable order alerts
Stop missing orders when the app is in the background. Some terminals (especially Sunmi) on "Optimised" battery mode let Android throttle or kill the app in the background, so new orders could arrive with no ring and no print until someone opened the app. The app now asks — with a clear explanation first — to be exempted from battery optimisation, so it stays awake to receive orders. There's also a Battery optimization status row in Settings → Diagnostics showing whether the exemption is active, with a one-tap fix.
From alpha.147 · Live delivery tracking
Follow your delivery orders in real time. When a delivery order is auto-dispatched to a courier, its details now show a live tracking card: a status timeline (dispatched → driver assigned → on the way → delivered), the courier's name with one-tap calling, and a Live tracking button that opens the courier's live map. Once delivered, it collapses to a tidy summary. Only appears for stores set up for automatic courier dispatch.
From alpha.146 · Faster, reliable Bluetooth receipts
Image receipts print faster. Designed (image-mode) receipts used to download a large image and process it on the device before printing — slow, especially on Bluetooth printers. The app now fetches the receipt already prepared for the printer from the server and streams it straight through, so it starts printing sooner. It looks exactly the same as before — the same design from your dashboard template.
No more garbled Bluetooth receipts. The app now sends to Bluetooth printers at a steady speed matched to how fast the printer can actually print, so the printer's buffer can't be flooded — the cause of the jumbled text on long receipts. There's a Bluetooth print speed setting (Safe / Default / Fast / Max); Default just works on most printers, and Max uses the printer's own flow control for top speed if it supports it.
From alpha.140 · Pause individual add-ons
Run out of one topping? Pause just that add-on. You can now pause a single add-on or choice — like extra cheese or a gluten-free base — instead of the whole item. A new Add-ons tab on the Menu screen lists every choice; pause one there and it's marked sold out on every item that offers it, so you only do it once. Pauses can run for a few hours, the rest of today, until tomorrow, or a custom date and time you pick — and they lift automatically. Custom times now work when pausing items and sizes too.
From alpha.139 · A distinct chime for customer messages & an in-app update prompt
Customer messages now have their own bell. A new customer message used to play the same sound as a new order and chimed just once, so the two were easy to confuse. It now has a dedicated "Message Tone" — a different bell that keeps ringing until you open the chat and read every message, so you won't miss one during a busy service. (It's sound-only; it won't vibrate, and it never rings over a new-order alert.)
Know when an update is ready. When a newer build is available on the Play Store, a banner now appears at the top of the app. Tap Update to download it in the background, then Restart to install — no need to go hunting in the Play Store. (Side-loaded builds from this page are unaffected — the banner simply doesn't show.)
From alpha.138 · Background chat push notifications
Customer messages now reach you even when the app is closed. When a customer messages about their order, the device now gets a push notification on a dedicated "Customer Messages" alert — even if the app is backgrounded or shut. Tap it to jump straight to that order's chat. While the app is open, the in-app bubble and chime now appear within about a second instead of waiting for the next refresh.
Note: requires the matching server update to be live. Allow notifications when prompted so background alerts can show.
From alpha.137 · Live customer chat alerts & meal deal pricing
Chat bubble + chime when customers message you. When a customer sends a message from their order tracking page, a notification bubble now pops up above the bottom tab bar showing their name, order number, and message preview, and a chime plays so you don't miss it. Tap it to go straight to that order with the chat already open — or dismiss it with the ✕. (This is checked every 15 seconds while the app is open. A background push when the app is closed is still coming.)
Meal deal pricing is now clear. Meal deals show the full deal price on the "Meal Deal" header, and each item underneath shows "Incl." (covered by the deal) or "+$X" for any paid extras — in the order screen, the new-order popup, and on the kitchen prep ticket. No more confusing per-item prices that didn't add up.
From alpha.136 · Meal deal grouping
Meal deal items grouped in the app. Meal deal items now appear as a group in both the order detail screen and the new order takeover, with a "Meal Deal" header instead of showing as separate flat items.
From alpha.135 · EOD breakdown, meal deal dockets & smarter scheduled orders
EOD full breakdown. The End-of-Day section now shows payment breakdown (Cash / Card / Online) and service breakdown (Pickup / Delivery / Dine in), plus a revenue detail section with subtotal, fees, discounts, tips, and tax. After 1 minute without refreshing, the section dims and shows a large "Refresh data" button so you always know whether you're looking at live figures.
Meal deals print as one docket. Items in a meal deal now group onto a single prep ticket (labelled MEAL DEAL) instead of printing as separate individual tickets. Each item in the deal is still listed clearly on the combined slip.
Scheduled orders respect business hours. Scheduled order takeover screens no longer appear outside your operating hours — they queue silently and surface when the store opens, so the tablet isn't interrupted overnight.
From alpha.134 · Business hours & resume-all
Knows when you're closed. Outside your opening hours the header now shows "Outside hours" (slate) instead of a green Open, and the Pause option is disabled — there's nothing to pause when you're already closed.
Resume everything at once. When items are paused, the Menu shows a "Resume all" banner that lists exactly what will come back, then resumes it in one tap.
From alpha.130 · New look — rolling UI redesign (build 1)
New navigation. The bottom tabs are now a cleaner floating bar, kept centred so it stays tidy on larger countertop terminals as well as handhelds. This is the first of several UI refresh builds — more screens get the new look in upcoming builds. Everything else works exactly as before.
Preview build — please test on your device and tell us if anything looks off.
From alpha.128 · Server-side receipt rendering
Receipts now print exactly what you designed in the dashboard — always. The app no longer generates receipt layout on-device. Instead it asks the server to render each receipt and streams the result straight to the printer. For image-mode templates (the default) this is unchanged. For text-mode templates the server now produces the ESC/POS bytes directly, so the printed receipt matches the live preview in your dashboard pixel-for-pixel, regardless of which device or printer is being used. The in-app Receipt Designer and the per-printer Image/Text format switch have been removed — receipt format is now controlled entirely from the dashboard template's render mode.
From alpha.127 · Pause timing, clearer prep tickets & a safer unpair
Pause all items, for how long you choose. Pausing a whole category used to silently pause for 1 hour. Now it opens the same time picker as pausing a single item — Rest of today, 2 hours, 4 hours, or Tomorrow.
Hide sizes on prep tickets. A new per-printer option in Settings → Printers (under prep tickets) lets the kitchen slip leave off the size/variant line when you don't need it called out. Half-and-half splits and required choices still print.
Store name on every prep ticket. Each prep ticket now prints your store's name in a bold banner across the top, so a slip is easy to place at a glance.
Bigger scheduled-order times. On the scheduled-order alert screens the time is now much larger and brighter, so it's easy to read at a distance.
Safer unpair. Unpairing a device now requires a valid staff PIN — a wrong code is properly rejected instead of sometimes being accepted.
From alpha.123 · Bluetooth printing fix — no more garbled receipts
Fixes Bluetooth printing on a real printer. Receipts were printing in slow stop-start bursts, and a full order could come out as gibberish on a long roll of paper. The app was sending the whole receipt to the printer in one burst, which overran the printer's small memory buffer and corrupted the print. It now sends the receipt in small, paced pieces so the printer keeps up — receipts print cleanly and steadily.
From alpha.122 · Print to Bluetooth printers
You can now print to a paired Bluetooth thermal printer. Before, picking a Bluetooth printer let you add it but nothing actually printed — the app only knew how to talk to network (WiFi) and the Sunmi built-in printer. Now receipts, kitchen prep tickets, the End-of-Day report and the test print all go out over Bluetooth too. Pair the printer once in your device's Bluetooth settings, then add it in Settings → Printers → Bluetooth, send a test print to confirm it's the right one, and save. Auto-print on accept works the same as it does for WiFi printers.
If a Bluetooth print fails, check the printer is on, in range, and still paired in your Bluetooth settings.
From alpha.121 · Orders print when you accept — with the right times
Tickets now print the moment you accept an order, not the moment it arrives — so the kitchen ticket and customer receipt always show the confirmed prep/ready time and full order details, never a blank or too-early time. Orders you reject no longer waste paper. Scheduled orders are handled too: acknowledging one prints a receipt right away as your record, and the kitchen prep ticket prints later, when it's time to start cooking.
From alpha.120 · Required picks first, optional last, blank lines between
Prep tickets now group an item's lines so the cook reads them in the order the kitchen needs them. Size (variant) and required modifiers — the meat option, the must-pick choice — print at the top right under the item name. Then a blank line, then the base ingredients, then a blank line, then optional add-ons and "No <ingredient>" requests. Notes still print last. Each section is separated by a blank line for breathing room.
Required vs optional is detected heuristically — a modifier with no extra price and not a "No X" line counts as required. The API doesn't expose a required flag yet, so this works best in stores where mandatory choices are included in the base price.
From alpha.119 · Cleaner prep tickets when an ingredient is removed
When a customer removes an ingredient, the prep ticket now just leaves it off the list instead of printing a "NO <ingredient>" line. The cook sees only what should actually be on the plate — no extra noise to read past. (Only applies when the item is flagged "Print ingredients on kitchen ticket" in the Dashboard; items not flagged still show "NO <ingredient>" as their only signal that something changed.)
From alpha.118 · Bigger, more readable prep tickets
Prep tickets now print at a size the kitchen can read from across the room. The item name is double-width and double-height, and every line underneath it — variant, ingredients, modifiers, removed items, notes, plus the order number, type and customer name in the header — is double-height. No more squinting at the build line.
From alpha.117 · Per-product ingredient printing
Choose per menu item whether its description prints as an ingredient list on the kitchen prep ticket. The previous device-wide toggle is gone — flag items individually in the Dashboard (Menu → product editor → "Print ingredients on kitchen ticket"). Items not flagged print no ingredients, so descriptions that are choices or options (e.g. a snack pack) stop printing as nonsense.
From alpha.116 · List each item's ingredients on prep tickets
- Print what's in every item, line by line. Turn on Settings → Printers → "Print ingredients on prep tickets" and each prep ticket lists the item's ingredients underneath its name — one per line — so the kitchen sees the full build at a glance. For a Traditional Kebab it prints the name, then Lettuce / tomato / onion / garlic sauce, each on its own line, followed by any added extras. The ingredients come straight from each product's menu description.
- Removed items stay visible, marked "NO". If a customer leaves something off, that ingredient still appears in the list, flagged NO in bold (e.g. NO tomato) and kept in place — so the prepper reads the exact, accurate build without hunting for what changed.
- It's one device-wide switch that applies to every prep ticket this device prints, on top of the per-printer receipt / prep-only / both setting. Leave it off and prep tickets print exactly as before.
From alpha.114 · Choose what each printer prints
- Set prep tickets per printer, not per device. When you add or edit a printer you now choose what it prints: the customer receipt, prep tickets only (one ticket per item, no receipt — for a dedicated kitchen printer), or both. So the counter printer can keep printing receipts while the kitchen printer prints a separate docket for every item — all from the same device. Change it any time from the printer's options.
From alpha.113 · A separate kitchen ticket for every item
- One docket per item, cut and ready to cook. Turn on Settings → Operations → "Separate prep ticket per item" and every new order also prints a clean kitchen slip for each item — one per portion, so three of the same dish prints three identical slips. Each shows just the item name and its choices (no prices, no totals) and is cut on its own, so the kitchen can grab one slip per thing to make. Reprint them any time from the order's screen. It's a per-device switch: turn it on for the kitchen terminal, leave it off at the counter.
From alpha.112 · Cleaner order alarm
- Each alert tone now plays all the way through. The repeating order alarm used to start the next ring on a fixed timer, cutting off longer alert sounds and overlapping them. Now every ring finishes before the next begins, with a short, even gap between them.
From alpha.111 · New-order screen pops in time with the alarm
- No more ringing over a blank screen. When a new order came in, the alert could start sounding a few seconds before the full-screen order takeover actually appeared. The app now jumps on the order the instant the alert arrives, so the takeover screen and its alarm come up together — for both immediate and scheduled orders.
From alpha.110 · Scheduled orders stand out in the list
- A "Scheduled order" banner on every pre-order card. Scheduled orders now wear a full-width yellow Scheduled order banner across the top of their card, so you can spot a pre-order at a glance while scrolling the list — no need to read the times.
From alpha.109 · Scheduled orders always get a heads-up
- No more silent pre-orders. Some setups confirm a scheduled order the instant it's placed, which meant it could slip straight into your list with no alert. Now those still pop the blue Scheduled Order screen with the alarm, so your staff see every pre-order as it comes in — not just a quiet row in the list. (A matching server-side change to formally hold pre-orders for acknowledgement is in progress.)
From alpha.108 · Scheduled orders, reworked
- Orders placed while you're closed wait quietly. A scheduled order that comes in outside opening hours no longer sounds the alarm or throws up a full-screen takeover — it just lands in your order list. The moment you re-open the store, a single blue Scheduled Orders screen lists every waiting pre-order (whatever day they're for) with one Acknowledge all button that confirms them in one tap
- A pre-order during service gets its own screen. When a scheduled order arrives while you're open, you get a blue Scheduled Order takeover showing the time, customer, type and total, with a single Acknowledge button. No auto-accept countdown — scheduled orders are never auto-accepted, so nothing slips into the kitchen before you've seen it
- A nudge when it's time to cook. Once a confirmed pre-order gets close to its prep window, an amber Time to prepare screen pops up with a 10-second Start preparing button that fills up and starts the order automatically — or tap it to start now, or Skip to dismiss. Starting takes you straight to the order so the kitchen can get going
From alpha.107 · Customise what prints on the EOD report
- Choose which sections print. The End-of-Day card in Settings now has a "What prints" list with a switch for each section — Summary, Revenue, Payment breakdown, Order types, Top sellers and Busiest hour. Turn off what you don't need and it's left off the printed report; the store name, date and print time always stay on
- Set it per device. Each terminal remembers its own choice, so a kitchen printer can run a short summary while the counter prints the full breakdown. Your selection applies to both the manual Print button and the daily auto-print
From alpha.106 · No more double order sound
- One sound, not two. A new order could play two alerts at once: the app's own alarm AND a separate Android system ringtone from the push notification. While the app is open you now hear only the in-app alarm — the duplicate system ringtone is suppressed
- The system ringtone now actually stops. The push notification's ringtone is played by Android, not the app, so the in-app silence/accept couldn't stop it. Accepting, dismissing, or rejecting an order now cancels that notification — which stops its sound. Repeat pushes for the same order no longer re-ring or pile up
From alpha.105 · Scheduled-order alarm fix + alarm failsafe
- Scheduled orders no longer alarm or auto-accept as if immediate. A future order could slip into the full-screen new-order alarm and even auto-accept, because the app trusted a single flag the server sometimes sent wrong. The app now also checks the order's scheduled time: anything scheduled for the future never triggers the alarm or the auto-accept countdown, and always shows the Scheduled banner. Orders that are due now still behave as before
- The alarm can no longer ring forever. The looping order sound now self-stops after 2 minutes as a failsafe, so a stuck alarm can't keep sounding indefinitely (the order stays on screen). Background alerting is unchanged
From alpha.104 · View End-of-Day report for any day + print fix
- View any day's End-of-Day report. The EOD card now has ‹ › day arrows. It opens on Today (live, up to now) and you can step back to any past day to see that day's orders, revenue and totals. The forward arrow stops at Today — you can't go into the future
- Printing always uses the latest figures. The Print button used to print whatever was loaded when you opened Settings, so a report opened before new orders arrived could print zeros. It now fetches the day you're viewing fresh before printing, and prints exactly that day
From alpha.103 · Auto-accept timer picker no longer snaps back
- You can now change the timer freely. Picking a new duration used to save once and then refuse to budge — the highlight snapped back to the default. The save endpoint persists the value but doesn't echo it back in its response, so the screen was overwriting your choice with a blank. The app now keeps the value you picked
From alpha.102 · Auto-accept timer fix + per-service auto-accept
- Auto-accept timer now actually works. Previously the new-order countdown always used a hardcoded 30 seconds no matter what you set in Settings — the configured value never reached the takeover screen. The app now reads your real timer and refreshes it every poll, so changes take effect within ~15 seconds without an app restart
- Auto-accept is now per service. The countdown only auto-accepts an order if that order's service (Pickup / Delivery / Dine-in) has its Auto-accept orders toggle on. Turning a service's toggle off means its orders are never auto-accepted
- Timer moved into each service card. The duration picker (30s / 60s / 90s / 2m) now appears right under a service's Auto-accept toggle, and only when that toggle is on. Enabling auto-accept seeds a sensible 30s default if no timer was set
From alpha.100 · Language selector + auto-detect
- Language selector on auth screens. A globe pill (EN/DE) in the top-right of both the pairing and PIN screens lets merchants switch language manually. Tap → pick from dropdown → screen reloads in the selected language instantly
- Auto-detect device language. On first launch, the app reads the device's system language. If the device is set to German, the pairing screen shows in German automatically — zero taps needed
- PIN screen shows store language. After pairing, the PIN screen renders in the store's language (seeded from persisted device auth at startup). No more English-only login for German merchants
- Locale persisted across restarts. Timezone and currency saved alongside locale after each store-status poll for full cold-start accuracy
From alpha.98 · Sunmi EOD print fix
- Sunmi EOD blank page fixed. End-of-Day report on Sunmi printers for German stores was printing a blank page. The ESC/POS code-page command confused the Sunmi parser. Now skipped on Sunmi with German characters transliterated to ASCII (ä→ae, ö→oe, etc.)
From alpha.97 · EU pairing + German locale deep pass
- Multi-region pairing. The app now silently tries each region when pairing (AU then EU). EU merchants (e.g. Liefermioo) enter their 6-digit code and it just works — no region picker needed. The winning region is pinned for all future API calls
- German locale deep pass (18 fixes). Currency formatting uses store locale everywhere (no more $ signs on EU stores). Decimal inputs accept commas. Unit suffixes (m/h/hr) localised to Min./Std. Payment method and status labels expanded (online, Apple Pay, Google Pay). Printer wizard placeholder, status fallbacks, and delay chips all localised
- Done tab optimised. Shows last 2 days of orders by default (was all history). Search expands to full history. Separate polling cadence (60s vs 15s for active orders) to reduce data transfer
- Receipt printing. German umlauts print correctly (ISO-8859-1 + CP1252 codepage). 24h time on EU receipts. Missing German strings added
From alpha.95 · Demo pairing + driver map
- Demo token pairing. A "Paste demo token" field now appears on the pairing screen alongside the 6-digit code. Paste the token from Dashboard → POS Devices → Demo Link to pair without a time-limited code — ideal for app-store reviewers
- Driver markers on map. Live driver pins on the Mapbox map, color-coded by status (idle=amber, assigned=blue, delivering=green). Tap a pin for a driver popup card with vehicle info and last-seen time
- Real GPS positions. Driver locations from live GPS data; falls back to approximated positions only when no GPS is available
- PII fix. Order detail no longer shows
enc:… strings for customer name and phone number
From alpha.92 · Compact UI redesign
- Compact header. Store status header compressed to a single row. Stats removed from header, replaced with a chart icon that opens the Insights sheet
- Merged tab + toolbar. Order tabs (New/Active/Done) and search/filter icons now share one row instead of two separate rows. ~13% more screen for order cards
- Full-bleed map. Map screen redesigned with BottomSheetScaffold. Map fills ~85% of the screen (was ~35%). Floating filter chips with integrated legend, compact delivery cards in a bottom sheet
- Map crash fix. Tapping the store pin no longer crashes (JsonNull guard)
- Store coordinates from API. Map centers on the actual store location instead of hardcoded Sydney defaults
From alpha.90 · Deep i18n completion
- Snackbar messages localized. "Marked ready", "Undo", bulk operation results ("5 orders updated", "Some updates failed") now translate to German
- Notification fallbacks localized. FCM "New Order" / "You have a new order" fallback text and the background alert channel name/description now use string resources
- Insights cancellation buckets localized. "Out of stock", "Too busy", "Closing", "Equipment", "Other" in the cancellations breakdown now translate
- Printer test slips localized. Sunmi test page ("PRINTER TEST", "Model:", "Serial:") and network test print ("IP:", "Port:") labels now follow store locale
- Accessibility content descriptions localized. "Call [phone]" on map and order detail phone buttons now uses string resources
- ApiError fallback fixed. displayMessage now returns null when the server sent no text, so ViewModel localized fallbacks actually fire instead of showing English "Something went wrong"
From alpha.89 · Map label fix
- Map "on the map" label localized. The delivery count label ("1 on the map", "3 of 5 on the map") was still hardcoded English. Now shows "1 auf der Karte" / "3 von 5 auf der Karte" in German
From alpha.88 · ViewModel + network layer i18n
- All ViewModel error messages localized. Every error fallback, status message, and snackbar text across 8 ViewModels (Settings, Orders, OrderDetail, Menu, Map, Pairing, Pin, Main) now uses getString(R.string.*) instead of hardcoded English
- Print status messages localized. "Test sent to X", "Receipt sent to X", "EOD printed on N printer(s)", "Print failed" messages now translate to German
- Push notification status localized. "Push notifications enabled/disabled", FCM token errors, push setup failures all use string resources
- EOD print errors localized. "Refresh the report first", "Add a printer before printing", "No printers responded" messages now translate
- Printer wizard + test errors localized. Sunmi cover/paper hints and WiFi connectivity hints use string resources
- Receipt "ETA" label fixed. German text-mode receipts showed "Fahrer-ETA:" (English acronym). Now shows "Ankunftszeit:" (native German)
- 40+ new string resources. Added to both values/strings.xml and values-de/strings.xml for complete coverage
From alpha.87 · Deep i18n audit
- Insights "Picking up" translated. The pace label in the insights sheet was still English in German locale. Now shows "Zieht an"
- Pause store preview localized. The customer-facing preview message in the pause modal was hardcoded English ("We're slammed right now", "back soon"). Now uses locale-aware string resources
- Tomorrow abbreviation localized. The upcoming-orders strip showed "Tom 6:00 PM" (English). Now shows "Morg. 18:00" in German
- Notification channel localized. Android notification channel name was hardcoded "Order Alerts". Now uses string resources
- PinPad accessibility. Backspace content description was hardcoded English. Now localized
- Dead code removed. Removed unused hardcoded English tips list from TipOfTheDay (string-array resource was already being used)
From alpha.86 · EOD German label fix
- EOD "BESTSELLER" → "MEISTVERKAUFT". Top Sellers section header on printed EOD used "BESTSELLER". Now uses native German "MEISTVERKAUFT"
From alpha.85 · Unpair fix + currency + status labels
- Settings unpair fixed. Unpairing from Settings was broken (showed "Required" error). Now passes the manager PIN correctly
- Currency symbol fixed. Stats header was showing "AU$" for non-AUD stores. Now reads currency from the status DTO
- Status labels translated. "Preparing", "Delivering", "Ready" on order cards were hardcoded English. Now use locale-aware string resources
From alpha.84 · Deep i18n sweep + EOD tax breakdown
- 100+ additional strings localized. Second pass caught every remaining English text: tours/walkthroughs (36 steps), order detail dialogs (cancel/refund/late), status pills, map screen (filter chips, ETA labels, empty states), push notification onboarding, no-internet dialog, tips of the day (20 tips as string-array), foreground notification
- EOD tax breakdown. Stores with multiple tax rates (e.g. 7% + 19% MwSt) now see each rate as a separate line on the printed EOD report instead of a single "Tax" total
- EOD printing on Sunmi fixed. The End-of-Day report now prints on Sunmi built-in printers (was only working on network printers)
- Currency formatting uses locale. Hardcoded "$" replaced with
NumberFormat.getCurrencyInstance() across Settings and Map screens
From alpha.83 · Full i18n + translated receipts
- 4 locales: en-AU, de-DE, en-GB, en-US. Every UI string (365 keys + 1 plural) extracted to Android resources with complete German translation. Locale is driven by tenant config from the API — no manual device setting needed
- Locale-aware money and time formatting. Currency symbol, decimal separator, and grouping all follow the store's locale. Non-ASCII currency symbols (EUR, GBP) replaced with ISO codes on thermal printer output for hardware safety
- Translated receipt printing. Text-mode ESC/POS fallback receipts (order receipts, test prints, quick-identify prints, EOD reports) now render in the store's language. German stores see "BESTELLUNG", "Gesamt:", "Vielen Dank!", "TAGESABSCHLUSS" etc. on paper
- All 13 screens wired. Orders, Order Detail, New Order Takeover, Menu, Map, Settings, Add Printer Wizard, PIN login, Pairing — every hardcoded English string replaced with
stringResource()
From alpha.81 · Brand leak audit (white-label safety)
- Diagnostics "API base" row removed. Was printing the raw
api.spatula.is hostname which is a brand leak for resellers white-labelling the app. Replaced with a generic "Connection: Online" row — the actually-useful signal for ops debugging
- Text-mode receipt "Order online" line de-branded. The legacy ESC/POS-text fallback path hardcoded
"Order online: order.spatula.is". Now prints just "Order online" — the normal (server-rendered PNG) path was already using the store's own storefront URL from config and was never affected
From alpha.80 · Orders toolbar slimmed down
- Scheduled tab removed. The dedicated tab is gone — scheduled orders surface through three other paths now: the alpha.65 Upcoming strip (chip row at the top showing every scheduled order due in the lookahead window), the alpha.66 auto-promote (pending scheduled orders within the promote window appear in New automatically), and the new "Scheduled" option in the filter sheet. Tab bar is now just New / Active / Done
- Search is now an icon. Tap the search icon in the toolbar and the search field slides down inline. Tap the X (or the icon again) to collapse. Saves ~64dp of permanent chrome when the merchant isn't searching
- Filter chips moved into a bottom sheet. Tap the filter icon → sheet opens with All / Pickup / Delivery / Dine-in / Overdue / Scheduled. Currently-applied filter shows as a small pill on the left of the toolbar with an X to clear. The filter icon also tints + shows a dot badge when active so the state is visible at a glance
- Net result: the Orders chrome dropped from ~270dp to ~160dp before the orders list starts. The merchant now sees one or two more cards on a typical phone without scrolling
From alpha.79 · Sunmi prints via sendRAWData (same fast path as network ESC-POS)
- Replaced the slow
printBitmap(Bitmap, ICallback) AIDL call with the same code path the network ESC-POS printer uses: render the template PNG server-side → decode to a Bitmap → convert to ESC/POS raster bytes via BitmapToEscPos → push to the Sunmi head as a single sendRAWData(byte[], ICallback) call
- The slow part of the old path was Sunmi's woyou compat shim doing the bitmap → raster conversion server-side, line-by-line. By doing the conversion in the app and shipping raw bytes, the printer head just streams them straight through — the same way it does for network ESC-POS prints and the same way the existing Relay APK is fast
- Full multi-template + copies parity: a Sunmi printer with
receiptTemplateIds = [kitchen, customer] and printCopies = 2 now renders both templates, concatenates the bytes, and dispatches the combined payload to the head N times — same as a network printer would
- Templates 100% preserved — your custom dashboard templates are still server-rendered as before, just routed through the new transport. Nothing about the template system changes
From alpha.78 · Sunmi printer feature parity + paper-size detection
- Auto-detected paper width at registration time — Sunmi model family → paper size (V-series = 58mm, T/D-series = 80mm). Sunmi V3 now registers as 58mm instead of the previous hardcoded 80mm, which was wasting time on the right half of every bitmap and contributing to the slow printing
- Auto-picked receipt template at creation — we now select the store's first configured template instead of leaving it null. Without a template the render endpoint falls back to the store's "Default" template which is often empty on real stores, producing sparse blank receipts
- Full feature parity with network printers — Sunmi built-in printers now appear in the same Printers list as network printers with identical row controls: auto-print toggle, multi-template picker, copies stepper, kitchen / customer toggles, edit dialog, test, remove. The dedicated Sunmi card now only shows on first-time setup (before the printer is registered) as a one-tap "Add" CTA; after that, manage everything in the main Printers list
- Edit dialog adapts to Sunmi — when editing a sunmi_internal printer, the IP/port fields are hidden (the device knows where its head is) and the validation skips them. Saving safely coerces empty IP / zero port to NULL so the DB CHECK constraint stays satisfied either way
- After installing alpha.78, remove + re-add the Sunmi built-in printer so the auto-detected paper width and auto-picked template both take effect on the DB row
From alpha.77 · Skip cutPaper on V3 (tear-bar hardware)
- Sunmi V3 ships with a manual tear-bar, not a rotating-blade auto-cutter. The
cutPaper AIDL accepts the call but there's no hardware to acknowledge, so the callback never fires — same callback-silently-dropped pattern as printerInit. Drop the call entirely; lineWrap(3) already advances enough paper to tear cleanly above the content
- The previous test slip now reports success instead of "cutPaper timed out 10s" while still printing identically
- Bigger Sunmi terminals (T2, T3) DO have auto-cutters. When we ship to one of those, gate cutPaper on the model returned by
getPrinterModal() instead of dropping unconditionally
From alpha.76 · Skip printerInit on V3
- The alpha.75 diagnostic confirmed Sunmi V3 firmware accepts the
printerInit AIDL call but never fires its callback — the method is essentially a no-op shim on V3. Since printerInit is optional anyway (Sunmi heads auto-initialize on first content op), we just skip it now. Both the test slip flow and the receipt print flow go straight to content ops
- Earlier V1/V2 firmware tolerated the missing init; if any customer reports skewed first-print output we can reintroduce it as a best-effort short-timeout op
From alpha.75 · Sunmi op-level diagnostics
- Each AIDL op now carries a label (
printerInit, setAlignment(center), printText(header), etc.) and the timeout error includes the label, so we can pinpoint exactly which method is hanging
- Added a sync probe (
getPrinterSerialNo) before any async op. Sync calls don't depend on the ICallback path, so if the probe succeeds we know the AIDL link works and any subsequent timeout is in callback handling. If the probe throws or hangs, the binding itself is the problem
- Dispatch errors caught separately from timeouts — "threw during dispatch" vs "timed out" point to different root causes
From alpha.74 · Sunmi AIDL ordering fix
- Real fix for the Sunmi test-print timeout. The alpha.72 AIDL file declared methods in a different order than Sunmi's actual service — AIDL assigns each method a transaction code by declaration position, so calling
printerInit() in our interface was actually triggering printerSelfChecking server-side. The server read malformed parameters, got stuck, the callback never fired, the op timed out at 10 seconds
- Rewrote
IWoyouService.aidl with all 28 methods in Sunmi's exact published order. Methods we don't call are still declared (they're dead weight in generated code — a few hundred bytes — but ABI-correct). Now printerInit lands on printerInit, setAlignment lands on setAlignment, and the callback chain actually completes
From alpha.73 · Sunmi diagnostics
- Test-print error now surfaces the actual exception cause instead of a generic "couldn't reach" hint, so a bind failure / cover-open / AIDL mismatch / paper-out are each distinguishable
- Service binding tries two intent shapes — action+package first (Sunmi's official sample), then bare action without setPackage (works on some older V1/V2 firmware that refuses setPackage on the bind intent). Logs which one succeeded
- Distinct timeout message when the service is installed but didn't connect within 5 seconds vs. when bindService refused both intent shapes
From alpha.72 · Sunmi built-in printer support
- Sunmi built-in printer — every Sunmi terminal (V1 / V2 / V2s / V3 / T2 / T2 Mini etc.) ships with a thermal head behind the woyou.aidlservice.jiuiv5 AIDL service. The app now binds that service and treats it as a first-class printer alongside network ESC-POS printers. Receipts use the same server-rendered PNG template path so output looks pixel-identical across both backends
- Settings → Hardware → Built-in printer — new card appears only on Sunmi devices (PackageManager check gates visibility). Tap "Add built-in printer" once to register it; the dashboard sees it as a regular printer entry. After that the card shows a test-print button, a remove button, and a green/red health dot driven by the existing alpha.40 print-attempt telemetry
- Print router dispatches by
connection_type — EscPosPrinter.printReceipt() now picks the right backend per printer. Network printers continue down the existing TCP/IP path; Sunmi printers route to the new SunmiInternalPrinter. Every existing call site (auto-print on new order, single reprint, bulk reprint, EOD auto-print, test slip) transparently picks up the new behaviour
- Print retry queue (alpha.69) covers both backends — a failed Sunmi print enqueues the same way a failed network print does, and the 30-second retry worker re-tries until the head responds or it gives up after 3 attempts
- Same telemetry — Sunmi prints fire the same
POST /pos/printers/:id/report as network prints, so the dashboard's Store Health page shows them with the same green/red dots and error messages
- Backend changes (migration 0134) widen
printers.connection_type to accept 'sunmi_internal', with a CHECK constraint that requires ip_address for network printers and forbids it for Sunmi rows. A partial unique index allows only one Sunmi entry per store — all terminals at the store share that one logical entry and the device routes locally
From alpha.71 · Sprint 11 (Training — Tip of the day)
- Tip of the day on Settings — a rotating one-line training nudge appears at the top of the Settings screen, surfacing a feature the merchant may not have discovered yet. Rotates daily (deterministic from day-of-year so the same tablet sees the same tip across visits within 24 hours). 20 tips covering bulk selection, undo, kitchen mode, scheduled auto-promote, retry queue, the Insights sheet — the most-recently-shipped features that take some discovering
- Dismissible per day — once tapped to close, hidden until tomorrow. "Next tip" walks the catalogue manually for merchants who want to browse
- Notes: Practice mode and multi-language (i18n) from the original Sprint 11 plan deferred. Practice mode needs a fake-data injection path at the API layer; i18n needs string extraction across the whole Compose UI — both are separate large sprints. Tip-of-the-day delivers the discoverability win in this sprint
From alpha.70 · Sprint 10 (Ops intelligence)
- Pace column in the stats strip — orders created in the last 60 minutes shown as "X/hr" alongside the existing Orders / Revenue / Active. Reads as a leading indicator the merchant can use to spot a rush before the kitchen feels it. Hidden when zero so it doesn't decorate the strip on a quiet shift
- Insights bottom sheet — tap the stats strip to open a modal with three deeper views computed client-side from the same orders pass that powers the strip (no new endpoints):
- Pace meter — current orders/hour with a verdict line ("Picking up — 2.4× usual" / "Steady" / "Slow patch") so the multiplier is interpretable at a glance
- Hourly heat bar — 24 vertical bars showing today's order arrivals by local-time hour. Current hour highlighted in Primary so the merchant can read "where am I in the day" without checking the clock
- Cancellation rollup — total cancellations today + a frequency map of reasons (normalised into "Out of stock" / "Too busy" / "Closing" / "Equipment" buckets). Spot "8 cancelled for Out of stock today" patterns and act on them
- All powered by extensions to
MainViewModel.TodayStats + the 15-second polling pass it already runs. No new endpoints, no extra polling, no client storage
From alpha.69 · Sprint 9 (Print resilience)
- Print retry queue — failed prints (kitchen ticket on auto-print + manual reprint from Orders + bulk reprint) now land in a singleton retry queue instead of just showing a toast and forgetting. A background coroutine retries every 30 seconds up to 3 attempts so a brief WiFi outage doesn't drop a ticket on the floor. App-wide snackbar surfaces the eventual outcome: "Receipt for #123 printed (retry 2)" on success or "Could not print receipt for #123 — check the printer" after the final failure
- Idempotent — a second enqueue for the same (printer, order) doesn't double-queue; instead it resets the attempt counter so the worker treats it fresh. A manual reprint that succeeds also clears the matching pending entry so we don't print the same receipt twice
- In-memory only — a missed print is only useful if it fires within minutes of the order arriving; persisting across app kills risks printing yesterday's order this morning. Compatible with the alpha.40 print-attempt telemetry — the API still gets one report per attempt
- Offline mutation queue + auto-pause on critical failure from the original Sprint 9 plan deferred to a future sprint — print resilience is the higher-leverage win to ship first
From alpha.68 · Sprint 8 (Kitchen mode + Undo toasts)
- Kitchen mode toggle — new Settings → Operations → "Kitchen mode" switch. When on, the Orders list renders with larger title fonts (short code 18→24sp, order number to match) so the screen reads cleanly from across a kitchen. Layout unchanged; just typography scales. Per-device pref via new
KitchenModePreferences singleton (Sprint 8 ships the lightweight version — full tile-mode layout + auto-bumping land in a future sprint)
- Undo snackbar helper — new
UndoSnackbar.kt with UndoableAction + showUndoable + BrandedSnackbarHost. Pairs a brand-styled snackbar with a "Undo" action button so destructive-but-reversible ops can offer a 4-second back-out window without per-screen plumbing
- Undo on order status changes — flipping an order to preparing / ready / delivering now shows "Marked ready · Undo". Tapping undo reverts the status via the same single-order API path. Reverting captures the prior status before the optimistic flip, so a misclick on the wrong card is one tap to fix. Terminal transitions (cancelled / completed) intentionally don't offer undo because the customer messaging has already gone out
From alpha.67 · Sprint 7 (Map polish)
- ETA pill on every delivery row — shows "Ready in 8m" / "Ready 2m ago" / "On the way · 5m" so the operator can see at a glance which deliveries are slipping without opening the popup. Pill colour mirrors the existing status (Waiting/Assigned/In transit/Late) so urgency reads instantly. Same summary appears inside the delivery popup so the two surfaces never disagree
- Tap-to-call shortcut on the popup card — small phone-icon bubble next to the customer name when a number is present. Tap to open the dialer pre-filled with the number — the merchant can chase a stuck delivery without copy-pasting from Order Detail
- Notes: live driver position + route polyline + driver reassignment were on the audit list but require API additions we haven't shipped on the backend yet (no driver-position or driver-list endpoints in the POS API). Those land alongside the server work — not in this sprint
From alpha.66 · Sprint 6 Part 2 (Auto-promote + Settings)
- Auto-promote scheduled orders — a scheduled order whose
scheduledFor is within the promote window now surfaces in the New tab alongside fresh just-placed orders, so the kitchen doesn't miss the prep window. Default window is 30 minutes. The Scheduled tab still shows it too — auto-promote is purely a duplication of surfacing, no state changes, no API calls
- Scheduled metadata on every scheduled card — the card now shows its scheduledFor time whenever
isScheduled is true, not just on the Scheduled tab. Without this, an auto-promoted order in the New tab looked identical to a fresh one and was easy to mistake
- Settings → Scheduled Orders — new card under Operations with two knobs:
- Promote ahead (10/20/30/45/60 min) — when an auto-promote should kick in
- Upcoming strip lookahead (2/4/8/12/24 h) — window the alpha.65 Upcoming strip considers worth showing
- Persisted locally via new
SchedulePromotePreferences singleton (SharedPref-backed StateFlow). Purely a client-side UX setting — no API or DB change. Tab badges + filteredOrders both stay in lockstep via the new combine-based stream
From alpha.65 · Sprint 6 Part 1 (Scheduled redesign — Upcoming strip)
- Upcoming strip on Orders — sticky horizontal row that surfaces every scheduled order due in the next 4 hours, regardless of which tab is active. Sits between the tab bar and the search row. Hides itself when there's nothing pending in the window so it doesn't eat space on a quiet shift. Each chip shows the time + customer name and a ring colour that reflects urgency: red "Due now" for anything past its scheduledFor, amber "in Xm" for the next 30 minutes, blue for everything else in the lookahead window. Tap a chip to jump straight to that order's detail screen
- Part 1 is purely additive — the Scheduled tab is unchanged. Part 2 (alpha.66) will layer in auto-promote at T-prepTime and a Settings control for the lookahead window
From alpha.64 · Sprint 5 (Bulk + Takeover polish)
- Bulk selection on Orders — long-press any order card to enter selection mode. Tap more cards to add or remove them from the selection (a checkmark + Primary border show what's picked). A bottom action bar surfaces three batch ops: Mark preparing, Mark ready, Print. "Select all" picks every order matching the current tab + search + chip; Cancel exits. Operations are sequential under the hood (same pattern as Pause-all-in-category) and confirm via the existing snackbar with success/failure counts
- Inline items expand in new-order takeover — the "X items · $Y" line is now tappable. Tap to reveal the actual item list right inside the takeover wash (qty + name + variant + modifiers + notes) without leaving the screen. Falls back to the static summary while the full order fetch lands. Helps the merchant accept-vs-reject without context-switching to Order Detail. List endpoint doesn't return items, so MainViewModel kicks off a background fetch the moment a new takeover surfaces; result cached per orderId in
takeoverOrderFull
- Reject templates in new-order takeover — already shipping in the existing reject flow (Too busy / Item unavailable / Closing soon / Other) so no new code here; called out for completeness against the audit list
- New
bounceCombinedClick Modifier in Interactions.kt — adds long-press support to the existing spring-press feel. Fires HapticFeedbackType.LongPress automatically so the gesture is unmistakable on devices where the press scale alone is subtle
From alpha.63 · Sprint 4 (Auth + Order Detail)
- Sticky customer header on Order Detail — name + tap-to-call shortcut pinned above the scrollable detail content so the merchant always knows whose order they're working on while scrolling through items, timeline, and chat. Coloured avatar bubble with the first initial, "X prev" chip for returning customers, and order type + short code as a subtitle. The full
CustomerInfoCard further down keeps the address + notes; this is just the always-on top-of-mind summary
- Returning-customer chip on the full Customer card too — small blue "X previous" pill next to the customer name pulled from
customerOrderCount. Kitchens love knowing this is a regular ("she always asks for extra sauce") and the field was already on the API
- "Switch user" link on PIN login — small pill that appears below the numpad once any digit has been typed, clearing the entry in one tap. Multiple staff share one tablet — faster than four backspaces when the wrong person starts typing. Hidden when the screen is fresh or during lockout so it doesn't add visual noise
From alpha.62 · Sprint 3 (Menu bulk actions)
- Pause all / Resume all per category — new "Pause all" link in the category header (next to "Pause sizes") that pauses every product in that category for 1 hour with a single tap. Flips to a green "Resume all" once everything is paused. Shows "Pause rest" when some items are paused but not all. Loops the existing single-product endpoint so the API needs no changes
From alpha.61 · Sprint 2 (Settings rework)
- Settings grouped into named sections — Account / Operations / Hardware / Diagnostics. Small uppercase labels with consistent letterspacing above each chunk, reused via new
SettingsGroupHeader component. Section content unchanged — purely visual organisation
- Printer health dots — each printer in the Printers section now shows a small green/red dot next to its name based on alpha.40 print-attempt telemetry. Red = last print failed, with the error message surfaced inline under the IP. Green = last print succeeded. No dot = no telemetry yet (fresh config)
- Diagnostics section — new card at the bottom showing API base, push subscription state, paired store name + truncated ID, device ABI, Android version. Useful when supporting a merchant remotely — they can read these values out
- (Settings search bar deferred to alpha.61b — wanted to keep this sprint scope tight)
From alpha.60 · Sprint 1
First ship of the design-audit rollout. Four tactical Orders-screen + header wins. All use the existing app palette — no new colours or component patterns introduced.
- Today's at-a-glance stats in the store header — orders count, revenue, active count appear in a translucent strip below the existing status chip. Refreshes on the same 15s polling cadence as the rest of the header. Computed client-side from the polled orders list since-midnight (cancelled orders excluded from revenue, refunds counted as completed sales). Auto-hides until the first poll lands so cold-start doesn't flash zeros
- Search bar + filter chips on Orders — search across short code / order number / customer name / phone / item name (all client-side, runs on every keystroke). Filter chip row: All / Pickup / Delivery / Dine-in / Overdue. Chip styling intentionally matches the Map screen filter chips for visual consistency. Tapping the active chip clears it (same toggle pattern as Map)
- 4-dot status timeline on each order card — placed → accepted → preparing → ready → done. Green for completed stages, amber for current, grey for upcoming. Inline label shows "Preparing · 8m to ready" pulled from
estimatedReadyAt. Cancelled orders collapse the dots to a single red "Cancelled" label. Toggleable via showStatusTimeline param on OrderCard for callers that want the older compact look
- Pause Store modal customer preview — new yellow-bordered preview box shows the EXACT message customers will see on the storefront while paused, updates live as the merchant picks reasons/durations. Smart phrasing — "Busy" preset becomes "We're slammed right now, back at 7:48 PM." — so the storefront message reads like a human wrote it, not a form
From alpha.59
- Refund button wired up on Order Detail. The
RefundDialog + refundOrder() view-model method already existed (and shipped weeks ago) but had no UI trigger — completed orders showed an empty bottom action bar. Now: completed or cancelled orders that took payment + still have refundable amount get a full-width red "Refund" button at the bottom. Two-stage flow: tap → Manager PIN dialog (action = "refund_order") → on authorize, the API returns a pinVerificationToken → refund amount/reason dialog → POST /pos/orders/:id/refund with the token. Same pattern as the unpair PIN gate but with the token actually used in the mutation
- Internal:
ManagerPinResult.Authorized now carries the pinVerificationToken so any sensitive action that needs token-based server verification can wire through the same dialog. Existing unpair callers ignore the token (their endpoint doesn't accept one)
From alpha.58
- APK size cut from ~83 MB to ~37 MB (56% smaller) by stripping the non-arm64 native libraries. Mapbox was shipping its native code for arm64-v8a, armeabi-v7a, x86, and x86_64 — but every device we actually ship to is arm64 (Sunmi terminals and basically every modern Android tablet). Added
ndk.abiFilters set to arm64-v8a only. No feature loss, no UI change. Once we move to Play Store, AAB delivery handles per-device ABI stripping automatically and this filter becomes redundant but harmless
From alpha.57
- Auth screens (pairing + PIN login) now play a looping steamy-kitchen video as the background with a dark overlay. Streamed from
spatula-assets R2 via the API (not bundled — APK stays the same size). Source clip was 65.9 MB, compressed with ffmpeg to 423 KB H.264 at 540×960. ExoPlayer caches it locally on first load so subsequent app launches play the loop offline
- Phone emoji icon removed from the pairing screen — it was a placeholder anyway
- Style pass on both screens: soft text shadow on the headings (so titles stay legible against any frame of the video), translucent black fill on the pairing screen's 6 digit boxes (so the digits never fight a busy frame). PIN entry styling kept as-is — already worked over the video with the overlay
- New API route
GET /assets/video/merchant-app/:filename in apps/api/src/routes/assets.ts — proxies the spatula-assets R2 bucket with a 30-day immutable cache header. Bump the filename to swap the video; the Android app picks up the new URL via MerchantVideoAssets.AUTH_BACKGROUND
From alpha.56
- Fixed two unpair bugs: the Settings → Unpair Device button now correctly prompts for a manager PIN (was missing the prompt entirely), and the PIN-login screen's gear-icon unpair option now also requires a manager PIN (was previously unpair-without-PIN). Both flows route through the same new
ManagerPinDialog Composable, which calls POST /pos/authorize-action with the entered PIN + action = "unpair_device". Wrong PIN clears the input and surfaces the API's reason text inline
- Pairing screen copy update — "Go to Dashboard → POS Devices to generate a code" is now "To pair your device go to Dashboard → Merchant Devices and generate a new code" so it matches what's actually in the dashboard
From alpha.55
- Removed the bottom-left Help FAB — it was overlapping the bottom action bar on the Order Detail screen. Walkthroughs are now strictly one-time-per-screen: each tour auto-fires the first time you visit a screen and never appears again. To replay later, clear app data (Settings → Apps → Restaurant Merchant → Storage → Clear data)
- Kept the underlying
WalkthroughManager + per-screen content intact in case we want to re-introduce a replay surface later (e.g. a "Show tour" item inside Settings instead of a floating button)
From alpha.54
- Per-screen walkthroughs replace the old two-step bootstrap tour. First time you land on each screen, a multi-step tour fires explaining what's there. Orders (4 steps), Order Detail (4 steps), Menu (3 steps), Map (2 steps), Settings (5 steps). Every tour has Skip + the existing Next / Got it buttons. Tracked per-screen — skipping the Menu tour doesn't affect whether the Settings tour fires later
- Persistent Help (?) button in the bottom-left corner of every main screen — uses the Phosphor question icon Raj provided. Tap it any time to replay the walkthrough for the current screen. Auto-hides while a tour is already running or during a new-order takeover so it never gets in the way
- Architecture: one app-wide
WalkthroughManager (singleton, SharedPref-backed) drives state. Per-screen content lives in WalkthroughContent.kt — adding a new screen's tour is a one-line addition to stepsForScreen() plus a maybeAutoStart() call from that screen
- Removed the alpha.52 bootstrap "store status + bottom nav" 2-step tour — its content is now folded into the per-screen Orders walkthrough that auto-fires on first sign-in anyway
From alpha.53
- Add Printer wizard — the Test button on each discovered printer now actually prints. Was previously just re-doing the TCP port probe the discovery scan already ran (so it never told you anything new). Now it sends a short slip — "PRINTER TEST · IP · Port · Time · Connection OK" — so you can identify which physical printer responded, helpful when there's a kitchen printer and a counter printer on the same subnet. Slip is intentionally minimal: no template needed, no bitmap rendering, works on both 58mm and 80mm paper
- Cleaned up the "Spatula Merchant" branding line in the existing TEST PRINT receipt at the Configure step — the merchant app is intentionally brand-neutral, that line shouldn't have been there
From alpha.52
- Replaced the canopas spotlight walkthrough with a step dialog. The spotlight only advanced when you tapped exactly on the highlighted target — confusing in practice, and "Tap anywhere" was misleading. New design: a clean modal with title + body + explicit
Next → / Got it button. Two steps with a progress indicator at the top, plus a "Skip tour" link on the first step. No ambiguity about how to dismiss
- Removed the now-unused canopas/compose-intro-showcase dependency — saved ~50 KB of APK size and one less third-party library to track
From alpha.51
- First-install walkthrough tour. Two-step tooltip overlay that fires once on first MainScreen entry, sequenced after the push notification onboarding dialog so the merchant sees them in order (notifications → tour). Step 1 spotlights the store-status header explaining green/amber/red and the open/close toggle; step 2 spotlights the bottom tab bar walking through Orders / Menu / Map / Settings. Tap anywhere to advance, tap on the final step to finish. Persisted via the existing
onboarding SharedPref (tour_seen key) so it never reappears once dismissed. Powered by canopas/compose-intro-showcase 2.0.1 (MIT, Compose-native, ~50 KB)
From alpha.50
- Fixed Settings showing "Push Notifications OFF" after the onboarding dialog had already enabled push. The API has no GET endpoint to read subscription state, so without a local cache the Settings toggle defaulted to false every time the screen was opened. Added a tiny
PushSubscriptionStore (SharedPref-backed singleton) as the single source of truth — both the onboarding subscribe path AND the Settings toggle write to it, and SettingsViewModel seeds its UiState from it on init. Toggle now reflects reality across app restarts
From alpha.49
- First-install push notification onboarding — replaces the cold POST_NOTIFICATIONS prompt Android shows at launch with a proper explainer dialog after PIN sign-in. Title: "Hear new orders the moment they come in", three bullet points (loud alert on alarm channel, wakes the screen, can turn off any time), big "Enable notifications" button + "Maybe later" link. Tapping Enable: re-prompts the system permission (with context this time, so grant rates go up), then subscribes the device's FCM token via
POST /pos/push-subscribe-fcm. Tapping Later: just dismisses, merchant can still toggle from Settings → Push Notifications. Shown once per device — tracked via SharedPref so it never nags after the first dismissal
- Removed the unconditional permission prompt that fired on every cold start of MainActivity — that was the prompt with zero context that most merchants were dismissing without thinking
From alpha.48
- Separated background-alert sound from the user-facing "Ringtone" preset. Restored the original
alert_ringtone.mp3 so the Settings sound picker's "Ringtone" option keeps its old clip. Added a new raw resource alert_message.mp3 with the Message Alert sound, mapped to the "message" sound key in NotificationSoundPlayer. Background-alert nag loop (ForegroundAlertService) + FCM fallback (SpatulaMerchantMessagingService) now both use "message" — so the tablet plays the new clip when the app is backgrounded, but tapping "Ringtone" in Settings still previews the old one
From alpha.47
- New background alert sound — swapped the bundled
alert_ringtone.mp3 in res/raw/ for the new "Message Alert" sound. The background-alert service was already hardcoded to the ringtone_plus key (the loudest preset) so no code change was needed — the file under that key just plays a different clip now. Originals (.mp3 + .wav) also stored in the spatula-assets R2 bucket at audio/merchant-app/ for reuse
From alpha.46
- Order Detail print icon now matches the orders-list reprint pattern — top-bar print icon swaps to a spinner while the receipt is being sent, and the bottom-bar "Print/Reprint" button also shows a spinner mid-print. Backed by a dedicated
isPrinting state in the ViewModel (kept separate from the existing isUpdating so status changes and prints don't fight each other for the spinner slot)
From alpha.45
- Real root-cause fix for "dark buttons too dark to read" — turned out the alpha.43 typography fix never actually worked.
bodyLarge (M3's default text style at the root) carried color = TextPrimary, and Compose's TextStyle.merge() kept that colour when merging into the Button's labelLarge (which had no colour). So every Button's text was rendering slate-900 on slate-800 via the merge. Fixed by dropping bodyLarge.color AND adding explicit color = Color.White + tint = Color.White to every Text and Icon inside every dark-background button across the whole app — belt and suspenders so this can't bite again
- Page surface change — page background nudged from
#F8FAFC (slate-50) to #EEF2F6 (custom slate-150). Cards stay pure white and now visibly lift off the page instead of blending in. Same change everywhere, single line in Color.kt
- Toast/Snackbar bypass — explicit custom Snackbar render at every
SnackbarHost callsite (Orders, OrderDetail, Settings) with containerColor = DarkBackground + contentColor = Color.White. Stops relying on M3 theme propagation, which was leaking the same bodyLarge colour into the Snackbar text
- Order detail bottom bar — Cancel / Print / Late buttons now set
tint on the Icon and explicit color on the Text (was previously relying on Button's content cascade)
From alpha.44
- Caught three more "dark button, dark text" bugs in order-detail dialogs — Cancel Order, Issue Refund, and Notify Customer confirm buttons were missing
contentColor = Color.White. They had only the container colour set (red or amber), so the button text fell through to onSurface (dark) and rendered invisible on the coloured background. Same root cause as the alpha.43 fix, just hiding in three pre-existing dialog implementations
From alpha.43
- Contrast fixes — typography styles were baking
color = TextSecondary/TextTertiary into labelLarge/Medium/Small, which silently overrode every Button's contentColor. Result: white-on-dark buttons rendered slate-on-slate (the "Enable" button was nearly unreadable). Removed the hardcoded colours from the label styles so LocalContentColor cascades correctly — every button across the app now shows its proper text colour
- Toast/Snackbar — explicit
inverseSurface + inverseOnSurface in the M3 ColorScheme. Was falling through to M3's muddy baseline; now locked to neutral-900 background + white text
- Pause pill button — content colour bumped from
TextSecondary to TextPrimary for visible punch on the Menu screen
- Live map — Mapbox token now baked into the build (was missing in alpha.42 because the Infisical env wasn't being passed through). Delivery map shows real tiles again
From alpha.42
- Install fix — enabled v1 (JAR) signing alongside v2/v3 so the APK installs cleanly on every Sunmi firmware. alpha.41 went out v2-only and the Sunmi package installer rejected it as "package appears to be invalid". No code changes, same features as alpha.41
From alpha.41
- Dashboard "Test print" button — tenant owners can now fire a test receipt from the Store Health page. The API pushes an FCM message to the tablet, which runs the print and reports the outcome back. No more "walk to the kitchen to check the printer"
From alpha.40
- Print-attempt telemetry — after every print the app fires a tiny POST to the API recording success/failure + error message. The tenant Store Health dashboard surfaces this per-printer so multi-store owners can spot a dead printer at a glance
From alpha.39
- Multi-template printing per printer — one printer can now print MULTIPLE templates per order (e.g. Kitchen ticket + Customer copy) back-to-back in a single print job with a cut between each. Edit Printer dialog has a new ordered chip picker
- Server-side fix: receipt template assignment now actually persists (was being silently dropped by Zod validation since the multi-template editor first landed)
From alpha.38
- Removed the misleading "Print customer receipts" and "Print kitchen tickets" toggles from each printer's expanded options — they were never wired into the print pipeline (just saved to DB and ignored). Use the Receipt templates picker in the Edit Printer dialog instead
From alpha.37
- Receipt template picker in Edit Printer dialog — switch a printer between Customer copy / Kitchen ticket / any custom template you've created in the dashboard, without delete + re-add. This is what the legacy "Print customer receipts / Print kitchen tickets" toggles were supposed to do but never did
From alpha.36
- App-wide "No Internet Connection" modal — pops the moment connectivity drops on any screen (not just orders), with the wifi-slash icon. Staff can dismiss to keep working; the modal auto-closes the instant connection comes back, and the next outage will re-prompt
From alpha.35
- Offline detection — real-time ConnectivityManager monitoring, amber banner under the tab bar when the network drops, with "Last updated at H:mm" so staff know how stale the list is
- Missed-order recovery — the moment connection comes back, fires an immediate catch-up fetch instead of waiting up to 15s for the next poll
- Edit Printer dialog — tap the pencil icon next to a printer to change name, IP, port, or paper width (58mm/80mm) without deleting and re-adding
- Darker thermal output — luminance threshold bumped from 127 to 160 so anti-aliased glyph edges print as black instead of fading to white. Text strokes look fuller
- Pairs with server-side typography fixes: 28px base font, font-smoothing off, heavier default weight, PNG now clipped to receipt content (no more 10cm of trailing blank reel)
From alpha.34
- Design token cleanup — all hardcoded hex color values across order card, takeover screen, main header, and unpair button now route through the central
ui/theme/Color.kt palette
- Added four new tokens (
SuccessStrong, SuccessSubtle, WarningStrong, DarkSurface80) for consistent text/background contrast pairings
- Visually identical to alpha.33 — purely a maintainability fix so future palette changes propagate in one place
From alpha.33
- Printer icon on order detail page — reprint receipt for any order (including completed/cancelled) from the top bar
- Background alert sound now only plays when device is paired, staff is signed in, and the store is actively accepting orders — silent on unpaired tablets, signed-out shifts, and closed/paused stores
- Receipt PNG render: pinned Inter fonts to v3.19 (.otf) — v4.0 dropped TTF files which Satori needs to render; fixes the text-fallback-every-time bug
- API now logs structured errors for failed receipt renders — Sentry + Telegram alert fire on every failed print so we see the cause instantly
From alpha.32
- Server-side receipt rendering — printed receipts now match the dashboard editor preview bit-for-bit (no more device-side drawing)
- Per-printer receipt template — pick which template each printer uses (Customer copy, Kitchen ticket, etc.)
- Real test print in the Add Printer wizard — sample receipt prints on the chosen template + paper width before save
- Templates load from the dashboard's new multi-template editor
In this build
- Device pairing (6-digit code)
- Staff PIN login with progressive lockout
- Orders: 4-tab view (New/Scheduled/Active/Done) with Lottie empty states
- Order detail: items, pricing, status flow, chat, cancel, refund
- Menu: category view, pause/resume products & variants, search
- Map: real Mapbox map with delivery pins, fly-to, popups
- Server-rendered PNG receipts → ESC/POS raster, text fallback on network failure
- Settings: services, printers (with template picker), sound picker, push, drivers, EOD
- New Order Takeover: full-screen alert with audio + countdown
- Background nag alert: repeating sound when app is minimized (silent when paused/closed)
- Pause Store Modal: duration + reason + service selection
- Bottom tab navigation with colored store status header
- Full API client (80+ POS endpoints wired)