hagz
All work

Product design · Android tablet app

Cupsync

A point-of-sale app that runs a café counter on 1 Android tablet. It keeps selling offline and sends the owner's daily report to Google Sheets.

Role
Solo product designer and front-end engineer. Research, flows, UI, prototyping, build and field testing.
Client
Sidecup, a café in the Philippines
Timeline
Started September 2026 · in use at the counter
Platform
Android tablet (Capacitor) and web, mobile-first
Tools
Claude Design · Next.js · Tailwind CSS · Capacitor · Dexie (offline storage) · Google Sheets
Status
Live at the counter, still iterating from field feedback
Cupsync order screen: categories on the left, drink tiles in the middle, and an empty current order with a 3-step hint on the right
The order screen on the tablet. Categories on the left, drinks in the middle, the order building on the right.

Overview

At Sidecup, Cupsync replaced a notebook-and-calculator routine with a till that works offline, handles local tax and discount rules, and reports to the owner in Google Sheets.

The core design problem: make every common task take a few taps under rush-hour pressure, without hiding the edge cases the business can’t afford to get wrong: discounts, partner orders, stock, and cash at close.

Users and context

3 people use the same tablet for different jobs, and the cashier’s job sets the pace for everyone else.

User Main job When they use it What they need most
Cashier Take orders and payment All shift, often with a queue Speed, clear totals, no maths
Barista Make drinks in order Glancing over from the bar What to make next, and how long it’s waited
Owner Run the business End of day, often off-site Trustworthy numbers without opening the POS

Constraints that shaped the design:

  • 1 shared tablet at the counter, used standing, often one-handed.
  • Unreliable café Wi-Fi: the till must never stop because the internet did.
  • Philippine rules: 12% VAT, and the legally required 20% discount for senior citizens and persons with disabilities (PWD).
  • Payments by cash, card and InstaPay QR, the Philippines’ instant bank-transfer network, scanned from GCash, Maya or any bank app.
  • Some orders come through partner establishments and must be reconciled monthly.
  • Rotating staff with little training time, and a PIN login per person.

Design principles

5 rules settled most arguments before they started.

  1. The counter never waits. No step may block a sale: not the internet, not a stock count, not a sync.
  2. Ask only when it matters. Options appear only for items that have them; everything else is 1 tap.
  3. Warn, don’t stop. Problems are shown where the work happens, and staff decide what to do.
  4. Status must be honest. An indicator that is sometimes wrong is worse than none, because people stop checking.
  5. Meet each user where they already are. The owner lives in Google Sheets, so the report goes there.

Rush-hour order entry

A plain drink is 1 tap; a drink with add-ons is 2 or 3.

Scenario. 6 customers are queued. The cashier needs to ring up an Americano with an extra shot, an ube milk and a cocktail without slowing the line.

Cupsync order screen on a phone: Coffee, Non-coffee and Refreshers chips across the top, 4 coffee tiles with a brown category stripe, and a bar at the bottom reading No items yet, ₱0.00
The order screen on a phone. Categories become chips across the top, and the bar at the bottom keeps the running total in view.
Cupsync Add-ons sheet for Salted caramel over the dimmed order grid: optional Espresso shot +₱30.00 and Syrup +₱10.00 buttons, with Cancel and Add to order ₱120.00
The Add-ons sheet. Add-ons are optional, so tapping Add to order straight away is fine.
  1. Pick a category from the rail: Coffee, Non-coffee, Refreshers or Cocktails.
  2. Tap a tile. It flashes to confirm the item went in.
  3. Coffee and milk drinks open a short Add-ons sheet (espresso shot +₱30, syrup +₱10). Confirming with nothing picked is fine.
  4. Plain items, like refreshers and cocktails, go straight to the cart.
  5. On a phone, a review bar keeps the running total in view.

Design decisions

  • Options only where they exist. An earlier version opened a size picker for most drinks. The real menu has 1 size, so that step was cut.
  • Fixed-height tiles. Names wrap to at most 2 lines, so the grid stays aligned and scannable at a glance.
  • Colour stripes per category. Staff find their place by colour before reading a word.
  • Ingredients on cocktail tiles. Names like “Kariñosa” don’t say what’s in the glass, so the answer sits on the tile.

What to measure: seconds and taps per order, and items corrected after being added.

Taking payment

1 payment screen handles 4 situations that look alike but must be recorded differently.

Early greyscale wireframe of a tablet orders screen: a list of draft, partner, completed and cancelled orders on the left, and on the right the selected order's items, a payment tag with Cash and Online payment options and a reference number field, the ingredients it will deduct from stock, and Void, Print slip, Save as draft and Take payment buttons
An early wireframe of the orders screen, made in Claude Design. Payment was a single Cash / Online tag. The build split it into the 4 situations below, because each one is recorded differently.
Situation What the screen does Why
Cash Shows change due; warns when the amount given is short Removes mental maths under pressure
QR / InstaPay Shows the café’s QR full size, with “Check the transfer arrived before handing over the order” A screenshot of a payment is not a payment
Senior / PWD Applies the discount and VAT rules automatically It’s the law, and hand-calculating it causes errors
Partner order Requires the partner slip reference before completing Month-end reconciliation fails without it

The QR button first said “GCash”. Customers paid from GCash, Maya and bank apps, so it became “QR / InstaPay”, and past records were unaffected. The partner slip reference is the only required field, because a missing one costs the most at month end.

Cupsync Take payment sheet: In-House or Partner classification, Cash, Card or QR / InstaPay payment choice, a Cash received field and a Change due line, with the Spanish latte order and VAT breakdown behind it
The payment sheet. Staff tag where the sale came from, then how it was paid. Cash asks for the amount received and works out the change.
Cupsync order history on a phone: ₱12,724.28 total sales from 27 completed sales, 1 voided order of ₱205.00 not counted, filters for In-House, Partner, Cash and QR / InstaPay, and a list of paid orders
Order history. Every sale keeps its payment type and channel, so in-house, partner, cash and QR orders can each be filtered out, and voided sales are kept but not counted.

When the internet drops

The till keeps selling offline, and the Online badge now tells the truth about the connection.

Scenario. The café Wi-Fi is connected but has no internet. Orders keep coming.

Cupsync header with a green Online badge next to Held Tabs, Recent Orders and the cash drawer float
Connected: the badge says Online.
The same Cupsync header with an amber Offline badge, while the order screen stays fully usable
Within seconds of the internet dropping, it says Offline. The till keeps selling.

What went wrong in the field. The header said Online while the tablet had no internet at all. Staff use that badge to judge whether syncing and the Google Sheets upload can work, so a wrong green light hides real problems.

Why it happened. The badge reported “connected to a network”, not “can reach the internet”. On the Android tablet it could never change at all.

What changed

  • The badge now confirms a real connection: when the app opens, when it returns to the foreground, when the network changes, and every 30 seconds.
  • It flips to Offline within seconds of losing the internet, and back once it returns.
  • Sales still save on the tablet first, so nothing at the counter depends on the badge being green.

A status light earns trust slowly and loses it in one shift. If it can’t be right, it shouldn’t be there.

What to measure: uploads that fail while the badge says Online (target: zero).

Barista serve queue

Paid orders are listed oldest first, because the barista’s question is “what’s next?”, not “what just came in?”. Each order has a live wait timer that changes colour as the wait grows: the colour is read from across the bar, the minutes up close. Drinks can be ticked off one at a time, since big orders are often handed over in parts, and orders still waiting on payment are timed too, so none can quietly sit forever.

Running out of stock

Every sale deducts its recipe from stock. When an ingredient runs low, each drink that uses it is tinted amber and names the cause (“Low: Matcha powder”), then turns red at zero. The app warns but never blocks a sale: stock counts drift from reality, so staff make the final call with a 1-tap Sold out toggle.

Cupsync stock catalog listing cups, oat milk, syrup and matcha, with 2 items highlighted as out of stock
The stock catalog. Each item converts its purchase unit into recipe units, shows which drinks use it, and is flagged when it runs out.

Closing the shift

1 End Shift button produces the day’s report, checks the cash drawer, and sends the numbers to the owner’s spreadsheet.

Scenario. It’s 9 pm. The admin on shift wants to go home; the owner wants to know how the day went without driving to the café.

Cupsync End shift confirmation: it will generate the report for 2026-10-25 and upload it to Google Sheets, with a red warning that 3 held tabs worth ₱410.00 are still unpaid and will be listed as owed, not as sales
Before closing, the app flags unpaid held tabs and says exactly how the report will treat them. The Google account is blurred.
The owner's Google Sheet of orders: columns for order ID, order number, date, time, staff, status, payment method and payment tag, with completed and cancelled orders from 16 October paid by cash and gcash
The owner's Google Sheet, filled in by the app. Older rows still say "gcash": renaming the button to QR / InstaPay left past records as they were.
  1. An admin taps End Shift; other staff don’t see the button. A confirm dialog shows the date, says whether the report will go to Google Sheets or stay on the tablet, and flags anything to deal with first: unpaid held tabs, ingredients low on stock, or a report already made today.
  2. Confirming builds the end-of-day report (the Z-reading, a till’s standard end-of-day report) from today’s orders: gross and net sales, discounts, VAT, sales by payment type and by in-house vs. partner, and voided orders with their total.
  3. Check the cash drawer: starting float + cash sales − cash expenses = what should be in the drawer. QR and card sales are shown beside it, not counted in it, because they never touch the drawer.
  4. Unpaid held tabs go on the report as money still owed, not as sales, including tabs opened on an earlier day.
  5. The report saves on the tablet first, then uploads to Google Sheets if an account is connected. If the upload fails, the report is still saved and can be uploaded later from the menu. Nothing is lost.
Cupsync expenses and stock ledger: ₱3,070.00 total spend split by how it was paid and what it was for, filters, and expenses such as Barista Oat Milk adding 2,000 ml to stock
The expenses ledger. Cash taken from the drawer is called out so the close adds up, and restocking an ingredient adds it straight back to stock.

Design decisions

  • The owner never has to open the POS. They already work in Google Sheets, so that’s where the report lands.
  • Separate cash from everything else. If QR payments counted as drawer cash, the drawer would look short every single day.
  • Closing never fails because of the internet. The upload is a bonus on top of a report that always saves.
  • Warn, don’t block. Unpaid tabs and low stock are flagged, but closing still goes ahead, and the report states how unpaid tabs were counted.
  • The date is read when you confirm, not when the dialog opened, so a dialog left open past midnight still closes out the right day.
  • Running it twice adds a report instead of replacing one, so an accidental second close can’t overwrite the first.

What to measure: minutes to close, and days where the drawer and the report disagree.

Lessons from field testing

The most useful findings came from watching the app on the real tablet, not from the desk.

Found What staff saw Root cause Fix
Oct 2026 Tapping “Connect Google account” did nothing The error message only showed once an account was already connected Errors now show in every state, with Google’s reason
Oct 2026 The account picker opened twice in a row The second sign-in step didn’t know which account was already chosen Pass the chosen account through; 1 picker
Oct 2026 “Online” shown with no internet The badge checked for a network, not for the internet Confirm a real connection every 30 s and on every change
Oct 2026 Payment button said “GCash” for every QR payment The label matched 1 app, not how people pay Renamed to “QR / InstaPay”

The pattern: every one was an empty or wrong state, not a missing feature. Designing the failure path deserves as much care as the happy path.

Working with the client

JB, the owner, used the app at the counter and passed on whatever came up during shifts. 2 examples show how that turned into changes.

A forgotten End Shift left a gap in the owner’s sheet. If the person on shift didn’t tap End Shift, that day’s report was never made or uploaded, so the owner’s numbers for the day were missing. JB reported it, and I shipped a fix the same day: a System Settings screen where the owner sets the café’s business day hours and can export any date or range to Google Sheets. A day nobody closed can still reach the owner’s sheet, and if the café stays open past midnight, late sales count toward the right day. Each upload now goes into a new dated file by default, so past exports are kept rather than overwritten.

Cupsync System Settings, Export and Day Hours tab: upload to a new dated file each time (recommended) or replace one spreadsheet, a toggle to include voided orders, business day opening and closing times, and the connected Google account, blurred
System Settings. Business day hours decide which day a late sale belongs to, and a second tab exports any date or range. End-of-day reports still come only from End Shift.

“How long has this tab been waiting?” JB wanted to see how long held tabs had been sitting unpaid. A bare number doesn’t tell staff when to act, so each held tab got a running timer in 3 states: green under 5 minutes, amber from 5, and red from 10. It’s the same scale the barista’s serve queue uses, so staff learn 1 rule for both.

Cupsync Held Draft Tabs sheet listing 3 unpaid tabs, OR #000174, #000173 and #000170, each with a red Unpaid timer over 71 hours and its total
Held tabs, each with a running timer. These 3 test tabs have waited for days, so all of them show red.

Looking for friction before it’s reported. In 2 weeks of field testing, the End Shift gap was the only bug the owner reported, so the check-ins now focus on small frictions instead of breakages. One result: resuming a held tab now opens the order straight away, 1 tap fewer at the moment a customer is waiting to pay.

Outcomes and measurement

Sidecup has been running the app at the counter for about 2 weeks of field testing, with the café’s real menu and payment QR. The next step is putting numbers on its impact.

  • Android tablet app and web version from 1 codebase, working fully offline.
  • Real menu: 14 drinks in 4 categories, with add-ons.
  • Cash, card, QR / InstaPay, senior / PWD discount and partner-order flows.
  • Serve queue, stock warnings, End Shift report and Google Sheets export.

One of our biggest worries when introducing new tech was the learning curve for our staff. To our relief, the interface is amazingly clean and intuitive. New baristas and team members pick it up in a matter of minutes. No complex manuals or lengthy training sessions required!

JB, Owner, Sidecup

Measurement plan. Baselines to be captured from the notebook routine and the first weeks of use.

Metric How it’s measured Target
Time per order Timed sample of 20 orders at peak Faster than the notebook routine
Payment errors Drawer vs. report mismatches per week Zero
Longest wait Serve-queue timestamps Under 10 min at peak
Partner orders missing a reference Sheets export check, monthly Zero
Status trust Uploads failing while badge shows Online Zero
Staff onboarding Time until a new hire rings up orders alone 1 shift or less

Reflection and next steps

Building the product myself made every design decision testable within a day, and showed me how much of good UX lives in states nobody draws in a mock-up.

What I’d do differently

  • Interview the owner and staff with a script before building, instead of learning mostly from field bugs.
  • Capture baselines on day one; numbers are much harder to recover later.
  • Prototype the failure states (offline, sign-in errors, empty queue) as deliberately as the main screens.

Next

  1. Let staff manage the menu and prices themselves; today a menu change needs an update to the app.
  2. Add a “changes not uploaded yet” badge so the owner’s sheet is never silently out of date.
  3. Run a usability session with a new hire and record the first shift.
  4. Publish the app to production with Google, so sign-in no longer expires every 7 days.
Next case studyCafé Mystika