The counter doesn't have the seconds a search field costs, so the scanner is the input method
An invoicing app for a trade counter where the barcode scanner is how lines get added (the main path, not a shortcut beside a search box) and an invoice can be finished and reprinted from the phone that raised it.
The shape of the work
- Industry
- E-commerce & Retail
- Duration
- 10 weeks
- Cooperation model
- Fixed price
Client name withheld under NDA. Engagement details are shown to the extent our agreement permits.
What we were brought in to do
The wholesaler serves trade customers over a counter. We built the app their staff raise invoices on: scan a code, the line appears, finish the invoice, find it again in history. The product list lives on the device, so a weak signal never stops a sale.
A trade wholesaler serving customers over a counter, where staff raise invoices standing up with a queue forming behind whoever is being served. The engagement was commissioned after a stocktake week in which counter throughput was measured properly for the first time, and the app in use turned out to be the slowest step in the transaction.
Mobile Engineering
Where the old way broke
Their previous app had a search field with a scan button beside it. Staff used the search field, because the scanner took two taps to reach and failed silently on a worn label. Each line took long enough that a queue formed at busy times, and the app got blamed for the queue.
Two taps is enough to make a feature disappear. Staff had learned the fast path was typing, and typing a product code at a counter takes long enough per line that a five-line invoice holds up a queue. The scanner also resolved against the server, so on a weak signal it added a wait to a path that was already losing.
Open the app on the scanner. A scanned code resolves against a product list held on the device and lands straight on the invoice; search still exists for labels too scuffed to read. The slow path is now the exception.
The app opens on the scanner
Scanning is the default input. Nobody has to reach past a search field for it.
The app opens on the camera with the scanner live. Scanning is the screen itself, because on a busy counter the input that takes an extra tap is the input nobody uses. Search still exists for scuffed or missing labels, one gesture away, but it's now the exception where it used to be the default.
- Opens on a live scanner, not on a search field
- Search is one gesture away, as the exception
- The default input is the fast one
The escape hatch for a scuffed label: the scanner paused one swipe away, and a typed search for “elbow” resolving against the 4,182 lines held on the device.
Lines on the invoice with no signal at the counter: every line resolved from the on-device catalog, and the ball valve's price flagged as coming from a stale local record instead of used silently.
Catalogue held locally
A scan resolves against on-device data, so a weak signal at the counter doesn't slow a sale.
The product catalog is held in SQLite on the device and refreshed in the background, so a scan resolves locally in milliseconds whatever the signal at the counter. Prices and stock reconcile when a connection is available, and an item whose local record is stale says so on the line instead of silently invoicing an old price.
- Catalog in on-device SQLite; scans resolve in milliseconds
- Background refresh when a connection exists
- Stale prices flagged on the line, never used silently
Rescan increments
Scanning the same item twice raises the quantity, so there's no second line to tidy up later.
Scanning the same item twice increments the quantity on the existing line instead of adding a second line to merge later, because a counter scanning six of one thing wants a line that says six. Decrement and remove are both reachable from the line without leaving the scanner, so a correction doesn't cost a mode change.
- Rescan increments the existing line
- Corrections reachable from the line, without leaving the scanner
- No tidying pass at the end of an invoice
The same code scanned twice: the copper elbow line goes from 2 to 3 instead of gaining a duplicate, with decrement and remove on the line and the subtotal exact to the cent.
An unrecognized code: the scanner reads it, finds no product, and opens the attach flow so the person holding the carton picks the product and maps the code for good, with nothing sent to the office.
Unknown codes get attached
An unmapped barcode opens a short attach flow, so the mapping is fixed by whoever is holding the item.
An unrecognized barcode opens a short attach flow right there: pick the product, confirm, done. The mapping is fixed permanently by the person actually holding the item, the only one who knows what it is. The alternative, a note for someone in the office to resolve later, is how barcode catalogs stay broken for years.
- Unknown codes open an attach flow immediately
- Fixed by the person holding the item, not queued for the office
- The mapping is permanent from the first occurrence
History at the counter
Invoice history held on the phone: today's invoices with customer, lines, time and amount, reprint on every row, and two invoices not yet synced that are still here to find.
Finished invoices stay on the device, so a reprint is a tap instead of a call to the office.
Completed invoices stay on the device with their line items, so a reprint or a lookup is a tap at the counter instead of a phone call to the office and a wait. History is searchable locally by customer, date and amount, and syncs upward without the local copy depending on that sync having happened.
- Completed invoices retained locally with their lines
- Reprint and lookup are a tap, not a phone call
- Local history doesn't depend on the sync having run
What we built together
- 01
Made the scanner the screen the app opens on, not a button next to search
The app opens on a live scanner, with no search field standing in front of it, because the input two taps away is the one nobody uses.
- 02
Held the product list on the device so a scan resolves without waiting on the network
The product catalog is held in on-device SQLite and refreshed in the background, so a scan resolves in milliseconds whatever the counter's signal.
- 03
Kept manual search for damaged labels, as an escape hatch off the main path
Manual search remains one gesture away for scuffed labels, as the escape hatch it should always have been.
- 04
Made an unrecognized code offer to attach itself to a product instead of just failing
The attach flow exists because the alternative, a note for the office, is how barcode catalogs stay broken for years at a time.
- 05
Kept finished invoices on the device so history and reprint work at the counter
Completed invoices stay on the device with their lines, so a reprint or lookup is a tap instead of a call to the office.
Operational results after launch
0
Taps to add a scanned line
Scanner
Screen the app opens on
0
Network calls per scan
None
Unknown codes reported to office
Taps to add a scanned line is zero: the scan is the action. Screen the app opens on is a fact about the launch route. Network calls per scan is zero, because resolution is local. Unknown codes reported to office is none: the attach flow resolves them at the counter.
Client name withheld under NDA. Figures are approximate, drawn from the engagement’s own reporting.
Phase by phase
Phase 1: The Product List
On The Device
Synced the catalog to local storage so a scan resolves without a round trip. At a counter, the difference between instant and one second is whether staff trust the scanner at all.
- Local Catalogue
- Sync
- Barcode Index
Phase 2: Scan To Line
The Default Path
Built the scan screen as the app's landing surface. A recognized code adds its line immediately; scanning the same code again increments the quantity instead of adding a duplicate.
- Scan Screen
- Line Add
- Quantity Increment
Phase 3: Unknown Codes
Offer, Do Not Fail
Made an unrecognized barcode open a short attach flow, so an unmapped code gets mapped by the person holding the item, with nothing reported to the office.
- Unknown Code Flow
- Attach To Product
- Confirmation
Phase 4: Invoice And History
Finish And Find Again
Built invoice completion, on-device storage and the history view, so a customer asking for a copy gets it at the counter, no call to the office needed.
- Invoice Completion
- Local History
- Reprint
The catalog on the device: 4,182 lines in SQLite with a current barcode index, prices and stock waiting to reconcile until signal returns, and the stale-price flag explained.
About our collaboration
- 01
We spent two mornings behind the counter before writing anything, and that's where the core decision came from. Staff weren't avoiding the scanner because it was unreliable; they avoided it because reaching it cost more than typing did. The fix was placement, not accuracy.
Ten weeks, fixed, with counter staff using builds from week three at real trading times, never in a quiet demo. The rescan-increments behavior came from watching that: staff scanning six of one item were producing six lines and tidying up afterward, which nobody had reported because it didn't feel like a bug.
What we'd carry into the next one
A feature two taps from where work happens is a feature that doesn't exist.
A feature two taps from where the work happens may as well not exist, however much better it is than the alternative staff actually reach for.
Resolve scans against on-device data. At a counter, a one-second wait is enough to lose staff trust in the scanner.
At a counter with a customer waiting, a one-second network wait is enough for staff to stop trusting the scanner and go back to typing.
Rescanning should increment, because the alternative is duplicate lines somebody tidies up later.
Rescanning has to increment. Otherwise you get duplicate lines that somebody tidies at the end: a step nobody reports and everybody resents.
An unknown barcode is best fixed by the person holding the item. An office queue is where it stays unknown.
The person holding the item is the only one who knows what an unknown barcode is, so routing it to an office queue guarantees it stays unknown.
Scan simulator, no signal
The scan is the action. Four things that happen at a counter, and none of them waits on the network.
A readable code, the same code again, a code nobody has mapped yet and a label too worn to read. Each one resolves against the catalogue on the phone, and the invoice lines change the way a counter needs them to. Pick one, or use the arrow keys once a tab is focused.
Counter 2 · no signal · catalogue of 4,182 lines on the phone
Step timings are illustrative, not measured
- 1PTFE thread tape, 1/2" × 520"TT-050-520 · $0.89 ea · new line$0.89
- 1PEX-B tubing, 1/2" × 100 ftPX-B050-100 · $38.90 ea$38.90
- 4Push-fit coupling, 1/2"PF-CPL-050 · $6.15 ea$24.60
- Camera reads the label0 78491 10228 4
- Barcode index on the phoneMatched TT-050-520 among 4,182 lines
- No line for it yetLine added at qty 1
- Nothing sentThe lookup never left the device
The line is on the invoice before anyone touches the screen.
From a code under the camera to a line on the invoice, with no network in between
React Native with Expo, TypeScript, on-device SQLite and a REST API behind it. The network sits at the edge of the diagram on purpose: at a counter, a scan that waits on signal is a scan staff stop trusting.
- 01 · TriggerThe scanner is the launch screenThe app opens on the live camera. Scanning is the screen itself; typed search is one gesture away for scuffed labels.
- 02 · ResolveBarcode index on the phoneA code resolves against the local index with zero network calls. An unrecognized code opens the attach flow instead of failing silently.
- 03 · EngineScan to lineA recognized code adds its line with no taps; the same code again increments the quantity. A stale local price is flagged on the line.
- 04 · StateSQLite on the deviceThe catalogue and every finished invoice with its lines are held locally, so history, lookup and reprint work at the counter.
- 05 · SyncREST API, in the backgroundThe catalog refreshes, and prices and stock reconcile, when a connection exists. Invoices sync upward; the local copy never waits on it.
Right lines, a working counter, no lost codes
Rescans that increment, scans that never wait on signal & unknown codes fixed at the counter
No duplicate lines, no silent old prices
Scanning the same item again increments the existing line instead of adding a second one to tidy later, and decrement and remove sit on the line without leaving the scanner. A product whose local record is stale says so on the line, so an old price never gets invoiced silently.
No signal doesn't mean a stopped sale
The catalog is held in SQLite on the phone, so a scan resolves with zero network calls whatever the signal at the counter. Finished invoices stay on the device with their lines, so history and reprint keep working, and the local copy never depends on the upward sync having run.
An unknown code gets attached on the spot
An unrecognized barcode opens a short attach flow on the spot: pick the product, confirm, and the mapping is fixed permanently by the person holding the item. It never becomes a note for the office, which is how barcode catalogs stay broken for years.
Is a queue forming at your counter while staff type product codes into an app? Scope your build in three minutes.
Scope your buildNearby engagements
Data & AnalyticsA checkout that stopped losing sales to a 6-second load
Profiling found the real bottleneck behind a slow checkout (an N+1 query and an oversized bundle) and tuned both, with before-and-after metrics locked in as a baseline against future regressions.
E-commerce & Retail · 5 weeks
Web PlatformsA reader people finish, and a library that remembers where they stopped
A reading platform for a comics catalog: a browse surface people can actually navigate, a reader that gets out of the way, and a history that puts everyone back on the page they left.
E-commerce & Retail · 18 weeks
Web PlatformsOne number, a whole handset, and a database that keeps up with 125 brands
A metered IMEI lookup service that turns fifteen digits into a device, its specifications and its status. It's sold three ways to three audiences and backed by a device database that maintains itself.
Consumer Electronics · 22 weeks
Let's talk
Running a large platform, shaping a first MVP, or getting a product ready for a funding round? Tell us where you are. We'll shape the process around it, and stay with you after launch.














